본문 바로가기
Spring

빈 후처리

by sangyunpark99 2025. 3. 13.
프록시 팩토리를 사용하는 방법보다
더 좋은 방법은 없을까?

 

이번글은 빈 후처리기에 대해 정리했습니다.

참고 강의 : 김영한의 스프링 핵심 원리 - 고급 편

 

프록시 팩토리로 프록시 객체를 생성하는 방식은 어떤 문제가 존재할까요?

 

  • 설정 파일이 너무 많아집니다. 예를 들어 스프링 빈이 100개가 존재하는 경우, 프록시를 통해 부가 기능을 적용하기 위해선 100개의 동적 프록시 생성 코드를 만들어야 합니다.
  • 스프링은 컴포넌트 스캔을 사용해서 실제 객체를 스프링 빈으로 등록합니다. 동적으로 생성된 프록시 객체는 컴포넌트 스캔을 통해서 등록을 할 수 없기 때문에, 프록시 객체를 직접 빈으로 등록해야 합니다. 예를 들어, 등록해야 할 프록시 객체가 100개가 존재한다면, 수동으로 빈으로 등록해야 합니다.

 

스프링이 빈을 등록하는 방식을 그림으로 나타내면 다음과 같습니다.

 

이 문제들을 어떻게 해결할 수 있을까요?

 

빈 후처리기를 사용하면 해결할 수 있습니다.

 

 

빈 후처리기(BeanPostProcessor)

객체를 빈 저장소에 등록하기 직전에 조작하고 싶은 경우 빈 후처리기를 사용합니다.

BeanPostProcessor라는 이름과 같이 빈을 생성한 후에 무언가를 처리하는 용도로 사용합니다.

 

빈 후처리기는 객체를 조작하거나 완전히 다른 객체로 바꾸는 것이 가능합니다.

과정을 그림으로 나타내면 다음과 같습니다.

 

  1. 생성 : 스프링 빈 대상이 되는 객체를 생성합니다. (@Bean, 컴포넌트 스캔 모두 포함)
  2. 전달 : 생성된 객체를 빈 저장소에 등록하기 직전에 빈 후처리기에 전달합니다.
  3. 후 처리 작업 : 빈 후처리기는 전달된 스프링 빈 객체를 조작하거나 다른 객체로 바꿔치기할 수 있습니다.
  4. 등록 : 빈 후처리기는 빈을 반환합니다. 빈을 그대로 반환할경우 해당 빈이 등록되고, 바꿔치기한 경우 다른 객체가 빈 저장소에 등록됩니다.

 

빈 후처리기로 객체를 바꿔치기하는 과정은 아래와 같습니다.

 

빈 후처리기 적용하기

 

먼저, 일반적인 스프링 빈을 등록하는 과정을 코드를 통해 알아보겠습니다.

public class BasicTest {

    @Test
    void basicConfig() {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(BasicConfig.class);

        A a = applicationContext.getBean("beanA", A.class);
        a.helloA();

        assertThrows(NoSuchBeanDefinitionException.class, () -> applicationContext.getBean(B.class));
    }

    @Slf4j
    @Configuration
    static class BasicConfig {
        @Bean(name = "beanA")
        public A a() {
            return new A();
        }
    }

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

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

 

new AnnotationConfigApplicationContext(BasicConfig.class)로 스프링 컨테이너를 생성하면서 BasicConfig.class 파일을 넘겨줍니다. BasicConfig.class 설정 파일이 스프링 빈으로 등록되게 됩니다.

 

@Slf4j
    @Configuration
    static class BasicConfig {
        @Bean(name = "beanA")
        public A a() {
            return new A();
        }
    }

 

 

A객체를 beanA라는 이름으로 스프링 빈으로 등록합니다.

 

