-
BeanPostProcessor 사용법: 빈 초기화 과정에서의 조작과 시점 이해하기Spring 2024. 11. 7. 18:02728x90반응형
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; }728x90반응형'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