Home 스프링 빈 생명주기 관리
Post
Cancel

스프링 빈 생명주기 관리

1. 글을 작성하게 된 계기


스프링을 사용하며 특정 시점에 작업을 처리 할 일이 있었는데, 이때까지 사용했던 방법과, 새로 알게 된 내용을 한 번에 정리하기 위해 글을 작성하게 되었습니다.

특정 시점은 스프링의 특정 빈이 초기화 되었을 때, 애플리케이션이 구동되었을 때와 같은 시점을 말합니다.







2. 라이프 사이클


The lifecycle of any object means when & how it is born, how it behaves throughout its life, and when & how it dies.





2-1. ApplicationRunner

ApplicationRunner를 구현 상속하면 스프링 애플리케이션이 구동된 직후, 즉, 모든 빈들이 초기화 된 후 특정 작업을 수행할 수 있습니다.

1
2
3
4
5
6
7
8
9
@Component(value = "startLog")
class StartLogConfig: ApplicationRunner {

    private val log = logger()

    override fun run(args: ApplicationArguments?) {
        log.info("Hello World.")
    }
}





만약 다른 클래스에서도 ApplicationRunner을 구현 상속하고 있다면 @DependsOn 으로 실행 순서를 제어할 수 있습니다.

1
2
3
@DependsOn("startLog")
@Component(value = "startLogV2")
class StartLogConfigV2 : ApplicationRunner







2-2. @PostConstruct

@PostConstruct를 사용하면 특정 빈이 생성된 후, 초기화 작업이 끝나고 IOC 컨테이너에 등록되면 이를 호출합니다. 이는 단일 빈의 생명주기 에 초점을 맞추기 때문에 ApplicationRunner와 함께 사용하면 @PostConstruct가 먼저 호출됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Component(value = "startLog")
class StartLogConfig: ApplicationRunner {

    private val log = logger()

    @PostConstruct
    fun initLogAfterBeanInit() {
        log.info("[1] Init.")
    }

    override fun run(args: ApplicationArguments?) {
        log.info("[2] Hello World.")
    }
}







한 클래스에서 여러개의 @PostConstruct를 사용할 수도 있는데, 이 경우 메서드 위치 순서 대로 메서드를 호출합니다. 즉, 위에서 부터 아래로 메서드가 실행됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Order(2)
@Component(value = "startLog")
class StartLogConfig{

    private val log = logger()

    @PostConstruct
    fun initLogAfterBeanInitV1() {
        log.info("[1-1] Init.")
    }

    @PostConstruct
    fun initLogAfterBeanInitV2() {
        log.info("[1-2] Init.")
    }

    @PostConstruct
    fun initLogAfterBeanInitV3() {
        log.info("[1-3] Init.")
    }

    ......

}
1
2
3
INFO 79248 --- [main] p.lifecycle.app.common.StartLogConfig    : [1-1] Init.
INFO 79248 --- [main] p.lifecycle.app.common.StartLogConfig    : [1-2] Init.
INFO 79248 --- [main] p.lifecycle.app.common.StartLogConfig    : [1-3] Init.







그러나 다른 빈에서도 @PostConstruct를 사용중이라면 메서드 단위로 실행 순서를 보장할 수 없습니다.

1
2
3
4
5
6
7
8
9
10
11
@Order(1)  // 순서 제어 불가
@Component
class OtherPostConstruct {

    private val log = logger()

    @PostConstruct
    fun otherInit() {
        log.info("[Other] Init")
    }
}







이 경우 마찬가지로 @DependsOn 을 사용해 어떤 빈이 먼저 등록될 지, 순서를 정해줘야 합니다. 즉, @PostConstruct는 동일 클래스 내 실행 순서를 보장** 할 수 있지만, 다른 클래스에서 이를 사용할 경우, 메서드 단위로 실행 순서를 보장할 수 없습니다.

1
2
3
@DependsOn(value = ["otherPostConstruct"])
@Component(value = "startLog")
class StartLogConfig







2-3. InitializingBean

InitializingBean을 구현상속하면 InitializingBean은 스프링 프레임워크에서 제공하는 인터페이스로, afterPropertiesSet() 메서드를 오버라이딩하여 빈의 모든 프로퍼티가 설정된 후에 커스텀 초기화 작업을 수행할 수 있습니다. 이 인터페이스를 사용하면 다음과 같은 특징이 있습니다:

1
2
3
4
5
6
7
8
9
@Component(value = "startLog")
class StartLogConfig: InitializingBean {

