빈 후처리기

현시기얌·2021년 11월 25일
0

AOP

목록 보기
7/19

빈 후처리기

@Bean이나 컴포넌트 스캔으로 스프링 빈으로 등록하면, 스프링은 대상 객체를 생성하고 스프링 컨테이너 내부의 빈 저장소에 등록한다.
그리고 이후에 스프링 컨테이너를 통해 등록한 빈을 조회해서 사용하면 된다.

빈 후처리기 - BeanPostProcessor

스프링이 빈 저장소에 등록할 목적으로 생성한 객체를 빈 저장소에 등록하기 직전에 조작하고 싶다면 빈 후처리기를 사용하면 된다.
빈 포스트 프로세서는 번역하면 빈 후처리기인데 이름 그대로 빈을 생성한 후에 무언가를 처리하는 용도로 사용한다.

빈 후처리기 기능

빈 후처리기는 객체를 조작하거나 완전히 다른 객체로 바꿔치기 하는 것 등 기능이 막강하다.

빈 후처리기 과정

빈 등록 과정

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

예제 코드 (일반적인 빈 등록과정)

@Slf4j
public class BasicTest {

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

        //A는 빈으로 등록된다.
        final A a = applicationContext.getBean("beanA", A.class);
        a.helloA();

        //B는 빈으로 등록되지 않는다.
        Assertions.assertThrows(NoSuchBeanDefinitionException.class, () -> applicationContext.getBean(B.class));
    }

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

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

    static class B {
        public void helloA() {
            log.info("hello B");
        }
    }
}

beanA라는 이름으로 A객체를 스프링 빈으로 등록했다.
B 객체는 스프링빈으로 등록하지 않았기 때문에 스프링 컨테이너에서 찾을 수 없다.

예제 코드 ( A객체 B객체로 바꿔치기 해보기)

BeanPostProcessor 인터페이스 - 스프링 제공

public interface BeanPostProcessor {
    Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException
    Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException 
}
  • 빈 후처리기를 사용하려면 BeanPostProcessor 인터페이스를 구현하고 스프링 빈으로 등록하면 된다.
  • postProcessBeforeInitialization : 객체 생성 이후에 @PostConstruct 같은 초기화가 발생하기 전에 호출되는 포스트 프로세서이다.
  • postProcessAfterInitialization : 객체 생성 이후에 @PostConstruct 같은 초기화가 발생한 다음에 호출되는 포스트 프로세서이다.
@Slf4j
public class BeanPostProcessorTest {

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

        //beanA 이름으로 B객체가 빈으로 등록된다.
        final B b = applicationContext.getBean("beanA", B.class);
        b.helloB();

        //A는 빈으로 등록되지 않는다.
        Assertions.assertThrows(NoSuchBeanDefinitionException.class, () -> applicationContext.getBean(A.class));
    }

    @Configuration
    static class BeanPostProcessorConfig {
        @Bean(name = "beanA")
        public A a() {
            return new A();
        }
        @Bean
        public AtoBPostProcessor helloPostProcessor() {
            return new AtoBPostProcessor();
        }
    }

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

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

    static class AtoBPostProcessor implements BeanPostProcessor{

        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
            log.info("beanName = {} bean = {}", beanName, bean);
            if (bean instanceof A) {
                return new B();
            }
            return bean;
        }
    }
}
  • 인터페이스인 BeanPostProcess를 구현하고 스프링 빈으로 등록하면 스프링 컨테이너가 빈 후처리기로 인식하고 동작한다.
  • 이 빈 후처리기는 A객체를 새로운 B객체로 바꿔치기 한다.
    파라미터로 넘어오는 빈(bean) 객체가 A의 인스턴스이면 B객체를 생성해서 반환한다.
    여기서 A대신에 반환된 값인 B가 스프링 컨테이너에 등록된다.

실행 결과

beanName = beanA bean = hello.proxy.postprocessor.BeanPostProcessorTest$A@23aa363a
hello B

beanName = beanA, bean = A 객체의 인스턴스가 빈 후처리기에 넘어 온 후 B객체로 바꿔치기 당해졌다.
따라서 최종적으로 beanA라는 스프링 빈 이름에 A객체 대신 B객체가 등록된 것을 확인할 수 있다.
A는 스프링 빈으로 등록조차 되지 않는다.

정리

빈 후처리기는 빈을 조작하고 변경할 수 있는 후킹 포인트이다.
이것은 빈 객체를 조작하거나 심지어 다른 객체로 바꾸어 버릴 수 있을 정도로 막강하다.
여기서 조작이라는 것은 해당 객체의 특정 메소드를 호출하는 것을 뜻한다.
일반적으로 스프링 컨테이너가 등록하는 특히 컴포넌트 스캔의 대상이 되는 빈들은 중간에 조작할 방법이 없는데
빈 후처리기를 사용하면 개발자가 등록하는 모든 빈을 중간에 조작할 수 있다.
이 말은 빈 객체를 프록시로 교체하는 것도 가능하다는 뜻이다.

빈 후처리기 덕분에 프록시를 생성하는 부분을 하나로 집중할 수 있다.
그리고 컴포넌트 스캔처럼 스프링이 직접 대상을 빈으로 등록하는 경우에도 중간에 빈 등록 과정을 가로채서 원본 대신에 프록시를 스프링 빈으로 등록할 수 있다.

cf) @PostConstruct

@PostConstruct는 스프링 빈 생성 이후에 빈을 초기화 하는 역할을 한다.
그런데 생각해보면 빈의 초기화 라는 것이 다순히 @PostConstruct 애노테이션이 붙은 초기화 메소드를 한번 호출만 하면 된다.
쉽게 이야기해서 생성된 빈을 한번 조작하는 것이다.
따라서 빈을 조작하는 행위를 하는 적절한 빈 후처리기가 있으면 될 것 같다.
스프링은 CommonAnnotationBeanPostProcesso라는 빈 후처리기를 자동으로 등록하는데
여기에서 @PostConstruct 애노테이션이 붙은 메소드를 호출한다.
따라서 스프링 스스로도 스프링 내부의 기능을 확장하기 위해 빈 후처리기를 사용한다.

프록시 적용 대상 여부 체크

  • 애플리케이션을 실행해서 로그를 확인해보면 알겠지만 우리가 직접 등록한 스프링 빈들 뿐만 아니라 스프링 부트가 기본으로 등록하는 수 많은 빈들이 빈 후처리기에 넘어온다.
    그래서 어떤 빈을 프록시로 만들 것인지 기준이 필요하다.
    ex) 특정 패키지를 기준으로 해당 패키지와 그 하위 패키지의 빈들을 프록시로 만든다.
  • 스프링 부트가 기본으로 제공하는 빈 중에는 프록시 객체를 만들 수 없는 빈들도 있다.
    따라서 모든 객체를 프록시로 만들 경우 오류가 발생한다.
profile
현시깁니다

0개의 댓글