-
[Spring Boot] Bean 생명주기 (Life cycle) 콜백Back-End/Spring Boot 2022. 10. 11. 00:02
📑 스프링 컨테이너 생명주기
스프링컨테이너에 대표적인, 아주 기본적인 생명주기를 살보자면 아래와 같다.
1. ApplicationContext를 이용해 객체를 생성하고 스프링 컨테이너를 초기화 한다 .
2. getBean()과 같은 메서드를 이용해서 컨테이너에 있는 빈 객체를 사용한다.
3. close() 메서드를 이용해 컨테이너를 종료한다.📌 컨테이너 초기화 작업 : 빈 객체 생성, 초기화 및 의존 객체를 주입
📌 컨테이너 종료 : 빈 객체를 소멸하는 작업
빈 객체의 생명 주기는 객체 생성 -> 의존 설정 -> 초기화 -> 소멸 과정이다.
1. 스프링 컨테이너를 초기화 할 때, 가장 먼저 빈 객체를 생성한다.
2. 빈 객체 생성 후, 의존을 설정한다. 즉) 의존성 주입을 한다.
3. 모든 의존 설정이 완료되면, 빈 객체 초기화를 한다.
( 빈 객체 초기화 위해, 빈 객체의 지정한 메서드를 호출한다.)
4. 초기화 콜백 -> 사용 -> 소멸 전 콜백
5. 스프링 컨테이너를 종료하면, 스프링 컨테이너는 빈 객체를 소멸시킨다.스프링 빈은 객체를 생성하고, 의존관계 주입이 다 끝난 다음에야 필요한 데이터를 사용할 수 있는 준비가 완료된다. (참고로 의존관계 주입은 ApplicationContext를 생성할 때 발생된다.) 따라서 초기화 작업은 의존관계 주입이 모두 완료되고 난 후에 호출해야한다. 그렇다면 어떻게 의존관계 주입이 모두 완료된 시점을 알 수 있을까?
스프링은 의존관계 주입이 완료되면 스프링 빈에게 콜백베서드를 통해 초기화 시점을 알려주는 다양한 기능을 제공해준다. 또한) 스프링은 스프링 컨테이너가 종료되기 직전에 소멸 콜백을 준다. 따라서 안전하게 종료 작업을 할 수 있다.
📑 @PostConstruct, @PreDestory
스프링은 크게 3가지 방법으로 빈 생명주기 콜백을 지원한다.
1. 인터페이스 ( InitializingBean, DisposableBean)
2. 설정 정보에 초기화 메서드, 종료 메서드 지정
3. @PostConstruct, @PreDestroy 애노테이션 지원위의 3가지 방법중에 3번을 대표적으로 사용하므로 3번에 대한 설명을 예제로 알아보려 한다.
다음과 같은 url을 저장하는 클래스가 있을 때 생성자를 만들어주고 set메서드로 url을 매개변수로 받는 메서드를 열어두었다. 서버와 연결했을때를 가정으로 connect라는 메서드와 서버 종료시 메서드인 disconnection 또한 생성해두었다.
package hello.core.lifecycle; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; public class NetworkClient { private String url; public NetworkClient(){ System.out.println("생성자 호출, url = " + url); // connect(); // call("초기화 연결 메시지"); } public void setUrl(String url){ this.url = url; } //서비서 시작시 호출 public void connect(){ System.out.println("connect: " +url); } public void call(String message){ System.out.println("call " + url + " message = " + message); } //서비스 종료시 호출 public void disconnect(){ System.out.println("close" + url); } //의존관계 주입이 끝나면 호출 //의존관계 주입은 ApplicationContext를 생성할 때 실행 @PostConstruct public void init() { System.out.println("NetworkClient.init"); connect(); call("초기화 연결 메시지"); } @PreDestroy public void close() { System.out.println("NetworkClient.close"); disconnect(); } }
여기서 잠깐! 생성자로 간편하게 url을 넘겨받음 되는 거 아냐? public NetworkClinet()가 아닌 public NetworkClienct(String url)로 생서자 호출할 때 초기화까지 해버리면 편하지 않나? 의문이 들 수 있다.
📌 객체의 생성과 초기화를 분리하자
생성자는 필수 정보(파라미터)를 받고, 메모리를 할당해서 객체를 생성하는 책임을 가진다. 반면 초기화는 이렇게 생성된 값들을 활용해서 외부 커넥션을 연결하는 등 무거운 동작을 수행한다. 따라서 생성자 안에서 무거운 초기화 작업을 함께 하는 것 보다 객체를 생성하는 부분과 초기화 하는 부분을 명확하게 나누는 것이 유지보수 관점에서 좋다.!! 물론 초기화 작업이 내부의 값들만 약간 변경한다면 무관하지만~~
위의 client 클래스를 가지고 테스트를 진행해보자.
package hello.core.lifecycle; 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(){ //ApplicationContext는 close 제공 안함. ConfigurableApplicationContext ac = new AnnotationConfigApplicationContext(LifeCycleConfig.class); NetworkClient client = ac.getBean(NetworkClient.class); ac.close(); } @Configuration static class LifeCycleConfig{ // @Bean(initMethod = "init", destroyMethod = "close") //스프링 컨테이너 실행 -> 스프링 빈으로 등록 -> setUrl() 호출 -> 의존관계 주입 -> 초기화 @Bean public NetworkClient networkClient(){ NetworkClient client = new NetworkClient(); client.setUrl("http://hello-spring.dev"); return client; } } }
생성자 호출, url = null
NetworkClient.init
connect: http://hello-spring.dev
call http://hello-spring.dev message = 초기화 연결 메시지 16:21:27.002
[main] DEBUG org.springframework.context.annotation.AnnotationConfigApplicationContext - Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@73e9cf3
NetworkClient.close closehttp://hello-spring.dev1. 테스트 코드에서 ac라는 스프링 컨테이너를 생성한다.
2. LifeCycleConfig.class에 있는 @Bean이 붙은 메서드들을 스프링 컨테이너에 빈으로 저장한다. networkClient라는 이름을 가진 빈이 생성된다. (2번까지 모두 빈 생성 과정)
2-1. networkClient라는 빈에서 NetworkClient 타입의 객체가 생성된다.
2-2. 객체가 생성됨과 동시에 NetworkClient 생성자를 호출해 생성자 호출, url = null이 출력된다.
2-3 . NetworkClient 타입인 networkClient에 setUrl을 통해 url에 값을 저장한다.
3. 이렇게 생성된 객체가 @Bean 메서드에 반환되면 이후에 스프링 빈으로 등록되게 된다. 이렇게 스프링 빈으로 등록되면 다르 곳에서 의존관계 주입이 될 수 있다.
4. 스프링 컨테이너 실행 -> 스프링 빈으로 등록 (setUrl() 호출) -> 의존관계 주입 -> 초기화(@PostConstruct)
반응형'Back-End > Spring Boot' 카테고리의 다른 글
[Spring] HTTP 응답 메시지 만들기 ( Model, @ResponseBody, @ResponseEntity) (0) 2022.10.19 [Spring Boot] HTTP 요청 파라미터, 메시지 ( @RequestParam, @ModelAttribute, @RequestBody, HttpEntity) (0) 2022.10.14 [Spring Boot] 빈 스코프 범위 (싱글톤, 프로토타입, 웹) (0) 2022.10.06 [Spring Boot] 생성자 주입이란? + @Autowired 옵션 처리 (1) 2022.10.04 [Spring Boot] 컴포넌트 스캔과 의존관계 자동 주입 (1) 2022.10.03