Spring - 스프링 컨테이너 코드로 파악하기 4) 초기화, 소멸
상당 부분이 직접 코드를 분석하여 정리한 내용이라 틀린 부분이 있을 수도 있습니다! 잘못된 부분은 알려주시면 바로 수정하겠습니다.🙇♂️
1. 초기화, 소멸 개념
빈의 생성 후 초기화와 소멸 전 콜백은 다양한 상황에서 활용할 수 있다 예를 들어 빈이 외부 시스템이나 리소스를 사용할 때 빈을 생성할 때 연결하는 코드를 작성하고 소멸 전에 리소스를 닫는 작업 등을 명시할 수 있다. 초기화와 소멸 전 콜백은 빈의 라이프 사이클 동안 딱 한 번 수행 됨이 보장된다.
빈의 초기화와 소멸은 크게 세 가지 방식이 존재한다.
1) 인터페이스 활용
1
2
3
4
5
6
7
public interface initializingBean {
void afterPropertiesSet() throws Exception;
}
public interface DisposableBean {
void destroy() throws Exception;
}
빈이 initializingBean 인터페이스를 구현하고 afterPropertiesSet을 정의함으로써 초기화를 구현할 수 있다. 소멸은 Disposablebean 인터페이스를 구현하고 destroy를 정의하면 구현할 수 있다. 하지만 이러한 방식은 Spring에 종속된 인터페이스를 구현해야 하므로 비즈니스 로직이 Spring에 종속되어 POJO를 만족하지 않는다. 그러므로 다른 프레임워크에서 재사용할 수 없다.
2) AutoCloseable 구현
빈이 AutoCloseable을 구현하는 경우 close() 메서드가 소멸 콜백으로 자동으로 실행된다. AutoCloseable은 자바 표준 인터페이스이므로 특정 프레임워크에 종속되지 않는다.
3) 초기화, 소멸 메서드 지정
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Configuration
static class Config {
@Bean(initMethod = "init", destroyMethod = "destroy")
public BeanA beanA() {
return beanA;
}
}
public class BeanA {
public void init() throws Exception {
System.out.println("init");
}
public void close() throws Exception {
System.out.println("destroy");
}
}
@Configuration 설정으로 어떤 메서드가 초기화 메서드, 소멸 메서드인지 지정할 수 있다. 메서드의 이름은 어떠한 이름이라도 사용할 수 있다. 이 방법은 외부 라이브러리에도 초기화, 소멸 동작을 지정할 수 있는 장점이 있다.
특이한 점은 destroyMethod는 생략할 수 있다. 생략하게 된다면 스프링 컨테이너는 close(), shutdown()라고 이름 붙은 메서드를 자동으로 찾아서 등록한다.
4) @PostConstruct, @PreDestroy
1
2
3
4
5
6
7
8
9
10
public class BeanA {
@PostConstruct
public void init() throws Exception {
System.out.println("init");
}
@PreDestroy
public void close() throws Exception {
System.out.println("destroy");
}
간편하게 초기화 메서드, 소멸 메서드를 지정할 수 있다. 또한 @PostConstruct는 스프링이 아닌 Jakarta EE 표준이므로 Spring 컨테이너가 아닌 다른 컨테이너에서도 동작한다. 따라서 POJO의 장점을 그대로 가져올 수 있다.
하지만 외부 라이브러리에는 지정할 수 없으므로 외부 라이브러리에 초기화, 소멸 동작을 지정하기 위해서는 두 번째 방법을 사용하는 것이 좋다.
2. 생성 후, 소멸 전 콜백 관련 코드
@PostConstruct 및 @PreDestroy를 활용한 전처리, 후처리 과정은 CommonAnnotationBeanPostProcessor가 담당한다. CommonAnnotationBeanPostProcessor의 부모 클래스인 InitDestroyAnnotationBeanPostProcessor에서 코드를 확인할 수 있다.
반면 InitializingBean을 구현하는 방식과 @Bean(initMethod=)로 지정하는 방식은 AbstractAutowireCapableBeanFactory에 구현되어 있다. DisposableBean과 @Bean(destroyMethod=) 방식은 DisposableBeanAdapter 클래스에서 구현된다.
1) 초기화 시 인터페이스 구현, 메서드 지정 방법 처리
AbstractAutowireCapableBeanFactory의 invokeInitMethods에서 초기화 메서드를 찾고 실행할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
protected void invokeInitMethods(String beanName, Object bean, @Nullable RootBeanDefinition mbd)
throws Throwable {
//InitializingBean을 구현하는 빈이면
boolean isInitializingBean = (bean instanceof InitializingBean);
if (isInitializingBean && (mbd == null || !mbd.hasAnyExternallyManagedInitMethod("afterPropertiesSet"))) {
if (logger.isTraceEnabled()) {
logger.trace("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");
}
((InitializingBean) bean).afterPropertiesSet();
}
if (mbd != null && bean.getClass() != NullBean.class) {
String[] initMethodNames = mbd.getInitMethodNames();
if (initMethodNames != null) {
for (String initMethodName : initMethodNames) {
if (StringUtils.hasLength(initMethodName) &&
!(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&
!mbd.hasAnyExternallyManagedInitMethod(initMethodName)) {
//@Bean(initMethod = ) 방식이면
invokeCustomInitMethod(beanName, bean, mbd, initMethodName);
}
}
}
}
}
2) 소멸 시 인터페이스 구현, 메서드 지정 방법 처리
DisposableBeanAdapter에서 소멸 처리가 구현되어 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public void destroy() {
if (!CollectionUtils.isEmpty(this.beanPostProcessors)) {
for (DestructionAwareBeanPostProcessor processor : this.beanPostProcessors) {
processor.postProcessBeforeDestruction(this.bean, this.beanName);
}
}
if (this.invokeDisposableBean) {
...
try {
//DisposableBean 인터페이스 구현하는 경우 destroy 실행
((DisposableBean) this.bean).destroy();
}
catch (Throwable ex) {
...
}
}
if (this.invokeAutoCloseable) {
...
try {
//빈이 AutoCloseable을 구현하고 있으면 close 메서드 실행
((AutoCloseable) this.bean).close();
}
catch (Throwable ex) {
...
}
}
//@Bean(destroyMethod=)방식을 구현하고 있으면 커스텀 메서드 실행
else if (this.destroyMethods != null) {
for (Method destroyMethod : this.destroyMethods) {
invokeCustomDestroyMethod(destroyMethod);
}
}
else if (this.destroyMethodNames != null) {
for (String destroyMethodName : this.destroyMethodNames) {
Method destroyMethod = determineDestroyMethod(destroyMethodName);
if (destroyMethod != null) {
destroyMethod = ClassUtils.getPubliclyAccessibleMethodIfPossible(destroyMethod, this.bean.getClass());
invokeCustomDestroyMethod(destroyMethod);
}
}
}
}
DisposableBeanAdapter의 inferDestroyMethodsIfNecessary 메서드에서 DestroyMethodName을 지정하지 않아도 close와 shutdown 메서드를 자동으로 찾아 소멸 전 콜백으로 지정하는 코드를 찾을 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
if (destroyMethodName == null) {
destroyMethodName = beanDefinition.getDestroyMethodName();
boolean autoCloseable = (AutoCloseable.class.isAssignableFrom(target));
if (AbstractBeanDefinition.INFER_METHOD.equals(destroyMethodName) ||
(destroyMethodName == null && autoCloseable)) {
// Only perform destroy method inference in case of the bean
// not explicitly implementing the DisposableBean interface
destroyMethodName = null;
if (!(DisposableBean.class.isAssignableFrom(target))) {
//AutoCloseable을 구현하면 close() 메서드를 사용
if (autoCloseable) {
destroyMethodName = CLOSE_METHOD_NAME;
}
else {
try {
//DestroyMethodName이 null이면 close() 메서드를 찾음
destroyMethodName = target.getMethod(CLOSE_METHOD_NAME).getName();
}
catch (NoSuchMethodException ex) {
try {
//DestroyMethodName이 null이고 close() 메서드도 없으면 shutdown() 메서드를 찾음
destroyMethodName = target.getMethod(SHUTDOWN_METHOD_NAME).getName();
}
catch (NoSuchMethodException ex2) {
// no candidate destroy method found
}
}
}
}
}
beanDefinition.resolvedDestroyMethodName = (destroyMethodName != null ? destroyMethodName : "");
}
3) @PostConstruct 생성 후 콜백
생성하고 초기화 전 postProcessBeforeInitialization을 실행하여 @PostConstruct 메서드 동작을 수행한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//InitDestroyAnnotationBeanPostProcessor.java
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
LifecycleMetadata metadata = findLifecycleMetadata(bean.getClass());
try {
metadata.invokeInitMethods(bean, beanName);
}
catch (InvocationTargetException ex) {
throw new BeanCreationException(beanName, "Invocation of init method failed", ex.getTargetException());
}
catch (Throwable ex) {
throw new BeanCreationException(beanName, "Failed to invoke init method", ex);
}
return bean;
}
4) @PreDestroy 소멸 전 콜백
소멸 전 invokeDestroyMethod를 실행하여 @PreDestroy와 같이 소멸 전 수행될 동작이 지정된 메서드를 실행한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Override
public void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException {
LifecycleMetadata metadata = findLifecycleMetadata(bean.getClass());
try {
metadata.invokeDestroyMethods(bean, beanName);
}
catch (InvocationTargetException ex) {
String msg = "Destroy method on bean with name '" + beanName + "' threw an exception";
if (logger.isDebugEnabled()) {
logger.warn(msg, ex.getTargetException());
}
else if (logger.isWarnEnabled()) {
logger.warn(msg + ": " + ex.getTargetException());
}
}
catch (Throwable ex) {
if (logger.isWarnEnabled()) {
logger.warn("Failed to invoke destroy method on bean with name '" + beanName + "'", ex);
}
}
}