Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
Tags
- 주식
- 다형성
- 배당성장
- 프로그래머스
- javascript
- 주린이
- 기업분석
- 제태크
- FCF
- 그리디 알고리즘
- 접근제어자
- object
- 금리인상
- 백준
- 객체지향
- XLF
- 미국주식
- 금리인하
- etf
- mco
- S&P500
- 무디스
- StringBuffer
- 알고리즘
- 현금흐름표
- Java
- 자바
- 오버라이딩
- 잉여현금흐름
- 인플레이션
Archives
- Today
- Total
오늘의하루
BeanPostProcessor 사용법: 빈 초기화 과정에서의 조작과 시점 이해하기 본문
반응형
BeanPostProcessor는 Spring에서 빈이 등록되기 전 빈을 조작할 수 있는 강력한 기능입니다.
빈 초기화 과정에서 BeanPostProcessor의 역할을 알아보고 @PostConstruct와의 실행 순서 차이에 따른 충돌 가능성을 알아보겠습니다.
Bean이 등록되는 과정
Spring Container가 빈을 등록할 때 간단하게 아래와 같은 순서로 진행됩니다.
- 빈 정의 등록 - 빈 메타데이터를 등록합니다.
- 빈 인스턴스 생성 - 빈 객체를 생성합니다.
- 의존성 주입 - 의존성을 주입합니다.
- 빈 초기화 - 초기화 메소드를 호출합니다.
- 빈 등록 - 컨테이너에 등록합니다.
BeanPostProcessor를 사용하면 빈 초기화 전후 단계에서 빈을 조작할 수 있습니다.
※ BeanPostProcessor는 가장 우선적으로 등록되어 이후 추가되는 빈을 관리할 수 있습니다.
예제 코드
아래 예제는 @PostConstruct와 BeanPostProcessor의 실행 순서까지 확인해 보기 위한 코드입니다.
@Slf4j
@Component
public class BeanPostProcessor implements org.springframework.beans.factory.config.BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof TestService1 testService1) {
testService1.setMessage("[BEFORE] >> Bean PostProcess Before Initialization");
return testService1;
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if(bean instanceof TestService2 testService2) {
testService2.setMessage("[AFTER] >> Bean PostProcess After Initialization");
return testService2;
}
return bean;
}
}
PostProcessBeforeInitialization
빈 등록 과정에서 초기화 작업 전 빈을 조작할 수 있습니다.
@Slf4j
@Service
public class TestService1 {
private String message;
@PostConstruct
public void init() {
this.message = "This is original message - testService1";
}
public void show() {
log.info(this.message);
}
public void setMessage(String message) {
this.message = message;
}
}
PostProcessAfterInitialization
빈 등록 과정에서 초기화 작업 완료 후 빈을 조작할 수 있습니다.
@Slf4j
@Service
public class TestService2 {
private String message;
@PostConstruct
public void init() {
this.message = "This is original message - testService2";
}
public void show() {
log.info(this.message);
}
public void setMessage(String message) {
this.message = message;
}
}
결과
testService1.show() : this is original message - testService1
>> @PostConstruct가 postProcessBeforeInitialization에서 설정한 값을 덮어쓰게 됩니다.
testService2.show() : [AFTER] >> Bean PostProcess After Initialization
>> postProcessAfterInitialization에서 @PostConstruct에서 설정한 값을 덮어쓰게 됩니다.
@PostConstruct와 BeanPostProcess의 충돌 🧨
@PostConstruct와 BeanPostProcess는 빈의 상태를 조작할 수 있는 기능이 있지만 각각 실행 시점이 다르기 때문에 문제가 발생합니다.
빈 인스턴스 생성 > 의존성 주입 > postProcessBeforeInitialization > @PostConstruct > postProcessAfterInitialization
BeanPostProcessor의 postProcessBeforeInitialization은 의존성 주입이 완료된 후 초기화 작업이 시작되기 전에 실행되므로 @PostConstruct보다 먼저 빈을 수정할 수 있지만 @PostConstruct에서 설정한 값이 BeanPostProcessor에서 설정한 값을 덮어쓸 수 있습니다.
왜 빈 후처리기가 먼저 등록될까?
ApplicationContext가 초기화되는 과정(애플리케이션 시작)에서 refresh()가 호출되게 되는데 이때 등록되는 과정을 보게 되면 알 수 있습니다.
// AbstractApplicationContext.java
@Override
public void refresh() throws BeansException, IllegalStateException {
// 동기화 작업을 위해 lock을 유지
this.startupShutdownLock.lock();
try {
this.startupShutdownThread = Thread.currentThread();
// Application context가 초기화 되는 시간을 측정
StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
// refresh 준비 단계 ( Application context 초기화 )
prepareRefresh();
// BeanFactory 인스턴스 생성
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// BeanFactory 준비 단계 ( BeanFactory 초기화 )
prepareBeanFactory(beanFactory);
try {
// 하위 클래스에서 BeanFactory 후처리 할 수 있도록 설정
// ※ BeanFactory를 후처리할 수 있는 후처리기라서 빈 후처리기와는 다름
postProcessBeanFactory(beanFactory);
// BeanFactory에서 빈 후처리기가 실행되는 시간 측정 시작
StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
// BeanFactory에 등록된 BeanFactory 후처리기 호출
invokeBeanFactoryPostProcessors(beanFactory);
// 빈 후처리기를 BeanFactory에 등록한다. ⭐
registerBeanPostProcessors(beanFactory);
// BeanFactory에서 빈 후처리기 실행되는 시간 측정 종료
beanPostProcess.end();
// 메시지 소스 초기화
initMessageSource();
// 이벤트 멀티캐스터 초기화 ( ? )
initApplicationEventMulticaster();
// Context 하위 클래스 초기화
onRefresh();
// Application listener 조회 및 등록
registerListeners();
// LAZY로 초기화하지 않는 빈을 인스턴스화 및 빈 후처리기 실행 후 등록 ⭐
finishBeanFactoryInitialization(beanFactory);
// refresh 끝
finishRefresh();
}
catch (RuntimeException | Error ex ) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// 싱글톤 빈 자원 해제
destroyBeans();
// active 상태 리셋
cancelRefresh(ex);
// 예외 전파
throw ex;
}
finally {
contextRefresh.end();
}
}
finally {
this.startupShutdownThread = null;
// 동기화 lock 해제
this.startupShutdownLock.unlock();
}
}
빈 후처리는 어떻게 초기화 전후에 조작하는걸까?
아래 코드를 통해 빈 후처리기가 초기화 단계 전후에 조작한다는 것을 확인할 수 있습니다.
// AbstractAutowireCapableBeanFactory.java
@SuppressWarnings("deprecation")
protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
invokeAwareMethods(beanName, bean);
Object wrappedBean = bean;
if (mbd == null || !mbd.isSynthetic()) {
wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
}
try {
invokeInitMethods(beanName, wrappedBean, mbd);
}
catch (Throwable ex) {
throw new BeanCreationException(
(mbd != null ? mbd.getResourceDescription() : null), beanName, ex.getMessage(), ex);
}
if (mbd == null || !mbd.isSynthetic()) {
wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}
return wrappedBean;
}
반응형
'Spring' 카테고리의 다른 글
[레거시 리팩토링] 상속을 넘어 컴포지션으로 가보자 (0) | 2024.12.17 |
---|---|
DTO(Data Transfer Object)를 쓰는 이유? (1) | 2024.11.11 |
[Spring] Proxy 내부 호출 문제 해결 및 순환 참조 문제 해결 (1) | 2024.08.28 |
[Spring] Thread Local 사용 시 주의 사항 (0) | 2024.08.03 |
[Spring] 부하 테스트에서 발견한 회원가입 로직 이상 동작에 대한 고찰 (0) | 2024.05.16 |
Comments