[Back-end]/[Spring]

[Spring] 스프링의 빈후처리기(AnnotationAwareAspectJAutoProxyCreator)

DevLoki 2021. 12. 7. 21:53

이전 프록시에서는 직접 빈 후처리기를 구현하였고 빈으로 등록되는 객체들을 프록시로 대체하는 작업을 진행해보았습니다.

 

[Spring] 빈 후처리기(BeanPostProcessor)

이전 글에서는 프록시 팩토리의 장점과 한계점에 대해 알아보았습니다. [Spring] 프록시 팩토리(ProxyFactory) 한계점을 다시 정리해보자면 다음과 같습니다. 프록시를 적용할 스프링 빈의 갯수만큼

yejun-the-developer.tistory.com

 

이번 포스팅에서는 수동으로 진행한 과정을 모두 자동으로 바꿔주는 스프링과 스프링 부트의 편리한 기능에 대해 자세히 알아보겠습니다.

 

우선, 스프링이 제공하는 빈 후처리기를 사용하기 위해선 aop 라이브러리를 추가해야 합니다.

Gradle을 사용하는 경우, build.gradle에 다음과 같이 코드를 추가한 후 [Ctrl + Shift + O]를 눌러 그래이들 변경사항을 로드합니다.

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

정상적으로 실행된 경우, 좌측 프로젝트 뷰의 외부 라이브러리에 다음과 같이 추가된 것을 확인하실 수 있습니다.

이 라이브러리가 추가되면 aspectweaver라는 aspectJ 관련 라이브러리가 추가되고, 스프링 부트가 AOP 관련 클래스를 스프링 빈에 자동으로 등록합니다. 디버그 모드로 실행해보면 다음과 같이 aop auto-configuration이 적용된 것을 확인할 수 있습니다.

스프링 부트는 AnnotationAwareAspectJAutoProxyCreator라는 빈 후처리기를 스프링 빈에 등록합니다.

AnnotationAwareAspectJAutoProxyCreator

스프링이 제공하는 빈 후처리기로 스프링 빈으로 등록된 Advisor를 찾아서 자동으로 프록시 적용이 필요한 곳에 프록시를 적용하여 스프링 빈으로 등록합니다. 이전 포스팅에서 설명했듯이 Advisor는 Pointcut과 Advice로 구성되어 있습니다. 따라서 Pointcut을 통해 프록시 적용 대상 판별과 Advice를 통한 부가기능 적용이 동시에 가능합니다.

동작 과정

  1. 생성 : @SpringBootApplication이 실행되며 @Bean과 @Component가 적용된 모든 스프링 빈 대상이 되는 객체를 생성합니다.
  2. 전달 : 생성된 객체들을 스프링 컨테이너의 빈 저장소에 등록하기 전에 빈 후처리기에 전달합니다.
  3. Advisor 조회 : AnnotationAwareAspectJAutoProxyCreator는 스프링 컨테이너 내의 모든 Advisor를 조회합니다.
  4. 프록시 적용 대상 확인 : 조회한 Advisor 마다 1. 에서 생성된 모든 스프링 빈 대상 객체를 검사하여 프록시 적용 대상인지 판별합니다. 스프링 밴 대상 객체의 모든 메서드에 포인트 컷을 매칭하여 하나의 메서드라도 적용이 되면 해당 객체는 프록시 생성 대상이 됩니다. 
  5. 프록시 생성 : 프록시 생성 대상이 된 객체들은 모두 프록시를 생성합니다. 프록시 대상 객체가 아니라면 원본 객체를 그대로 반환합니다.
  6. 등록 : 반환된 프록시나 원본 대상 객체들을 모두 스프링 컨테이너에 빈 등록합니다.

 

포인트컷의 적용 대상 확인

포인트컷으로 프록시 적용 대상을 확인할 때 실무에서는 일반적으로  AspectJ에서 제공하는 AspectJExpressionPointcut을 사용합니다. execution이나 within 등 다양한 명령어를 이용하여 구체적인 적용 조건을 쉽게 작성할 수 있습니다. 

포인트컷이 적용 대상을 확인하는 시점은 2가지입니다.

  1. 프록시 생성 시점 : 위 그림처럼 스프링 빈을 등록하기 전 후처리 과정에서 조회된 Advisor의 Pointcut을 이용하여 적용대상인지 확인합니다.
  2. 사용시점 : 스프링 빈으로 등록된 프록시들은 Advice가 가지고 있는 부가 기능을 담은 코드가 추가된 게 아닙니다. 프록시는 단지 메서드 호출이 발생했을 때 Advice의 적용 대상이라면 그때 부가 기능을 즉, Advisor의 Advice를 호출하는 것입니다. 따라서 프록시가 호출되는 시점인 사용시점에도 포인트컷을 통해 Advice 적용 대상을 확인하고 실행합니다.

예시

Target.java

package study.springpostprocessor.advisor;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class Target {
    void run(){
        log.info("Target.run() 실행");
    }
}
  • 스프링 빈의 등록 대상이 되는 타깃 클래스입니다. Pointcut의 적용대상이 되기 때문에 프록시가 생성되어 스프링 빈으로 등록됩니다.

MyAdvice.java

package study.springpostprocessor.advisor;

import lombok.extern.slf4j.Slf4j;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.aspectj.lang.annotation.Aspect;

@Slf4j
@Aspect
public class MyAdvice implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        log.info("부가기능 실행");
        return invocation.proceed();
    }
}
  • 직접 구현한 간단한 Advice입니다. 실제 요청 대상 메서드를 호출하기 전에 로그를 출력하여 부가기능의 역할을 합니다.

AdvisorConfig.java

package study.springpostprocessor.advisor;

import org.springframework.aop.Advisor;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.aop.support.NameMatchMethodPointcut;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AdvisorConfig {

    @Bean
    public Target target(){
        return new Target();
    }

    @Bean
    public Advisor myAdvisor(){
        NameMatchMethodPointcut nameMatchMethodPointcut = new NameMatchMethodPointcut();
        nameMatchMethodPointcut.setMappedName("run");
        return new DefaultPointcutAdvisor(nameMatchMethodPointcut, new MyAdvice());
    }


}
  • 타깃 클래스와 어드바이저를 빈 등록하는 설정 파일입니다. DefaultPoincutAdvisor를 통해 어드바이저를 구현하였으며, 포인트컷은 NameMatchMethodPointcut을 사용하여 "run"이라는 이름의 메서드를 가진 클래스들을 프록시 적용 대상으로 판별합니다.

AdvisorConfigTest.java

@Slf4j
@SpringBootTest
class AdvisorConfigTest {

    @Autowired
    Target target;

    @Autowired
    ApplicationContext applicationContext;

    @Test
    void autoProxyCreator(){
        log.info("target = {}", target.getClass());
        target.run();
    }
}

결과

타깃의 run() 메서드를 호출하였는데 부가기능 실행 로그가 함께 출력된 것을 볼 수 있습니다. 또한 target.getClass()의 결과도 EnhancerBySpringCGLIB가 적용되어 프록시가 빈으로 등록된 것을 확인할 수 있습니다.

'[Back-end] > [Spring]' 카테고리의 다른 글

[Spring] @Aspect  (2) 2021.12.10
[Spring] 빈 후처리기(BeanPostProcessor)  (0) 2021.12.05
[Spring] 다이내믹 프록시(DynamicProxy)  (0) 2021.11.30
[Spring] 프록시와 디자인패턴  (1) 2021.11.30
[Spring] DispatcherServlet이란?  (0) 2021.09.18