[Spring] BeanPostProcessor

dnjstjt12·2025년 3월 14일

BeanPostProcessor

스프링은 BeanPostProcessor인터페이스를 통해 빈의 초기화 전/후에 빈을 커스터마이징할 수 있습니다.

BeanPostProcessor 인터페이스

package org.springframework.beans.factory.config;
...
public interface BeanPostProcessor {
    @Nullable
    default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Nullable
    default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
}

스프링이 제공하는 인터페이스로 빈의 초기화 전이나 후에서 빈객체를 조작하거나 다른 객체로 바꿔치기할 수 있습니다.

초기화 단계는 스프링 빈이 생성되고, 의존성 주입이 완료된 이후에, 그 빈을 사용하기 전 마지막 준비 작업을 수행하는 단계입니다.

즉,객체가 new 되고 필요한 의존성 (@Autowired, 생성자 등)이 주입된 다음
"자, 이제 이 객체 사용할 준비 다 됐어!" 라고 스프링이 선언하기 전에
→ 초기화(init) 라는 단계를 거치는 거예요.

동작 과정

  1. 생성: 빈을 인스턴스화 하고 @Autowire 등을 통해 빈 의존성을 주입합니다.
  2. 전달: 빈을 BeanPostProcessor에 전달합니다.
  3. postProcessBeforeInitialization(): 초기화 전의 작업을 합니다.
  4. 초기화: @PostConstruct 등의 어노테이션이 붙으면 초기화 작업을 진행합니다.
  5. postProcessAfterInitialization(): 초기화 후의 작업을 합니다.
  6. 등록: BeanPostProcessor의 빈을 스프링 IoC 컨테이너에 등록합니다.

@PostConstruct는 직접 호출되는 게 아니라, 스프링 내부의 CommonAnnotationBeanPostProcessor를 통해 실행됩니다.

즉, @PostConstruct는 "빈 초기화 단계"로 간주되며
스프링이 만든 BeanPostProcessor가 이걸 자동으로 호출
해주는 겁니다.

빈을 바꿔치기하는 코드 예제

A 클래스

@Slf4j
public class A {
    public void helloA() {
        log.info("hello A");
    }
}

B 클래스

@Slf4j
public class B {
    public void helloB() {
        log.info("hello B");
    }
}

이제 A를 빈에 등록할 수 있게 Config를 작성해보겠습니다.

Config

@Configuration
public class Config {

    @Bean
    public A a(){
        return new A();
    }

    @Bean
    public AToBPostProcessor aToBPostProcessor(){
        return new AToBPostProcessor();
    }

    @Slf4j
    static class AToBPostProcessor implements BeanPostProcessor {

        @Override
        public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
            if( bean instanceof A){
                log.info("A 인스턴스를 B로 바꿉니다.");
                return new B();
            }
            return bean;
        }
    }
}

여기서 AToBPostProcessor은 A를 B로 바꾸어줍니다.

테스트 코드

@Slf4j
public class PostTest {
    @Test
    public void test() {
        ApplicationContext context = new AnnotationConfigApplicationContext(Config.class);

        B b = (B) context.getBean("a", B.class);
        b.helloB();

        Assertions.assertThrows(NoSuchBeanDefinitionException.class,
                () -> context.getBean(A.class));
    }
}

이 코드를 실행하면, 스프링은 A 빈을 생성하고 초기화 전에 AToBPostProcessor를 통해 B 인스턴스로 바꿔치기하게 됩니다. 이후 @Autowired A로 주입받은 객체는 실제로는 B 클래스 인스턴스입니다.

테스트 결과

빈 A를 B 객체로 바꾸어 스프링 IoC컨테이너에 등록하는 코드를 보았습니다. 그러면 프록시로도 바꿀 수 있지 않을까요?

BeanPostProcessor와 프록시

프록시 관련 링크:
https://velog.io/@dnjstjt1297/Spring-Proxy12
https://velog.io/@dnjstjt1297/Spring-Proxy22

실제 스프링 내부에서도 이렇게 빈을 프록시 객체로 감싸서 등록하는 방식이 자주 사용됩니다.

예를 들어, AOP나 트랜잭션 처리, 보안 기능 등은 실제 빈을 프록시로 감싸서 실행 흐름을 제어합니다.

스프링은 AutoProxyCreator라는 특별한 BeanPostProcessor를 내부적으로 등록하여, 프록시 적용이 필요한 빈을 자동으로 감싸주는 역할을 합니다.

정리

항목설명
BeanPostProcessor빈을 초기화 전/후에 조작할 수 있는 스프링 확장 포인트
AutoProxyCreatorBeanPostProcessor를 구현한 프록시 적용 전용 클래스
실무 적용 예AOP, 트랜잭션, 보안, @Async, @Scheduled 등

참고
강의: 인프런의 김영한님의 『스프링 핵심 원리-고급편』을 듣고 작성했습니다.

profile
안녕하세요!

0개의 댓글