빈(Bean)의 라이프사이클(Life-Cycle) 제어
개발을 진행하면서 애플리케이션을 구동할 때 Service 계층에서 Arcus 캐시로 접근해야 하는 상황이 발생하였다고 가정해보자.
Arcus 문서에는 서버 종료 시에 shutdown() 함수를 반드시 호출하라고 명시되어 있었다. 이러한 상황에서 어떻게 Bean의 소멸을 처리할 것인지 문제가 발생하였는데, 이를 위해 다음과 같은 가상의 CacheClient를 통해 Bean의 라이프사이클을 제어하는 방법에 대해 알아보고자 한다.
@RequiredArgsConstructor
public class CacheClient {
private final String url;
// 생성자 이후 호출되어야 함
public void connect() {
System.out.println("connect: " + url);
}
// 애플리케이션 종료 시 호출되어야 함
public void disconnect() {
System.out.println("close + " + url);
}
}
[ 1. InitializingBean, DisposableBean ]
Spring 초기에는 위의 2가지 인터페이스를 제공하여 빈의 라이프사이클을 관리할 수 있도록 하였다. InitializingBean과 DisposableBean은 각각 afterPropertiesSet() 메소드와 destroy() 메소드를 오버라이드 하도록 지원한다.
@RequiredArgsConstructor
public class CacheClient implements InitializingBean, DisposableBean {
private final String url;
@Override
public void afterPropertiesSet() throws Exception {
this.connect();
}
@Override
public void destroy() throws Exception {
this.disconnect();
}
// 생성자 이후 호출되어야 함
public void connect() {
System.out.println("connect: " + url);
}
// 애플리케이션 종료 시 호출되어야 함
public void disconnect() {
System.out.println("close + " + url);
}
}
하지만 최근에는 다음과 같은 이유로 이러한 방법을 사용하지 않는다.
- 자바 표준 인터페이스가 아닌 스프링 전용 인터페이스로써 스프링에 종속적이다.
- 초기화, 소멸 메소드의 이름을 변경할 수 없다.
- 캐시 등 직접 제어를 해주어야 하는 외부 라이브러리에 적용할 수 없다.
[ 2. PreDestroy, PostConstruct ]
Spring에서는 위와 같은 문제들 때문에 @PreDestroy와 @PostConstruct 라는 어노테이션의 사용을 권장하고 있다.
@RequiredArgsConstructor
public class CacheClient {
private final String url;
@PostConstruct
public void init() {
this.connect();
}
@PreDestroy
public void destroy() {
this.disconnect();
}
// 생성자 이후 호출되어야 함
public void connect() {
System.out.println("connect: " + url);
}
// 애플리케이션 종료 시 호출되어야 함
public void disconnect() {
System.out.println("close + " + url);
}
}
@PreDestroy와 @PostConstruct는 Spring의 어노테이션이 아닌 자바 표준 javax의 어노테이션이다. 그렇기 때문에 Spring이 아닌 다른 컨테이너에서도 동작 가능하며, 상당히 편리하게 이용 가능하다.
그렇기 때문에 가급적이면 두 어노테이션을 통해 처리할 것을 권장하고 있다. 하지만 만약 외부 라이브러리를 Bean으로 등록하는 경우라면 코드 수정이 불가능하여 다른 방법을 사용해야 한다.
[ 3. 빈의 초기화, 소멸 메소드 지정 ]
빈을 생성하기 위한 @Bean 어노테이션에 initMethod와 destroyMethod를 설정하여 초기화, 소멸 메소드를 지정할 수 있다. 이를 통해 위의 코드를 수정하면 다음과 같다.
@RequiredArgsConstructor
public class CacheClient {
private final String url;
public void init() {
this.connect();
}
public void destroy() {
this.disconnect();
}
// 생성자 이후 호출되어야 함
public void connect() {
System.out.println("connect: " + url);
}
// 애플리케이션 종료 시 호출되어야 함
public void disconnect() {
System.out.println("close + " + url);
}
}
@Configuration
public class CacheConfig {
@Bean(initMethod = "init", destroyMethod = "destroy")
public CacheClient cacheClient() {
return new CacheClient("http://localhost:1234");
}
}
이를 이용함으로써 메소드의 이름을 자유롭게 변경할 수 있으며, 코드에 접근할 수 없는 외부 라이브러리에도 초기화, 종료 메소드를 적용할 수 있게 되었다.
또한 destroyMethod에는 inferred라는 추론 기능이 추가되어 있다. 일반적으로 종료 메소드의 이름으로 close 또는 shutdown을 많이 이용하기 때문에, close 또는 shutdown 함수가 존재하면 이를 소멸자 메소드로 인식하여 호출하는 것이다. 이러한 추론 기능을 사용하고 싶지 않으면 destroyMethod="" 처럼 destroyMethod를 공백으로 지정하면 된다.
참고