ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • BeanPostProcessor 사용법: 빈 초기화 과정에서의 조작과 시점 이해하기
    Spring 2024. 11. 7. 18:02
    728x90
    반응형

    BeanPostProcessor는 Spring에서 빈이 등록되기 전 빈을 조작할 수 있는 강력한 기능입니다.

    빈 초기화 과정에서 BeanPostProcessor의 역할을 알아보고 @PostConstruct와의 실행 순서 차이에 따른 충돌 가능성을 알아보겠습니다.

    Bean이 등록되는 과정

    Spring Container가 빈을 등록할 때 간단하게 아래와 같은 순서로 진행됩니다.

    1. 빈 정의 등록 - 빈 메타데이터를 등록합니다.
    2. 빈 인스턴스 생성 - 빈 객체를 생성합니다.
    3. 의존성 주입 - 의존성을 주입합니다.
    4. 빈 초기화 - 초기화 메소드를 호출합니다.
    5. 빈 등록 - 컨테이너에 등록합니다.
    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
    반응형
Designed by Tistory.