Home Spring Framework - 빈 후처리기
Post
Cancel

Spring Framework - 빈 후처리기

빈 후처리기

일반적인 빈 등록

Alt text

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

빈 후처리기(BeanPostProcessor)

스프링이 빈 저장소에 등록할 목적으로 생성한 객체를 빈 저장소에 등록하기 직전에 조작하고 싶다면 빈 후처리기를 사용하면 된다. 빈 후처리기(BeanPostProcessor)는 이름 그래도 빈을 생성한 후에 무언가를 처리하는 용도로 사용된다. 객체를 조작하거나 완전히 다른 객체로 다꿔치기하는 것도 가능하다.

빈 후처리기 과정

Alt text

  1. 생성

    • 스프링 빈 대상이 되는 객체를 생성
    • @Bean, 컴포넌트 스캔 모두 포함
  2. 전달

    • 생성된 객체를 빈 저장소에 등록하기 직전에 빈 후처리기에 전달
  3. 후 처리 작업

    • 빈 후처리기는 전달된 스프링 빈 객체를 조작하거나 다른 객체로 바꿔치기 가능
  4. 등록

    • 빈 후처리기에서 빈을 그대로 반환하면 해당 빈이 등록되고, 바꿔치기 하면 다른 객체가 빈 저장소에 등록됨

Alt text

빈 후처리기 적용

빈 후처리기를 적용하기 위해선 BeanPostProcessor 인터페이스를 구현하고, 스프링 빈으로 등록하면 된다.

1
2
3
4
public interface BeanPostProcessor {
  Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
  Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}
  • postProcessBeforeInitialization
    • 객체 생성 이후에 @PostConstruct 같은 초기화가 발생하기전에 호출되는 포스트 프로세서
  • postProcessAfterInitialization
    • 객체 생성 이후에 @PostConstruct 같은 초기화가 발생한 다음에 호출되는 포스트 프로세서

빈 후처리기 적용 예시

아래 그림과 같은 객체 바꿔치기 구현 예시

Alt text

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
public class BeanPostProcessorTest {
  @Test
  void postProcessor() {
    ApplicationContext applicationContext =
      new AnnotationConfigApplicationContext(BeanPostProcessorConfig.class);

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

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

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

    @Bean
    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 postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
      log.info("beanName={} bean={}", beanName, bean);

      if (bean instanceof A) {
        return new B();
      }
      return bean;
    }
  }
}
  • AToBPostProcessor
    • 빈 후처리기
    • 인터페이스인 BeanPostProcessor를 구현하고 스프링 빈으로 등록하면 스프링 컨테이너가 빈 후처리기로 인식하고 동작함

빈 후처리기 적용 정리

빈 후처리기는 빈을 조작하고 변경할 수 있는 후킹 포인트 이다. 이것은 빈 객체를 조작하거나 심지어 다른 객체로 바꿀수도 있을정도로 막강한 기능이다.

@PostConstruct의 비밀 @PostConstruct는 스프링 빈 생성 이후에 빈을 초기화하는 역할을 한다. 동작을 생각해보면 빈의 초기화라는 것이 단순히 @PostConstruct 어노테이션이 붙은 초기화 메소드를 한번 호출만 하면 된다. 이러한 동작을 수행하기 위해 스프링은 CommonAnnotationBeanPostProcessor라는 빈 후처리기를 자동으로 등록하고, 여기서 @PostConstruct 어노테이션이 붙은 메소드를 호출한다. 그러므로 스프링은 스스로도 스프링 내부의 기능을 확장하기 위해 빈 후처리기를 사용한다.

빈 후처리기로 프록시 샐성시 포인트컷 포인트컷은 이미 클래스, 메소드 단위의 필터기능을 가지고 있기 떄문에 프록시 적용 대상 여부를 정밀하게 설정 가능하다. 어드바이저는 포인트컷을 가지고 있으므로 어드바이저를 통해 포인트컷을 확인할 수 있다.

결과적으로 포인트컷은 다음 두곳에 사용된다.

  1. 프록시 적용 대상 여부를 체크하여 꼭 필요한 곳에만 프록시를 적용(빈 후처리기 - 자동 프록시 생성)
  2. 프록시의 어떤 메소드가 호출되었을 때 어드바이저를 적용할지 판단(프록시 내부)

스프링이 제공하는 빈 후처리기

자동 프록시 생성기(AutoProxyCreator)

  • 자동으로 프록시를 생성해주는 빈 후처리기
  • 자동 프록시 생성기는 스프링 빈으로 등록된 Advisor들을 자동으로 찾아서 프록시가 필요한 곳에 자동으로 프록시를 적용
  • Advisor안에는 Pointcut과 Advice가 이미 모두 포함되어 있으므로, Advisor만 알고 있으면 그 안에 있는 Pointcut으로 어떤 스프링 빈에 프록시를 적용해야 할지 알 수 있음

