오늘의하루

BeanPostProcessor 사용법: 빈 초기화 과정에서의 조작과 시점 이해하기 본문

Spring

BeanPostProcessor 사용법: 빈 초기화 과정에서의 조작과 시점 이해하기

오늘의하루_master 2024. 11. 7. 18:02
반응형

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;
}
반응형
Comments