개발 이야기/Spring

[Spring] 빈의 생명 주기

제이온 (J.ON) 2021. 6. 17.

안녕하세요? 제이온입니다.

 

오늘은 빈의 생명 주기에 대해서 알아 보겠습니다.

 

 

빈의 생명 주기

지금까지 빈은 등록이 되고난 이후에 의존 관계가 주입된다는 사실을 배웠습니다. 하지만 빈은 여기서 끝나지 않습니다. 일단 전체적인 스프링 빈의 생명 주기를 말씀드리겠습니다.

 

 

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

 

 

스프링 컨테이너가 생성되고, 스프링 빈이 등록되어 의존 관계가 주입되는 것까지는 알겠습니다. 그런데, 초기화 콜백과 소멸전 콜백은 들어본 적도 사용해 본적도 없습니다. 단지 우리는 의존 관계 주입이후 해당 빈을 사용하기만 했죠.

 

지금부터 초기화 콜백과 소멸전 콜백을 이용하는 방법을 다뤄보겠습니다.

 

 

InitializingBean, DisposableBean 인터페이스 사용

첫 번째는 위 두 가지 인터페이스를 사용하여 직접 콜백 함수를 정의해 주는 것입니다.

 

 

@Component
public class OrderServiceImpl implements OrderService, InitializingBean, DisposableBean {

    private final DiscountPolicy discountPolicy;

    public OrderServiceImpl(DiscountPolicy discountPolicy) {
        this.discountPolicy = discountPolicy;
    }

    @Override
    public Order createOrder(int age, String itemName, int itemPrice) {
        int discountPrice = discountPolicy.discount(age, itemPrice);
        return new Order(itemName, itemPrice, discountPrice);
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("초기화 콜백입니다.");
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("소멸전 콜백입니다.");
    }
}

 

 

위와 같이 초기화 콜백과 소멸전 콜백을 재정의합니다.

 

 

public class Main {

    public static void main(String[] args) {
        final ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        final OrderService orderService = context.getBean(OrderService.class);
        final Order order = orderService.createOrder(15, "연필", 3000);
        System.out.println(order.getDiscountPrice());
        context.close();
    }
}

// 실행 결과
21:43:28.604 [main] DEBUG org.springframework.context.annotation.AnnotationConfigApplicationContext - Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@29b5cd00
21:43:28.670 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalConfigurationAnnotationProcessor'
21:43:28.957 [main] DEBUG org.springframework.context.annotation.ClassPathBeanDefinitionScanner - Identified candidate component class: file [C:\Users\hikar\Desktop\pjy\programming\ps\out\production\classes\blog\discount\RateDiscountPolicy.class]
21:43:28.964 [main] DEBUG org.springframework.context.annotation.ClassPathBeanDefinitionScanner - Identified candidate component class: file [C:\Users\hikar\Desktop\pjy\programming\ps\out\production\classes\blog\order\OrderServiceImpl.class]
21:43:29.485 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerProcessor'
21:43:29.497 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerFactory'
21:43:29.504 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalAutowiredAnnotationProcessor'
21:43:29.510 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalCommonAnnotationProcessor'
21:43:29.539 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'appConfig'
21:43:29.571 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'rateDiscountPolicy'
21:43:29.572 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'orderServiceImpl'
21:43:29.631 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Autowiring by type from bean name 'orderServiceImpl' via constructor to bean named 'rateDiscountPolicy'
초기화 콜백입니다.
300
21:43:29.743 [main] DEBUG org.springframework.context.annotation.AnnotationConfigApplicationContext - Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@29b5cd00, started on Thu Jun 17 21:43:28 KST 2021
소멸전 콜백입니다.

 

 

사용자 입장에서는 ApplicationContext대신 ConfigurableApplicationContext를 사용해야 합니다. 왜냐하면 ApplicationContext에는 close()가 없기 때문이죠. 이를 통해 기존의 ApplicationContext는 소멸전 콜백을 부르지 않고 바로 프로그램이 종료됨을 알 수 있습니다.

 

그리고 실행 결과를 보면 알 수 있듯이, 빈이 등록되고 Autowiring으로 의존 관계 자동 주입 이후, 초기화 콜백, 사용, 소멸전 콜백이 불립니다.

 

하지만 위 방법은 초기화와 소멸 메소드의 이름을 변경할 수 없고 외부 라이브러리에 적용할 수 없다는 단점이 있습니다.

 

 

설정 파일에 빈 등록 초기화, 소멸 메소드 지정

위 방법 대신 설정 파일에 빈 등록 초기화 및 소멸 메소드를 지정해 줄 수 있습니다. 다만, 컴포넌트 스캔 어노테이션이 붙은 설정 파일에서는 불가하고, 수동 빈 등록일 때만 가능합니다.

 

 

@Configuration
public class AppConfig {

    @Bean(initMethod = "init", destroyMethod = "close")
    public OrderServiceImpl orderService() {
        return new OrderServiceImpl(discountPolicy());
    }

    @Bean
    public FixDiscountPolicy discountPolicy() {
        return new FixDiscountPolicy();
    }
}

 

 

