인프런 김영한 님의 강의를 참고했습니다.
스프링 핵심 원리 - 기본편 - 인프런 | 강의
스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다., - 강의 소개 | 인프런...
www.inflearn.com
빈의 생명주기
객체의 초기화 작업과 종료 작업이 어떻게 진행되는지 알아둘 필요가 있다.
예제 코드로
package hello.core.lifeCycle;
public class LifeCycleClient {
private String print;
public LifeCycleClient(){
System.out.println("생성자 호출, print = " + print);
beanStart();
call("생성자!");
}
public void setPrint(String print){
this.print = print;
}
//빈 시작시 호출
public void beanStart(){
System.out.println("beanStart: " + print);
}
public void call(String message){
System.out.println("call: " + print + " message = " + message);
}
//빈 종료시 호출
public void beanClose(){
System.out.println("close: " + print);
}
}
이런 class를 만들어보자.
우리는 이 class로 빈의 생명주기에 대해 알아볼 것이다.
package hello.core.lifecycle;
import hello.core.lifeCycle.LifeCycleClient;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
public class BeanLifeCycleTest {
@Test
public void lifeCycleTest(){
ConfigurableApplicationContext applicationContext = new AnnotationConfigApplicationContext(LifeCycleConfig.class);
LifeCycleClient client = applicationContext.getBean(LifeCycleClient.class);
applicationContext.close();
}
@Configuration
static class LifeCycleConfig{
@Bean//(initMethod = "init", destroyMethod = "close")
public LifeCycleClient lifeCycleClient(){
LifeCycleClient networkClient = new LifeCycleClient();
networkClient.setPrint("hello!");
return networkClient;
}
}
}
그리고 테스트코드로 해당 빈을 열고 닫아보자.
실행해 보면
이렇게 print에 값이 들어가 있지 않은 것을 볼 수 있다.
생성자를 통해 생성하는 단계에는 print에 대한 값이 들어가있지 않고, 모두 생성이 된 후에 setter를 통해 값이 들어가기 때문이다.
스프링 빈은 객체를 생성하고, 의존관계 주입이 다 끝난 다음에야 필요한 데이터를 사용할 준비가 된다.
그렇기에 스프링은 의존관계 주입이 완료되면 스프링 빈에게 초기화 시점을 알려주는 기능을 제공한다. 당연히 스프링 컨테이너가 종료되기 직전에 소멸을 알려주는 기능도 제공한다.
스프링 빈의 LifeCycle
스프링 컨테이너 생성 -> 스프링 빈 생성 -> 의존관계 주입 -> 초기화 콜백 -> 사용 -> 소멸 전 콜백 -> 스프링 종료
의 주기를 가진다.
물론 생성자에서 로직들을 추가하여 초기화를 할 수도 있지만, 유지보수를 위해 객체를 생성하는 부분과 초기화하는 부분을 분리는 것이 좋다.
생명주기 콜백
스프링은 3가지 방법으로 빈 생명주기 콜백을 지원한다.
InitializingBean, DisposableBean 인터페이스
설정 정보에 초기화 메서드, 종료 메서드를 지정
@PostConstruct, @PreDestroy annotation
- InitializingBean, DisposableBean 인터페이스
InitializingBean, DisposableBean 인터페이스를 implements 하고 해당 인터페이스들에 있는 메서드들을 afterPropertiesSet(), destroy() 메서드들을 구현하는것이다.
package hello.core.lifeCycle;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
public class LifeCycleClient implements InitializingBean, DisposableBean {
private String print;
public LifeCycleClient() {
System.out.println("생성자 호출, url = " + print);
}
public void setPrint(String print) {
this.print = print;
}
//서비스 시작시 호출
public void beanStart() {
System.out.println("LifeCycleClient.beanStart, print = " + print);
}
public void call(String message) {
System.out.println("call: " + print + " message = " + message);
}
//서비스 종료시 호출
public void beanClose() {
System.out.println("LifeCycleClient.disConnect, print = " + print);
}
public void afterPropertiesSet() throws Exception{
System.out.println("NetworkClient.init");
beanStart();
call("초기화 연결 메시지");
}
public void destroy() throws Exception{
System.out.println("NetworkClient.close");
beanClose();
}
}
이렇게 해당 메서드들을 구현하고 테스트를 진행해보자.
이렇게 생성자가 먼저 호출이 되고, 그 후에 초기화 콜백 메서드가 실행이 된다.
빈의 로직들이 실행이 되고, 소멸 전 콜백 실행 해 소멸이 되는 것을 볼 수 있다.
이 방법은 메서드의 이름을 변경할 수 없고 내가 코드를 고칠 수 없는 외부 라이브러리에는 적용이 불가능하여 스프링 초기에 사용하던 방법이고 최근에는 사용하지 않는다고 한다.
- 설정 정보에 초기화 메서드, 종료 메서드를 지정
설정 정보에 @Bean(initMethod = "init", destroyMethod = "close")를 입력해 메서드들을 지정할 수 있다.
바로 해보도록 하자, 우선 LifeCycleClient를 좀 수정하고
package hello.core.lifeCycle;
public class LifeCycleClient{
private String print;
public LifeCycleClient() {
System.out.println("생성자 호출, url = " + print);
}
public void setPrint(String print) {
this.print = print;
}
//서비스 시작시 호출
public void beanStart() {
System.out.println("LifeCycleClient.beanStart, print = " + print);
}
public void call(String message) {
System.out.println("call: " + print + " message = " + message);
}
//서비스 종료시 호출
public void beanClose() {
System.out.println("LifeCycleClient.disConnect, print = " + print);
}
public void init(){
System.out.println("LifeCycleClient.init");
beanStart();
call("init!");
}
public void close(){
System.out.println("LifeCycleClient.close");
beanClose();
}
}
@Bean에 initMethod와 DestroyMethod를 추가한다.
package hello.core.lifeCycle;
import hello.core.lifeCycle.LifeCycleClient;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
public class BeanLifeCycleTest {
@Test
public void lifeCycleTest(){
ConfigurableApplicationContext applicationContext = new AnnotationConfigApplicationContext(LifeCycleConfig.class);
LifeCycleClient client = applicationContext.getBean(LifeCycleClient.class);
applicationContext.close();
}
@Configuration
static class LifeCycleConfig{
@Bean(initMethod = "init", destroyMethod = "close")
public LifeCycleClient lifeCycleClient(){
LifeCycleClient networkClient = new LifeCycleClient();
networkClient.setPrint("hello!");
return networkClient;
}
}
}
그러면
이렇게 생명주기에 맞게 실행이 되는 것을 볼 수 있다.
@DestroyMethod에 추록기능이 있어 close, shutdown을 찾아가지만 그래도 지정해주도록 하자.
- @PostConstruct, @PreDestroy annotation
가장 많이 사용하는 방법이라고 한다, 하지만 외부 라이브러리에는 적용하지 못하기 때문에 외부 라이브러리에 사용하려면 위의 방법을 사용해야 한다고 한다.
예상했던대로
package hello.core.lifeCycle;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
public class LifeCycleClient{
private String print;
public LifeCycleClient() {
System.out.println("생성자 호출, url = " + print);
}
public void setPrint(String print) {
this.print = print;
}
//서비스 시작시 호출
public void beanStart() {
System.out.println("LifeCycleClient.beanStart, print = " + print);
}
public void call(String message) {
System.out.println("call: " + print + " message = " + message);
}
//서비스 종료시 호출
public void beanClose() {
System.out.println("LifeCycleClient.disConnect, print = " + print);
}
@PostConstruct
public void init(){
System.out.println("LifeCycleClient.init");
beanStart();
call("init!");
}
@PreDestroy
public void close(){
System.out.println("LifeCycleClient.close");
beanClose();
}
}
그냥 이렇게 @PostConstruct, @PreDestroy annotation을 달아주는 방법이다.