관련 기능을 사용하기 위해선 아래와 같이 추가 필요

1
2
// build.gradle
implementation 'org.springframework.boot:spring-boot-starter-aop'
  • 해당 라이브러리를 추ㅏ하면 aspectjweaver라는 aspectJ관련 라이브러리를 등록하고, 스프링 부트가 AOP 관련 클래스를 자동으로 스프링 빈에 등록 함

자동 프록시 생성기 작동 과정

Alt text

  • 생성
    • 스프링이 스프링 빈 대상이 되는 객체를 생성
    • @Bean, 컴포넌트 스캔 모두 포함
  • 전달
    • 생성된 객체를 빈 저장소에 등록하기 직전에 빈 후처리기에 전달
  • 모든 Advisor 빈 조회
    • 자동 프록시 생성기(빈 후처리기)는 스프링 컨테이너에서 모든 Advisor를 조회
  • 프록시 적용 대상 체크
    • 앞서 조회한 Advisor에 포함되어 있는 포인트컷을 사용하여 해당 객체가 프록시를 적용할 대상인지 판단
      • 이때 객체의 클래스 정보는 물록이고 해당 객체의 모든 메소드를 포인트컷에 하나하나 모두 매칭 함
      • 조건이 하나라고 만족하면 프록시 적용 대상
      • 예를 들어 10개의 메소드 중에 하나만 포인트컷 조건에 만족해도 프록시 적용 대상이 됨
  • 프록시 생성
    • 프록시 적용 대상이면 프록시를 생성하고 반환하여 프록시를 스프링 빈으로 등록
      • 만약 프록시 적용 대상이 아니라면 원본 객체를 반환하여 원본 객체를 스프링 빈으로 등록
  • 빈 등록
    • 반환된 객체는 스프링 빈으로 등록

Alt text

자동 프록시 생성기와 포인트 컷

중요한 내용으로, 포인트컷은 2가지에 사용된다.

  • 프록시 적용 여부 판단 - 생성 단계
    • 자동 프록시 생성기는 포인트컷을 사용하여 해당 빈이 프록시를 생성할 필요가 있는지 없는지 체크
    • 클래스 + 메소드 조건을 모두 비교
      • 모든 메소드를 체크하는데, 포인트컷 조건에 하나하나 매칭함
      • 만약 조건에 맞는것이 하나라도 있다면 프록시를 생성
      • 예) orderController에 포인트컷 조건을 만족하는 request() 메소드와, 포인트컷 조건을 불만족하는 noLog() 메소드가 있을떄 하나라도 만족하는 메소드가 있으므로 프록시를 생성
    • 조건에 맞는 것이 하나도 없다면 프록시를 생성할 필요가 없으므로 프록시를 생성하지 않음
  • 어드바이스 적용 여부 판단 - 사용 단계
    • 프록시가 호출되었을떄 부가 기능인 어드바이스를 적용할지 말지 포인트컷을 보고 판단
      • 예) orderController에 포인트컷 조건을 만족하는 request() 메소드와, 포인트컷 조건을 불만족하는 noLog() 메소드가 있을 떄
        • orderController의 reqeust()는 포인트컷 조건에 만족하므로 프록시는 어드바이스를 먼저 호출하고 target을 호출
        • orderController의 noLog()는 포인트컷 조건에 불만족하므로 target만 호출

프록시를 모든 곳에 생성하는 것은 비용낭비이므로 꼭 필요한 곳에만 적용해야 한다. 그러므로 자동 프록시 생성기는 모든 스프링 빈에 프록시를 적용하는 것이 아닌 포인트컷으로 한번 필터링하여 어드바이스가 사용될 가능성이 있는 곳에만 프록시를 생성한다.

하나의 프록시, 여러 Advisor 적용

예를 들어 어떤 스프링 빈이 advisor1, advisor2가 제공하는 포인트컷의 조건을 모두 만족하면 프록시 자동 생성기는 몇개의 프록시를 생성할까? 정답은 자동 프록시 생성기는 하나의 프록시만 생성한다. 왜냐하면 프록시 팩토리가 생성하는 프록시는 내부에 여러 Advisor를 포함할 수 있기 때문에, 불필요하게 여러개의 프록시를 생성할 필요가 없기 때문이다. 이는 스프링 AOP도 동일한 방식으로 동작한다.

프록시 자동 생성기 상황별 정리

  • advisor1의 포인트컷만 만족
    • 프록시 1개 생성
    • 프록시에 advisor1만 포함
  • advisor1, advisor2의 포인트컷 모두 만족
    • 프록시 1개 생성
    • 프록시에 advisor1, advisor2 포함
  • advisor1, advisor2의 포인트컷 모두 불만족
    • 프록시가 생성되지 않음

Alt text

Alt text


참고

  • 스프링 핵심 원리 - 고급편(김영한)
This post is licensed under CC BY 4.0 by the author.