빈 생성-소멸 과정
Spring 컨테이너는 아래와 같은 순서로 빈을 생성한다.
- 스프링 빈 생성
- 의존관계 주입
- PostConstruct 콜백
- 객체 사용
- PreDestory 콜백
- 객체 소멸
(단 생성자 주입을 사용하는 경우 의존관계 주입은 (당연히) 스프링 빈 생성과 동시에 일어난다)
위와 같은 생명주기 하에서, 빈이 생성되고 나서(post construct) 또는, 빈이 소멸되기 전(pre destroy)에 실행될 메서드를 지정할 수 있다.
어노테이션을 이용한 생명주기 콜백
JSR-250 표준 어노테이션인 @PostConstruct와 @PreDestroy 를 이용해 특정 생명주기에서 실행될 메서드를 지정할 수 있다.
다만 @PostConstruct와 @PreDestroy 어노테이션은 java.xml.ws.annotation 패키지 내에 있는데, 이 패키지가 Java 9 부터 Deprecated되었다.
이는 jakarta.annotation-api 로 이관되었으므로, 의존성에 jakarta.annotation-api를 추가해 사용할 수 있다.
<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
<version>2.1.1</version>
</dependency>
아래와 같이 어노테이션을 추가한 메서드를 만들었다.
@PreDestroy 콜백이 정상 작동할 수 있도록 ConfigurableApplicationContext의 close메서드를 호출해 스프링 컨테이너를 정상 종료시켰다.
(ConfigurableApplicationContext는 ApplicationContext의 자식 인터페이스이다.)
package com.sample.spring;
import com.sample.spring.repository.SampleRepository;
**import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;**
public class Application {
private final SampleRepository sampleRepository;
public Application(SampleRepository sampleRepository) {
this.sampleRepository = sampleRepository;
}
public void run() {
System.out.println(sampleRepository.getLastUserId());
}
@PostConstruct
public void postConstruct() {
System.out.println("@PostConstruct");
}
@PreDestroy
public void preDestroy() {
System.out.println("@PreDestroy");
}
}
package com.sample.spring;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Main {
public static void main(String[] args) {
ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
context.getBean(Application.class).run();
context.close();
}
}
위 코드를 실행하면 아래와 같은 결과가 나온다.
@PostConstruct
200
@PreDestroy
어노테이션 기반 생명주기 콜백은 스프링 공식 문서에서 권장하는 방식이다.
JSR-250 @PostConstruct및 @PreDestroy주석은 일반적으로 최신 Spring 애플리케이션에서 수명 주기 콜백을 수신하기 위한 모범 사례로 간주됩니다. 이러한 주석을 사용한다는 것은 Bean이 Spring 특정 인터페이스에 연결되지 않음을 의미합니다.
인터페이스 구현을 통한 생명주기 콜백
Spring에서 제공하는 인터페이스를 구현해 특정 생명주기에서 작동할 로직을 작성할 수도 있다. 그러나 Spring의 인터페이스와 강하게 결합되기 때문에 Spring 프레임워크를 다른 프레임워크로 교체하면 정상 작동하지 않는다는 문제가 있다.
org.springframework.beans.factory.InitializingBean을 상속받고 afterPropertiesSet 메서드를 구현하면 @PostConstruct와 동일한 결과를 얻을 수 있다.
또, org.springframework.beans.factory.DisposableBean을 상속받고 destroy 메서드를 구현하면 @PreDestroy와 동일한 결과를 얻을 수 있다.
package com.sample.spring;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import com.sample.spring.repository.SampleRepository;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
public class Application implements InitializingBean, DisposableBean {
private final SampleRepository sampleRepository;
public Application(SampleRepository sampleRepository) {
this.sampleRepository = sampleRepository;
}
public void run() {
System.out.println(sampleRepository.getLastUserId());
}
@PostConstruct
public void postConstruct() {
System.out.println("@PostConstruct");
}
@PreDestroy
public void preDestroy() {
System.out.println("@PreDestroy");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("afterPropertiesSet");
}
@Override
public void destroy() throws Exception {
System.out.println("destroy");
}
}
결과
@PostConstruct
afterPropertiesSet
200
@PreDestroy
destroy
BeanPostProcessor
생명주기 콜백과는 다소 다르지만, BeanPostProcessor 인터페이스를 구현해 스프링에 모든 빈이 생성될 때 마다 동작할 코드를 작성할 수 있다.
당연히 이 클래스도 빈으로 등록되어야 한다.
public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor {
// simply return the instantiated bean as-is
public Object postProcessBeforeInitialization(Object bean, String beanName) {
return bean; // we could potentially return any object reference here...
}
public Object postProcessAfterInitialization(Object bean, String beanName) {
System.out.println("Bean '" + beanName + "' created : " + bean.toString());
return bean;
}
}
결과
Bean 'messenger' created : org.springframework.scripting.groovy.GroovyMessenger@272961
코드
https://github.com/youhogeon/lickTheSpring/tree/c024980eba36e7aadce916e6e0c3c0b3fd59a450
'핥아먹기 시리즈 > Spring Core 핥아먹기' 카테고리의 다른 글
10. Component Scan과 생성자 주입 (0) | 2022.12.28 |
---|---|
9. 의존관계 자동 주입 (@Autowired) (0) | 2022.12.28 |
7. Singleton Pattern (0) | 2022.12.28 |
6. 모든 빈 조회 (0) | 2022.12.28 |
5. 진정한 DI (0) | 2022.12.28 |