Back-End/Spring Advance & Boot

스프링 고급편 - 빈 후처리기

Meluu_ 2024. 9. 27. 19:23

✔️ 빈 후처리기 - BeanPostProcessor


스프링이 빈 저장소에 등록할 목적으로 생성한 객체를 빈 저장소에 등록하기 직전에조작이 가능하다.

한 마디로 빈 생성후무언가 처리하는 용도이다.

 

 이를 사용하면 컴포넌트 스캔 대상들도 프록시로 등록할 수 있게 된다.   

 

 

 

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

public interface BeanPostProcessor {
    Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException
    Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException
}

 

빈 후처리기를 사용하려면 해당 인터페이스 구현 후 스프링 빈 등록

postProcessBeforeInitialization : 객체 생성 이후 @PostConstruct 같은 초기화가 발생 전에 호출되는 포스트 프로세서

postProcessAfterInitialization : 객체 생성 이후 @PostConstruct 같은 초기화가 발생한 다음에 호출되는 포스트 프로세서  

 

 

참고

스프링은 CommonAnnotationBeanPostProcessor 라는 빈 후처리기를 자동으로 등록하는데, 여기에서
@PostConstruct 애노테이션이 붙은 메서드를 호출한다. 따라서 스프링 스스로도 스프링 내부의 기능을 확장
하기 위해 빈 후처리기를 사용한다.

 

 

 

적용

 

@Slf4j
public class PackageLogTracePostProcessor implements BeanPostProcessor {
    private final String basePackage;
    private final Advisor advisor;


    public PackageLogTracePostProcessor(String basePackage, Advisor advisor) {
        this.basePackage = basePackage;
        this.advisor = advisor;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        log.info("param beanName={}, bean={}", beanName, bean);

        //프록시 적용 대상 여부 체크
        // 프록시 적용 대상이 아니면 원본을 그대로 진행
        String packageName = bean.getClass().getPackageName();
        if (!packageName.startsWith(basePackage)) {
            return bean;
        }

        // 프록시 대상이면 프록시를 만들어서 반환
        ProxyFactory proxyFactory = new ProxyFactory(bean);
        proxyFactory.addAdvisor(advisor);

        Object proxy = proxyFactory.getProxy();

        return proxy;

    }
}

 

패키지명으로 프록시 적용 대상을 체크하고 맞다면 프록시를 만들어서 반환, 아니라면 bean을 그대로 반환한다. 

 

설정클래스를 만들어서 빈 후처리기를 등록한다. 이때 advisor도 만들어서 파라미터로 넘겨야한다.

 

@Slf4j
@Configuration
@Import({AppV1Config.class, AppV2Config.class}) // v3는 컴포넌트 스캔 대상이기에 알아서 등록됨
public class BeanPostProcessorConfig {

    @Bean
    public PackageLogTracePostProcessor logTracePostProcessor(LogTrace logTrace) {
        return new PackageLogTracePostProcessor("hello.proxy.app", getAdvisor(logTrace));
    }

    private Advisor getAdvisor(LogTrace logTrace) {
        //pointcut
        NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
        pointcut.setMappedNames("request", "order*", "save*");

        // advice
        LogTraceAdvice advice = new LogTraceAdvice(logTrace);
        return new DefaultPointcutAdvisor(pointcut, advice);


    }


}

 

그런데 굳이 후처리기에서 적용대상을 체크할 필요없이 포인트 컷을 사용하면 깔끔하다.

 

 

 

 

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


build.gradle - 추가 

implementation 'org.springframework.boot:spring-boot-starter-aop'

aspectJ 라이브러리등록  (AOP 관련 클래스를 자동으로 스프링빈에 등록)

 

 

자동 프록시 생성기 -AutoProxyCreator

  • 스프링 부트 자동 설정으로 AnnotationAwareAspectJAutoProxyCreator 라는 빈 후처리기가 
    스프링 빈에 자동으로 등록
  • 이 빈 후처리기는 스프링 빈으로 등록된 Advisor 들을 자동으로 찾아서 프록시가 필요한 곳에자동으로 프록시를 적용
  • Advisor의 포인트 컷 + Advice가 있으므로 프록시 적용 대상 구분 가능
  • @AspectJ와 관련된 AOP 기능도 자동으로 찾아서 처리

 

 

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

  1. 생성 : 스프링이 스프링 빈 대상을 객체로 생성 (@Bean, 컴포넌트 스캔 대상)
  2. 전달 : 생성된 객체를 빈 저장소등록 전 빈 후처리기에 전달
  3. 모든 Advisor 빈 조회 : 자동 프록시생성기 - 빈 후처리기는 스프링 컨테이너에서 모든 Advisor를 조회  
  4. 프록시 적용 대상 체크 : advisor에 포함되어 있는 포인트 컷이 프록시 적용대상 체크 (클래스정보, 객체의 모든 메서드) 
    1. 이때 하나라도 만족하면 프록시로생성 
  5. 프록시 생성 : 프록시 적용 대상이면 프록시 생성 및 반환, 스프링 빈으로 등록
      1. 아니라면 원본 객체를 스프링 빈으로등록   
  6.  빈 등록 : 반환된 객체는 스프링 빈으로 등록

 

 

여기서 여러 Advisor를 적용할 경우, Advisor의 포인트 컷 조건과 맞으면 적용할 수 있다. 

예) advisor 1 의 조건 과 advisor 2 의 조건 따로따로 체크 후 맞는 것은 적용

 

따라서 1 만 적용, 1 , 2 적용,  2만 적용하는 경우가 생긴다.     

 

 

 

스프링이 AnnotationAwareAspectJAutoProxyCreator 빈 후처리기를 자동으로 등록

따라서 Advisor만 빈으로 등록하면 된다. 

 

 

 

 

 

 

중요 : 포인트컷은  2가지에 사용

1. 프록시 적용 여부 판단 - 생성 단계

  • 프록시 생성 여부는클래스 + 메서드 조건 모두 비교하여 체크,  만약 포인트 컷 조건에 하나라도 만족한다면 프록시를 생성
  • 만족하지 않는다면 생성 X  

2. 어드바이스 적용 여부판단 - 사용 단계

  • 부가 기능인 어드바이스를 적용할지 말지포인트컷을 보고 판단  

  

 

프록시를 모든 곳에 생성하는 것은 비용 낭비이기에 최소한의 프록시를 적용

 

 

패키지에 메서드 이름까지 함께 지정할 수 있는 매우정밀한 포인트 컷  

AspectJExpressionPointcut

AspectJ 라는 AOP 에특화된포인트 컷 표현식을 적용 가능 (AOP 챕터에서 자세히..)

 

@Bean
public Advisor advisor3(LogTrace logTrace) {
   AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
 	pointcut.setExpression("execution(* hello.proxy.app..*(..)) && 
    					!execution(* hello.proxy.app..noLog(..))");
 	LogTraceAdvice advice = new LogTraceAdvice(logTrace);
 	//advisor = pointcut + advice
 	return new DefaultPointcutAdvisor(pointcut, advice);
}

* : 모든 반환 타입

hello.proxy.app.. : 해당 패키지와 그 하위 패키지

*(..) : * 모든메서드 이름, (..) 파라미터는 상관 없음 

 

 

🔖 학습내용 출처


스프링 핵심 원리 - 고급편 / 김영한