 A a = applicationContext.getBean("beanA", A.class);

 

"beanA"라는 이름으로 A 타입의 스프링 빈을 찾을 수 있습니다.

 

 

 

applicationContext.getBean(B.class)​

 

B타입 객체는 스프링 빈으로 등록을 하지 않았기 때문에 오류가 발생합니다.

 

 

출력 결과

 

 

이제, 빈 후처리기를 적용 해보겠습니다.

 

스프링에선 빈 후처리기를 위한 BeanPostProcessor 아래와 같은 인터페이스를 제공합니다.

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;
    }
}

 

빈 후처리기를 사용하기 하려면, BeanPostProcessor 인터페이스를 구현하고 스프링 빈으로 등록하면 됩니다.

postProcessBeforeInitialization : 객체 생성 이후, @PostConstruct 같은 초기화가 발생하기 전에 호출됩니다.

postProcessAfterInitialization : 객체 생성 이후 @PostConstruct같은 초기화가 발생한 다음에 호출됩니다.

 

public class BeanPostProcessTest {

    @Test
    void postProcessor() {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeanPostProcessorConfig.class);

        B b = applicationContext.getBean("beanA", B.class);
        b.helloB();

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

    @Slf4j
    @Configuration
    static class BeanPostProcessorConfig {

        @Bean(name = "beanA")
        public A a() {
            return new A();
        }

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

    }

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

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

    @Slf4j
    static class AToBPostProcessor implements BeanPostProcessor {
        @Override
        public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
            log.info("beanName = {} bean = {}", beanName, bean);
            if(bean instanceof A) {
                return new B();
            }

            return bean;
        }
    }
}

 

AToBPostProcessor : BeanPostProcessor 인터페이스를 구현한 빈 후처리기 입니다. 스프링 빈으로 등록하는 경우 스프링 컨테이너가 빈 후처리기로 인식하고 동작합니다.

 

AToBPostProcessor 빈 후처리기는 A 객체를 새로운 B 객체로 바꿔치기합니다. 즉, 파라미터로 넘어온 빈 객체가 A의 인스턴스이면 새로운 B 객체를 생성해서 반환해 줍니다. 

 

출력 결과는 다음과 같습니다.

 

 

@Slf4j
@Configuration
static class BeanPostProcessorConfig {

    @Bean(name = "beanA")
    public A a() {
        return new A();
    }

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

 

분명히 BeanPostProcessorConfig에서 "beanA"이름으로 A객체를 빈으로 등록했음에도 불구하고

아래와 같은 빈 후처리 코드로 인해서, A객체가 B객체가 등록이 됩니다.

@Slf4j
static class AToBPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        log.info("beanName = {} bean = {}", beanName, bean);
        if(bean instanceof A) {
            return new B();
        }

        return bean;
    }
}

 

빈을 생성한 후에 빈 초기화를 하는 @PostConstruct는 어떤 원리로 동작할까요?

 

@PostConstruct 동작 원리

@PostConstruct는 Spring 빈이 생성된 후 초기화 작업을 수행하는 역할을 합니다.
빈의 초기화란, 클래스 내부에서 @PostConstruct 애노테이션이 붙은 메서드를 실행하는 것을 의미합니다.

그러나 Spring 컨테이너는 빈으로 등록된 객체의 내부 메서드를 직접 호출하지 않습니다.
따라서 빈 후처리기(Bean PostProcessor)를 활용하여 @PostConstruct가 붙은 메서드를 찾아 실행하는 방식으로 동작합니다.

 

정리

  • 빈 후처리기는 빈을 조작하고 변경할 수 있습니다.
  • 빈 후처리기는 빈 객체를 다른 객체로 바꿀 수 있습니다.
  • 빈 후처리기를 사용하면 빈 객체를 프록시로 교체하는 것도 가능합니다.

'Spring' 카테고리의 다른 글

[Spring DB] Connection Pool  (0) 2025.04.25
[Spring Docs] DispatcherServlet #2  (0) 2025.03.16
어드바이저  (0) 2025.03.12
프록시 팩토리  (0) 2025.03.11
CGLIB  (0) 2025.03.10