Spring

Spring Bean의 생명주기

Tommy__Kim 2023. 4. 21. 15:18

스프링 빈의 라이프 사이클은 다음과 같습니다다.

스프링 컨테이너 생성 => 스프링 빈 생성 => 의존관계 주입 =>

초기화 콜백 => 사용 => 소멸 전 콜백 => 스프링 종료


바로 예제와 함께 스프링 빈의 라이프 사이클을 알아보려고 합니다.

다음과 같은 Client 가 있다고 가정합니다.

[Client Code]

public class Client{
    private String clientName;

    public Client() {
        System.out.println("생성자 호출, clientName = " + clientName);
        init();
        call("Client 초기화 메세지");
    }

    public void setClientName(String clientName) {
        this.clientName = clientName;
    }

    // 시작 시 호출
    public void init() {
        System.out.println("init: " + clientName);
    }

    public void call(String message) {
        System.out.println("clientName: " + clientName + " message: " + message);
    }

    // 종료 시 호출
    public void close() {
        System.out.println("close: " + clientName);
    }
}

client에는 clientName이 있고 생성자에 초기화 하는 기능이 있습니다.

[Test Code]

public class BeanLifeCycleTest {
    @Test
    public void lifeCycle() {
        ConfigurableApplicationContext applicationContext = new AnnotationConfigApplicationContext(LifeCycleConfig.class);
        Client client = applicationContext.getBean(Client.class);
        applicationContext.close();

    }
    @Configuration
    static class LifeCycleConfig {

        @Bean
        public Client client() {
            Client client = new Client();
            client.setClientName("helloClient");
            return client;
        }
    }
}

[Test Result]

생성자 호출, clientName = null
init: null
clientName: null message: Client 초기화 메세지

당연하게도 Client에는 생성자만 있고 clientName을 변경할 부분이 없어 null로 뜨게 됩니다.

Spring의 Bean LifeCycle Callback

스프링 빈은 객체를 생성하고 의존관계 주입이 끝난 후에 데이터를 사용할 수 있는 준비가 완료됩니다.
그러므로 초기화 작업은 의존관계 주입이 모두 완료된 후에 호출해야 합니다.

스프링은 의존관계 주입이 완료되면 스프링 빈에게 콜백 메서드를 통해 초기화 시점을 알려주는 다양한 기능을 제공합니다.
추가적으로 스프링은 스프링 컨테이너가 종료되기 직전에소멸 콜백을 줍니다.

이 부분을 활용해 초기화 및 종료작업을 진행하면 됩니다.

스프링은 크게 3가지 방법으로 빈 생명주기 콜백을 지원하고 있습니다.

  • Interface(InitializingBean, DisposableBean)
  • 설정 정보에 초기화 메서드, 종료 메서드 지정
  • @PostConstruct, @PreDestroy 어노테이션

Interface (InitializingBean, Disposable Bean)

적용하려는 class에 InitializingBean, DisposableBean을 구현해 주면 됩니다.

[Client Code]

public class Client implements InitializingBean, DisposableBean{
    private String clientName;

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

    public void setClientName(String clientName) {
        this.clientName = clientName;
    }

    // 시작 시 호출
    public void init() {
        System.out.println("init: " + clientName);
    }

    public void call(String message) {
        System.out.println("clientName: " + clientName + " message: " + message);
    }

    // 종료 시 호출
    public void close() {
        System.out.println("close: " + clientName);
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        init();
        call("초기화 연결 메세지");
    }

    @Override
    public void destroy() throws Exception {
        close();
    }
}

[Test Code]

public class BeanLifeCycleTest {
    @Test
    public void lifeCycle() {
        ConfigurableApplicationContext applicationContext = new AnnotationConfigApplicationContext(LifeCycleConfig.class);
        Client client = applicationContext.getBean(Client.class);
        applicationContext.close();

    }
    @Configuration
    static class LifeCycleConfig {

        @Bean
        public Client client() {
            Client client = new Client();
            client.setClientName("helloClient");
            return client;
        }
    }
}

[Test Result]

생성자 호출, clientName = null
init: helloClient
clientName: helloClient message: 초기화 연결 메세지
15:17:46.532 [main] DEBUG org.springframework.context.annotation.AnnotationConfigApplicationContext - Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@60bd273d, started on Sat Mar 04 15:17:46 KST 2023
close: helloClient

test 결과를 확인하면 초기화 메서드가 적절하게 호출된 것을 알 수 있습니다다.
또한 Spring Container 종료가 호출되자 destroy 메서드가 실행된 것을 확인할 수 있습니다.

인터페이스를 구현하는 방식은 스프링 초창기에 나온 방식으로 다음과 같은 단점을 갖습니다.

  • 스프링 전용 인터페이스
    • 해당 코드가 스프링 인터페이스에 의존
  • 초기화, 소멸 메서드의 이름을 변경할 수 없다.
  • 일부 외부 라이브러리의 경우 적용 불가능

