스프링 빈 생명 주기 콜백(Spring Bean LifeCycle Callback) 알아보기
이번 포스팅에서는 지금까지 배운 Spring Bean의 생명 주기(LifeCycle)에 대해 자세히 알아보는 시간을 가지도록 하겠습니다.
Spring Bean LifeCycle Callback이란?
스프링 빈 생명 주기 콜백(Spring Bean LifeCycle Callback)이 무엇일까요? 이름 그대로 유추해보면 Spring Bean의 생성과 소멸 그 사이 생명 주기에 대한 내용임을 알 수 있고 그때 발생하는 콜백 메서드를 가리키는 말 같습니다.
네 맞습니다. 간단히 정리해보면, 스프링 Bean이 생성되거나 소멸되기 직전에 Spring이 Bean 안에 있는 메서드를 호출해줄 수 있는 기능입니다. 예를 들어, Spring Bean이 생성되고 나서 초기화하는 용도로 호출해줄 수도 있고 혹은 Bean이 사라지기 직전에 뭔가 안전하게 종료하기 위한 메서드를 호출할 수도 있습니다.
조금 더 실무적인 예를 들어보면, 데이터베이스 커넥션 풀을 관리할 때 사용할 수도 있습니다. 데이터베이스를 사용하기 위해 애플리케이션 서버가 올라올 때 데이터 베이스랑 연결을 미리 맺어놓게 됩니다. 왜냐하면, TCP/IP handshaking 등을 할때 시간이 오래 걸리기 때문에 미리 연결시켜놓지 않으면 클라이언트에게 서비스를 제공하는데 지연 시간이 발생할 수 있기 때문입니다.
혹은 네트워크 단에서 소켓을 미리 열어두는 예시를 들 수도 있을 것 같습니다. 미리 소켓을 열어놔야 클라이언트에게 빠른 응답을 줄 수 있게 됩니다. 그 외에도 애플리케이션이 종료되기 직전에 안전하게 종료하기 위해 미리 열어두었던 소켓들을 먼저 종료시킨 뒤 애플리케이션이 종료되도록 할 수도 있습니다. 이런 상황들에서 유용하게 사용할 수 있는 것이 바로 콜백 메서드입니다.
Spring Bean의 LifeCycle에 대해
그렇다면 지금부터 Spring Bean의 LifeCycle에 대해 먼저 이해해보겠습니다. Spring Bean은 Spring Bean 객체 생성을 먼저 한 뒤, 그 이후 의존 관계 주입이 일어나게 됩니다.
즉, Spring Bean 객체 생성 시점에 생성자가 호출되고 그 이후 의존 관계가 주입된다는 말입니다. 비로소 의존 관계 주입까지 모두 끝나야 이제 온전히 Spring Bean을 사용할 수 있는 준비 상태가 갖춰지게 됩니다.
다시 말해, 생성자 호출 시점 이후에 의존 관계가 주입되기 전에 이 Spring Bean 객체를 사용해버렸다가는 예기치 못한 동작이 발생할 수 있습니다. 따라서 온전히 의존 관계 주입까지 모두 마친 뒤에 사용하기 위해서는 Spring에서 이제 의존 관계 주입까지 모두 끝났다라는 콜백 신호를 받아야 합니다.
이를 위해 Spring에서는 Spring Bean에 의존 관계 주입까지 모두 마친 뒤에 콜백 메서드를 통해 적절한 초기화 시점을 알려주는 다양한 기능을 제공하고 있습니다. 이와 같은 맥락으로, Spring은 Spring Container가 종료되기 직전에 소멸에 대한 콜백을 줍니다. 이를 통해 안전하게 종료 작업을 진행할 수 있습니다. 이 모든 Spring Bean의 LifeCycle을 정리해보면 아래와 같습니다.
Spring Bean LifeCycle
스프링 컨테이너 생성 -> 스프링 빈 생성(constructor injection) -> 의존 관계 주입(setter/field injection) -> 초기화 콜백 -> 사용 -> 소멸 전 콜백 -> 스프링 종료
Spring에서 제공하는 Spring Bean LifeCycle Callback 3가지
지금부터는 Spring에서 제공하는 Spring Bean LifeCycle Callback 3가지에 대해 알아보겠습니다. 스프링에서는 다음과 같이 3가지 Callback을 제공합니다.
- InitializingBean, DisposableBean 인터페이스
- 설정 정보에서 초기화 혹은 종료 메서드 지정
- @PostConstruct, @PreDestroy 애너테이션 지정
첫 번째 방법은 Spring에서 제공해주는 InitializingBean이나 DisposableBean 인터페이스를 상속받아서 Overriding된 메서드를 사용하는 것입니다. 이 방법은 굉장히 스프링 초기에 나온 방법이고 스프링 전용 인터페이스에 의존해야하고 초기화나 소멸 메서드의 이름을 변경할 수 없다는 단점 때문에 지금은 잘 쓰이지 않습니다.
두 번째 방법은 Bean을 등록하는 시점에 초기화 혹은 종료 메서드를 지정해주는 방법입니다. 간단하게 @Bean(initMethod = “init”, destroyMethod = “close”)처럼 초기화 혹은 소멸 메서드 이름을 지정해주면 됩니다. 이 방법의 장점은 메서드 이름을 자유롭게 지정해줄 수 있다는 점이 있고 첫 번째 방법과 다르게 스프링 코드에 의존하지도 않아서 좋습니다.
하지만 사실 두 번째 방법이 좋은 이유는 destroyMethod의 default 값이 (inferred)라는 점입니다. infer은 추론하다라는 뜻인데 이름 그대로 추론하여 close, shutdown 등의 이름의 메서드를 destroy 시점에 자동으로 호출해줍니다. 이게 유용한 이유는 외부 라이브러리를 사용할 때 대부분 종료 메서드를 close나 shutdown이라는 이름으로 만들어 놓게 되는데 이걸 자동으로 지정하지 않아도 호출해주는 셈입니다. 만약 이런 추론 기능을 사용하기 싫다면 destroyMethod = ““처럼 공백을 지정해주면 됩니다.
마지막 방법은 @PostConstruct, @PreDestroy 애너테이션을 지정하는 방법입니다. 결론부터 말씀드리면 지금까지 설명 드린 방법 말고 이 애너테이션 지정 방법을 쓰시는 게 좋습니다.
왜냐하면 이는 최신 스프링에서 가장 권장하는 방법이기도 하고 스프링에 종속적인 기술이 아니라 JSR-250이라는 자바 표준에서 지원하는 기능이기 때문입니다. 따라서 스프링이 아닌 다른 컨테이너에서도 잘 동작합니다.
하지만, 이 방법의 유일한 단점이 있는데 바로 내가 직접 코드를 고쳐 위 애너테이션들을 원하는 메서드에 붙여줘야되기 때문에 외부 라이브러리에는 적용할 수 없다는 점입니다. 당연하게도 외부 라이브러리의 구현 코드를 내가 직접 수정할 수 없기 때문입니다. 그러므로 이때는 @Bean 애너테이션의 initMethod와 destroyMethod 옵션을 사용하는 것을 권장합니다.
마무리
이로써 지금까지 Spring Bean LifeCycle 전반적인 흐름에 대해 먼저 알아봤고 그 이후 콜백 메서드의 정의와 이 메서드들이 왜 필요한지, 어떻게 적용할 수 있는지 모든 방법에 대해 알아봤습니다.
객체의 생성부터 소멸 시점까지 잘 관리하기 위해 반드시 필요한 기능이라고 생각되며 조만간 이를 직접 활용해보는 기회가 있었으면 좋겠습니다.
References
- 인프런 내 김영한 강사님의 스프링 핵심 원리 - 기본편