    private val log = logger()

    override fun afterPropertiesSet() {
        log.info("[2] afterPropertiesSet")
    }
}







InitializingBean 인터페이스 InitializingBean은 스프링 프레임워크에서 제공하는 인터페이스로, afterPropertiesSet() 메서드를 오버라이딩하여 빈의 모든 프로퍼티가 설정된 후에 커스텀 초기화 작업을 수행할 수 있습니다. 이 인터페이스를 사용하면 다음과 같은 특징이 있습니다:

프로그래매틱: 초기화 로직을 Java 코드 내에서 명시적으로 구현해야 합니다. 확장성: InitializingBean 인터페이스를 구현하는 것은 클래스의 계층구조 내에서 활용할 수 있으며, 초기화 로직을 상속받은 클래스에서 재사용할 수 있습니다. 스프링 종속성: 이 인터페이스는 스프링 프레임워크에 종속적이므로, 스프링 외의 다른 컨테이너에서는 사용할 수 없습니다. 실행 시점의 차이 @PostConstruct: 종속성 주입이 완료된 후 즉시 호출됩니다. InitializingBean: afterPropertiesSet() 메서드는 @PostConstruct 어노테이션이 지정된 메서드 이후에 호출됩니다. 즉, @PostConstruct는 afterPropertiesSet()보다 먼저 실행됩니다. 사용 시 고려 사항 @PostConstruct가 권장되는 방식입니다. 그 이유는 위에서 언급한 것처럼 간결하고 선언적이며 자바 EE 표준을 따르기 때문입니다. 그러나 InitializingBean 인터페이스를 사용해야 하는 특정 경우도 있을 수 있습니다. 예를 들어, 빈의 초기화 로직을 상속 계층을 통해 전파하고자 할 때나 빈 후처리기와의 상호 작용을 보다 세밀하게 제어해야 할 때 등입니다.

어떤 방식을 사용할지는 애플리케이션의 요구사항과 개발자의 선호도에 따라 결정됩니다. 일반적으로 스프링에서는 @PostConstruct를 사용하는 것을 더 선호합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Component(value = "startLog")
class StartLogConfig: BeanPostProcessor {

    private val log = logger()

    @PostConstruct
    fun initLogAfterBeanInit() {
        log.info("[1] Init.")
    }

    override fun afterPropertiesSet() {
        log.info("[2] HELLO")
    }

    override fun run(args: ApplicationArguments?) {
        log.info("[3] Hello World.")
        log.info("Env is:$env")
    }

    override fun destroy() {
        log.info("[4] GoodBye.")
    }
}







1
2
3
4
5
6
7
8
9
10
11
12
13
 interface BeanPostProcessor {
    @Nullable
    default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException
    {
        return bean
    }

    @Nullable
    default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException
    {
        return bean
    }
}







2-5. DisposableBean

DisposableBean을 구현 상속하면 스프링 애플리케이션이 종료되는 시점에 특정 작업을 수행할 수 있습니다. 즉, 애플리케이션을 종료할 때, destroy 메서드를 실행하는 것입니다.

1
2
3
4
5
6
7
8
9
@Component(value = "startLog")
class StartLogConfig: DisposableBean {

    private val log = logger()

    override fun destroy() {
        log.info("[4] GoodBye.")
    }
}







@Bean 초기화 메서드

1
2
3
4
5
6
7
8
9
10
11
12
class ExampleBean {

    private val log = logger()

    fun initExampleBean() {
        log.info("[ExampleBean] Init.")
    }

    fun destroyExampleBean() {
        log.info("[ExampleBean] Destroy.")
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Component
@Profile("!test")
class ExampleConfig {

    private val log = logger()

    @Bean(
        initMethod = "initExampleBean",
        destroyMethod = "destroyExampleBean"
    )
    fun exampleBean(): ExampleBean {
        log.info("[Profile] Init.")
        return ExampleBean()
    }
}



















3. 정리


스프링 부트 3.2 버전 이후, 잘못된 URL로 요청이 왔을 때, 어떻게 커스텀한 에러 응답을 내려줄 수 있는지 살펴보았습니다. 방법 자체는 간단한데요, 추가로 @EnableWebMvc와 DispatcherServlet에 대해 학습해 보실 것을 권장해 드립니다.


This post is licensed under CC BY 4.0 by the author.

Controller가 호출되기 까지 어떤 과정을 거칠까?

Exception