본문 바로가기

Spring

스프링 빈 생명주기 콜백

데이터베이스 커넥션 풀이나, 네트워크 소켓처럼 애플리케이션의 시작 시점에 필요한 연결을 미리 해두고, 애플리케이션 종료 시점에 연결을 모두 종료하는 작업을 진행하려면, 객체의 초기화와 종료 작업이 필요하다.

이번에는 스프링을 통해 이러한 초기화 작업과 종료 작업을 어떻게 진행하는지 알아보자.

 

스프링 빈은 다음과 같은 생명주기를 가진다.

빈의 생명주기
스프링 컨테이너 생성 -> 스프링 빈 생성 -> 의존관계 주입 -> 초기화 콜백 -> 사용 -> 소멸전 콜백 -> 스프링 컨테이너 종료

 

스프링은 3가지 방법으로 빈 생명주기 콜백을 지원한다.

1. 인터페이스 InitializingBean, DisposableBean

첫 번째는 InitializingBean 인터페이스와 DisposalBean 인터페이스를 상속받아 구현하는 것이다.

public class NetworkClient implements InitializingBean, DisposableBean {
    private String url;

    public NetworkClient() {
         System.out.println("생성자 호출, url = " + url); 
    }

    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);
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("NetworkClient.afterPropertiesSet");
        connect();
        call("초기화 연결 메시지");
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("NetworkClient.destroy");
        disConnect();
    }
}

 

public class BeanLifeCycleTest {

    @Test
    void lifeCycleTest(){
       ConfigurableApplicationContext ac = new AnnotationConfigApplicationContext(LifeCycleConfig.class);
       NetworkClient client = ac.getBean(NetworkClient.class);
       ac.close();
    }

    @Configuration
    static class LifeCycleConfig{

        @Bean
        public NetworkClient networkClient(){
            NetworkClient networkClient = new NetworkClient();
            networkClient.setUrl("http://hello-spring.dev");
            return networkClient;
        }
    }
}

 

출력 결과를 보면 초기화 메서드가 주입 완료 후에 적절하게 호출된 것을 확인할 수 있다.

또한, 스프링 컨테이너의 종료가 호출되자 소멸 메서드가 호출된 것도 확인할 수 있다.

2. 빈 등록 초기화, 소멸 메서드 지정

설정 정보에 @Bean(initMethod="init", destoryMethod="close") 처럼 초기화, 소멸 메서드를 지정할 수 있다.

public class NetworkClient {

    private String url;

    public NetworkClient() {
        System.out.println("생성자 호출, url = " + url); 
    }

    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);
    }

    public void init() { 
        System.out.println("NetworkClient.init"); 
        connect();
        call("초기화 연결 메시지");
    }

    public void close() {
        System.out.println("NetworkClient.close");
        disConnect();
    }
}

 

설정 정보에 초기화, 소멸 메서드 지정

public class BeanLifeCycleTest {

    @Test
    void lifeCycleTest(){
       ConfigurableApplicationContext ac = new AnnotationConfigApplicationContext(LifeCycleConfig.class);
       NetworkClient client = ac.getBean(NetworkClient.class);
       ac.close();
    }
    
    @Configuration
    static class LifeCycleConfig {

        @Bean(initMethod = "init", destroyMethod = "close")
        public NetworkClient networkClient() {
            NetworkClient networkClient = new NetworkClient();
            networkClient.setUrl("http://hello-spring.dev");
            return networkClient;
        } 
    }
}

 

다음과 같이 초기화, 소멸 메서드가 잘 호출되는 것을 확인할 수 있다.

인터페이스 방식에 비해 메서드 이름을 자유롭게 줄 수 있고, 스프링 코드에 의존하지 않아도 된다.

또한, 코드가 아니라 설정 정보를 사용하기 때문에 코드를 고칠 수 없는 외부 라이브러리에도 초기화, 종료 메서드를 적용할 수 있다.

3.  어노테이션 @PostConstruct, @PreDestroy

마지막은 어노테이션을 이용하는 것이다.

초기화 메서드를 정의하고 이에 @PostConstruct을, 소멸 메서드를 정의하고 @ProDestroy를 적용하면 된다.

public class NetworkClient {

    private String url;

    public NetworkClient() {
        System.out.println("생성자 호출, url = " + url); 
    }

    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);
    }

    @PostConstruct
    public void init() {
        System.out.println("NetworkClient.init"); 
        connect();
        call("초기화 연결 메시지");
    }

    @PreDestroy
    public void close() {
        System.out.println("NetworkClient.close");
        disConnect();
    }
}

 

public class BeanLifeCycleTest {

    @Test
    void lifeCycleTest(){
       ConfigurableApplicationContext ac = new AnnotationConfigApplicationContext(LifeCycleConfig.class);
       NetworkClient client = ac.getBean(NetworkClient.class);
       ac.close();
    }

    @Configuration
    static class LifeCycleConfig {

        @Bean
        public NetworkClient networkClient() {
            NetworkClient networkClient = new NetworkClient();
            networkClient.setUrl("http://hello-spring.dev");
            return networkClient;
        }
    }
}

 

다음과 같이 초기화, 소멸 메서드가 잘 호출되는 것을 확인할 수 있다.

 

마무리

이렇게 3가지의 생명주기 콜백 방법에 대해서 알아보았다.

주로 어노테이션을 이용한 방법을 자주 사용하며, 최신 스프링에서 가장 권장하는 방법이다.

하지만, 코드를 수정할 수 없는 외부 라이브러리를 초기화, 소멸해야한다면 추가적으로 메서드를 정의하고 이를 설정 정보에 적용하는 메서드 방법을 사용해야한다.

'Spring' 카테고리의 다른 글

[JPA] N+1 문제  (0) 2023.01.08
@RequestMapping  (0) 2022.09.12
Spring MVC  (0) 2022.09.08
스프링 컨테이너, 스프링 빈, 싱글톤  (0) 2022.09.04
스프링의 핵심  (1) 2022.09.04