빈 생명주기 콜백 (init, cleanup)
데이터베이스 커넥션 풀을 연결할 때. 혹은 소켓을 연결할 때. 객체 생성 이후 연결을 맺고, 객체가 사라지기 전에 연결을 끊는 작업이 필요하다. 스프링에서는 이런 초기화 작업, cleanup 작업을 쉽게 할 수 있도록 기능을 제공한다.
먼저 스프링 빈의 라이프사이클은 다음과 같다.
스프링 컨테이너 생성 -> 스프링 빈 생성 -> 의존관계 주입 -> 초기화 콜백 -> ... -> 소멸전 콜백 -> 스프링 종료
스프링에서는 초기화 콜백, 소멸전 콜백을 구현할 수 있는 세가지 방법을 제공한다.
spring 인터페이스를 활용하는 방법, @Bean 기능을 이용하는 방법, 또 다른 annotation을 활용하는 방법이다.
차례대로 알아보자.
1. 인터페이스 InitializingBean, DisposableBean
InitializeBean, DisposableBean을 implements 한 뒤, afterPropertiesSet() 과 disconnect()를 override하는 방법이다.
스프링 인터페이스에 의존적이고, 메소드 명을 바꿀 수 없으며 외부 라이브러리에 적용할 수 없다는 단점이 있다. 잘 쓰이지 않는 방법이다.
2. @Bean 기능
@Bean을 이용해 bean을 수동 등록하고, 이때 @Bean(initMethod = "init", destroyMethod = "close") 와 같이 initMethod와 destroyMethod 각각에 초기화 하는 함수와 cleanup 함수를 넣어 준다. 물론 초기화 함수와 cleanup 함수가 객체 내에 정의되어 있어야 한다.
public class NetworkClient {
private String url;
public NetworkClient() {
System.out.println("생성자 호출, url = " + url);
//connect();
//call("초기화 연결 메시지");
}
public void setUrl(String url) {
this.url = url;
}
//서비스 시작시 호출
public void connect() {
System.out.println("connect : "+ url);
}
public void call(String msg) {
System.out.println("call : " + url + " msg = " + msg);
}
//서비스 종료시 호출
public void disconnect() {
System.out.println("close " + url);
}
public void init(){
//의존관계 주입이 끝나면 호출
System.out.println("NetworkClient.afterPropertiesSet");
connect();
call("초기화 연결 메시지");
}
public void close(){
System.out.println("NetworkClient.destroy");
disconnect(); //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;
}
}
위와 같이 사용하면 된다.
추가적으로, destroyMethod를 명시하지 않으면 스프링에서 "shutdown", "close"라고 되어있는 메소드를 찾아서 자동으로 cleanup 함수로 호출한다. 만약 cleanup 함수를 호출하고 싶지 않다면 destroyMethod="" 라고 명시하자.
3. @PostConstruct, @PreDestory
어노테이션 이름을 보면 알 수 있듯이, 초기화 함수에 @PostConstruct를(생성자 이후 실행된다는 의미다), cleanup 함수에 @PreDestroy를 붙이면 된다. (매우 편리하다!)
public class NetworkClient {
private String url;
public NetworkClient() {
System.out.println("생성자 호출, url = " + url);
//connect();
//call("초기화 연결 메시지");
}
public void setUrl(String url) {
this.url = url;
}
//서비스 시작시 호출
public void connect() {
System.out.println("connect : "+ url);
}
public void call(String msg) {
System.out.println("call : " + url + " msg = " + msg);
}
//서비스 종료시 호출
public void disconnect() {
System.out.println("close " + url);
}
@PostConstruct
public void init(){
//의존관계 주입이 끝나면 호출
System.out.println("NetworkClient.afterPropertiesSet");
connect();
call("초기화 연결 메시지");
}
@PreDestroy
public void close(){
System.out.println("NetworkClient.destroy");
disconnect(); //close를 안전하게
}
}
현재 스프링에서 권장하는 방법이고, 가장 일반적으로 쓰이는 방식이다. bean 자동 등록(컴포넌트 스캔)과 잘 어울린다.
단점이 하나 있는데, 외부 라이브러리에 적용이 어렵다는 점이다. 외부 라이브러리 객체를 스프링 빈으로 등록해서 쓴다면, 외부 라이브러리 코드를 수정할 수 없기 때문에 이 방법으로 초기화, cleanup 함수를 지정할 수 없다. 이때는 위에서 이야기한 @Bean 기능을 이용하자. (외부 라이브러리에서 보통 초기화, cleanup 함수가 정의되어 있으므로 이를 initMethod와 destroyMethod로 지정하면 된다)
사진, 내용 출처 : 인프런, 김영한 강사님의 '스프링 핵심 원리 - 기본편' 강좌, 강의자료