기존의 AppConfig를 위와 같이 변경합니다. 다만, OrderService가 아닌 OrderServiceImpl에 초기화 및 소멸 전 콜백 메소드가 있는 것이므로 반환을 OrderServiceImpl로 해야 합니다.

 

 

public class Main {

    public static void main(String[] args) {
        final ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        final OrderService orderService = context.getBean(OrderService.class);
        final Order order = orderService.createOrder(15, "연필", 3000);
        System.out.println(order.getDiscountPrice());
        context.close();
    }
}

// 실행 결과
21:54:00.403 [main] DEBUG org.springframework.context.annotation.AnnotationConfigApplicationContext - Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@32eebfca
21:54:00.458 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalConfigurationAnnotationProcessor'
21:54:00.826 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerProcessor'
21:54:00.832 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerFactory'
21:54:00.836 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalAutowiredAnnotationProcessor'
21:54:00.842 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalCommonAnnotationProcessor'
21:54:00.865 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'appConfig'
21:54:00.875 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'orderService'
21:54:00.913 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'discountPolicy'
초기화 콜백입니다.
300
21:54:00.997 [main] DEBUG org.springframework.context.annotation.AnnotationConfigApplicationContext - Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@32eebfca, started on Thu Jun 17 21:54:00 KST 2021
소멸전 콜백입니다.

 

 

동일한 결과를 얻어오는 것을 확인하실 수 있습니다. 해당 방법은 수동 빈 등록일 때만 사용이 가능하지만, 우리가 건드릴 수 없는 외부 라이브러리에 대해서도 콜백 메소드를 지정할 수 있다는 장점이 있습니다.

 

 

@PostConstruct, @PreDestroy 어노테이션 사용

하지만 우리는 컴포넌트 스캔을 사용하고 싶습니다. 이럴 때에는 불필요하게 인터페이스를 상속받을 필요 없이 두 가지 어노테이션을 사용하면 됩니다.

 

 

@Component
public class OrderServiceImpl implements OrderService {

    private final DiscountPolicy discountPolicy;

    public OrderServiceImpl(DiscountPolicy discountPolicy) {
        this.discountPolicy = discountPolicy;
    }

    @Override
    public Order createOrder(int age, String itemName, int itemPrice) {
        int discountPrice = discountPolicy.discount(age, itemPrice);
        return new Order(itemName, itemPrice, discountPrice);
    }
    
    @PostConstruct
    public void init() {
        System.out.println("초기화 콜백입니다.");
    }
    
    @PreDestroy
    public void close() {
        System.out.println("소멸전 콜백입니다.");
    }
}

 

 

초기화 콜백에는 @PostConstruct, 소멸전 콜백에는 @PreDestroy를 사용하는 것이죠.

 

 

public class Main {

    public static void main(String[] args) {
        final ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        final OrderService orderService = context.getBean(OrderService.class);
        final Order order = orderService.createOrder(15, "연필", 3000);
        System.out.println(order.getDiscountPrice());
        context.close();
    }
}

// 실행 결과
21:57:14.775 [main] DEBUG org.springframework.context.annotation.AnnotationConfigApplicationContext - Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@4e718207
21:57:14.842 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalConfigurationAnnotationProcessor'
21:57:15.080 [main] DEBUG org.springframework.context.annotation.ClassPathBeanDefinitionScanner - Identified candidate component class: file [C:\Users\hikar\Desktop\pjy\programming\ps\out\production\classes\blog\discount\RateDiscountPolicy.class]
21:57:15.091 [main] DEBUG org.springframework.context.annotation.ClassPathBeanDefinitionScanner - Identified candidate component class: file [C:\Users\hikar\Desktop\pjy\programming\ps\out\production\classes\blog\order\OrderServiceImpl.class]
21:57:15.327 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerProcessor'
21:57:15.336 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerFactory'
21:57:15.341 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalAutowiredAnnotationProcessor'
21:57:15.347 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalCommonAnnotationProcessor'
21:57:15.374 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'appConfig'
21:57:15.390 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'rateDiscountPolicy'
21:57:15.392 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'orderServiceImpl'
21:57:15.444 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Autowiring by type from bean name 'orderServiceImpl' via constructor to bean named 'rateDiscountPolicy'
초기화 콜백입니다.
300
21:57:15.510 [main] DEBUG org.springframework.context.annotation.AnnotationConfigApplicationContext - Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@4e718207, started on Thu Jun 17 21:57:14 KST 2021
소멸전 콜백입니다.

 

 

짜잔 동일하게 결과가 나오는 것을 알 수 있습니다.

 

 

정리

지금까지 빈의 생명 주기 및 콜백 메소드를 알아 보았습니다. 빈과 관련된 이야기는 1차적으로 이번 포스팅에서 마치도록 하고, 다음 포스팅에는 스프링 MVC쪽으로 넘어 가려고 합니다.

 

 

출처

 

스프링 핵심 원리 - 기본편 - 인프런 | 강의

스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다., 스프링 핵심 원리를 이해하고, 성장하는 개발자가 되어보세요! 📣 확인해주

www.inflearn.com

 

추천 글