설정 정보에 초기화 메서드, 종료 메서드 지정

빈 등록 시에 @Bean(initMethod="{method이름}", destroyMethod="{Method이름}")을 넣어주면 됩니다.

[Client Code]

public class Client {
    private String clientName;

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

    public void setClientName(String clientName) {
        this.clientName = clientName;
    }

    // 시작 시 호출
    public void init() {
        System.out.println("init: " + clientName);
    }

    public void call(String message) {
        System.out.println("clientName: " + clientName + " message: " + message);
    }

    // 종료 시 호출
    public void close() {
        System.out.println("close: " + clientName);
    }

    public void initBean() {
        System.out.println("Client.initBean");
        init();
        call("초기화 연결 메세지");
    }

    public void closeBean() {
        System.out.println("Client.closeBean");
        close();
    }
}

앞선 코드에서 Interface를 제거해주고 Override메서드 대신 initBean(), closeBean() 이라는 메서드를 만들어 줍니다.

[Test Code]

public class BeanLifeCycleTest {
    @Test
    public void lifeCycle() {
        ConfigurableApplicationContext applicationContext = new AnnotationConfigApplicationContext(LifeCycleConfig.class);
        Client client = applicationContext.getBean(Client.class);
        applicationContext.close();

    }
    @Configuration
    static class LifeCycleConfig {


    @Bean(initMethod = "initBean", destroyMethod = "closeBean")
        public Client client() {
            Client client = new Client();
            client.setClientName("helloClient");
            return client;
        }
    }
}

Configuration에서 Bean 등록시에 위 코드와 같이 등록을 해주면 됩니다.

[Test Result]


생성자 호출, clientName = null
Client.initBean
init: helloClient
clientName: helloClient message: 초기화 연결 메세지
15:26:49.097 [main] DEBUG org.springframework.context.annotation.AnnotationConfigApplicationContext - Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@60bd273d, started on Sat Mar 04 15:26:48 KST 2023
Client.closeBean
close: helloClient

정상적으로 동작하는 것을 확인 할 수 있습니다.

설정 정보를 통한 초기화 메서드, 종료 메서드 지정 방식은 다음과 같은 특징을 갖습니다.

  • 메서드 이름을 자유롭게 줄 수 있다.
  • 스프링 코드에 의존하지 않는다.
  • 외부 라이브러리에도 적용이 가능하다.

@PostConstruct, @PreDestroy 어노테이션

초기화 하고 싶은 메서드에 @PostConstruce, 종료 하고 싶은 메서드에 @PreDestroy 를 붙여주면 됩니다.

[Client Code]

public class Client {
    private String clientName;

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

    public void setClientName(String clientName) {
        this.clientName = clientName;
    }

    // 시작 시 호출
    public void init() {
        System.out.println("init: " + clientName);
    }

    public void call(String message) {
        System.out.println("clientName: " + clientName + " message: " + message);
    }

    // 종료 시 호출
    public void close() {
        System.out.println("close: " + clientName);
    }
    @PostConstruct
    public void initBean() {
        System.out.println("Client.initBean");
        init();
        call("초기화 연결 메세지");
    }
    @PreDestroy
    public void closeBean() {
        System.out.println("Client.closeBean");
        close();
    }
}

[Test Code]

public class BeanLifeCycleTest {
    @Test
    public void lifeCycle() {
        ConfigurableApplicationContext applicationContext = new AnnotationConfigApplicationContext(LifeCycleConfig.class);
        Client client = applicationContext.getBean(Client.class);
        applicationContext.close();

    }
    @Configuration
    static class LifeCycleConfig {

        @Bean
        public Client client() {
            Client client = new Client();
            client.setClientName("helloClient");
            return client;
        }
    }
}

[Test Result]

생성자 호출, clientName = null
Client.initBean
init: helloClient
clientName: helloClient message: 초기화 연결 메세지
15:53:19.136 [main] DEBUG org.springframework.context.annotation.AnnotationConfigApplicationContext - Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@60bd273d, started on Sat Mar 04 15:53:19 KST 2023
Client.closeBean
close: helloClient

어노테이션만으로 초기화 종료 메서드가 잘 실행되는 것을 확인할 수 있습니다.

특징

  • 스프링에서 권장하는 방법(현재)
  • javax.annotation.PostConstruct, javax.annotation.PreDestroy 패키지
    • 스프링이 아닌 다른 컨테이너에서도 도악한다.
  • 다른 외부 라이브러리에서 적용이 불가능하다.
    • 외부 라이브러리를 초기화, 종료 해야한다면 @Bean(initMethod, destroyMethod) 를 사용하자

'Spring' 카테고리의 다른 글

Spring의 핵심 Concept는 무엇인가  (0) 2023.05.23
Enum Validation  (0) 2023.04.24
싱글톤에 대해서  (2) 2023.04.10
Dependency Injection (의존 관계 주입)  (0) 2023.04.10
Spring Rest Docs Tutorial  (0) 2023.04.06