모르지 않다는 것은 아는것과 다르다.

Spring

Spring AOP 2편 (With Spring Transaction)

채마스 2023. 4. 20. 21:09

개요

  • 1편에서 프록시, 어드바이스, 포인트컷, 어드바이저의 개념을 알아보았다.
  • 이번 편에서는 이러한 4가지 요소를 활용하여 Spring AOP가 어떻게 적용되는지 살펴보도록 하자.



Spring AOP 적용방법

  • 위에서 프록시, Advice, Advisor, 포인트컷에 대한 개념을 정리해 보았다.
  • Spring AOP 적용방법에 대해서 알아보자.
  • 아래와 같이 4가지 방법으로 Spring AOP를 적용할 수 있다. (아래로 갈수록 좋다.)
    1. 프록시객체 직접 구현
    2. 빈 후처리기 사용
    3. AnnotationAwareAspectJAutoProxyCreator 사용
    4. @Aspect 사용
  • 실무에서는 거의 4번 사용한다. 그렇기 때문에 AOP 동작과정을 모르고 넘어가는 경우가 많다.
@Slf4j
public class SampleService {

    @Transactional
    public void save() {
        log.info("sample service save!");
    }
}
  • 위와 같이 target이 될 클래스를 하나 구현한다.
  • 이제부터 4가지 방법을 알아보자.



프록시객체 직접 구현

  • 아래와 같이 SampleService를 빈으로 등록하는데, Proxy 객체를 빈으로 등록한다.
@Configuration
public class AopConfig {

    @Bean
    public SampleService sampleService() {

        // 프록시 팩토리 생성
        SampleService target = new SampleService();
        ProxyFactory proxyFactory = new ProxyFactory(target);

        proxyFactory.addAdvisor(getAdvisor());

        // 프록시 객체 생성
        return (SampleService) proxyFactory.getProxy();
    }

    private Advisor getAdvisor() {
        //포인트컷 생성
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression("execution(* test.aop.service.*.save(..))");

        // 어드바이저 생성 (포인트컷 + 어드바이스)
        return new DefaultPointcutAdvisor(pointcut, new SampleAdvice());
    }

}
    • ProxyFactory의 인자로 target 인스턴스를 넘겨주고, 어드바이저를 세팅해준다.
      • 어드바이저에는 하나의 포인트 컷과 하나의 어드바이스가 필요하다.
      • 만약 항상 적용하고 싶다면, Pointcut.TRUE를 포인트컷으로 사용하면 된다.
    • proxyFactory.getProxy() 메소드를 통해서 Proxy객체를 가져올 수 있다.
    • SampleService가 interface가 아니기 때문에, CGLIB 기반으로 프록시가 생성된다.
SampleService가 interface이면, JDK동적프록시 기반으로 프록시가 생성된다.
하지만, proxyFactory.setProxyTargetClass(true)로 설정하면 무조건 CGLIB 기반으로 프록시를 생성한다.
최근 스프링 부트(2.0 이후)는 기본적으로 proxyFactory.setProxyTargetClass(true)로 설정된다.
따라서 target 클래스가 interface라고 할지라도 CGLIB기반으로 프록시가 생성된다.

  • 이렇게 되면, SampleService의 save() 에는 어드바이저(부가기능)가 적용된다.
  • 하지만 위의 방식은 단점이 존재한다.
    • 부가기능을 적용하고 싶은 클래스가 있으면 매번 저렇게 프록시 객체를 직접 만들어줘야 한다는 것이다.
    • 위 문제를 해결하기 위한 방법으로 빈 후처리기를 사용하면 된다.



빈 후처리기 사용

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

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {

        //프록시 적용 패키지가 아니면 기존 bean 반환
        String packageName = bean.getClass().getPackage().getName();
        if (!packageName.startsWith(basePackage)) {
            return bean;
        }

        //프록시 적용 패키지라면 프록시 객체를 bean으로 교체
        ProxyFactory proxyFactory = new ProxyFactory(bean);
        proxyFactory.addAdvisor(advisor);

        return proxyFactory.getProxy();
    }
}
  • 위의 코드는 해당 패키지 하위에 있는 빈을 조회해서 프록시 객체로 변환해서 빈으로 등록해주는 로직이다.
@Configuration
public class AopConfig {

    @Bean
    public SampleService sampleService() {
        return new SampleService();
    }

    @Bean
    public LogProxyPostProcessor logProxyPostProcessor() {
        return new LogProxyPostProcessor("test.aop", getAdvisor());
    }

    private Advisor getAdvisor() {
        //포인트컷 생성
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression("execution(* test.aop.service.*.save(..))");

        // 어드바이저 생성 (포인트컷 + 어드바이스)
        return new DefaultPointcutAdvisor(pointcut, new SampleAdvice());
    }

}
  • SampleService는 그냥 빈으로 등록한다. (보통은 @Service 애노테이션을 사용해서 빈으로 등록하겠지만)
  • 이전처럼 프록시 객체를 하나하나 빈으로 등록해 줄 필요 없이 빈 후처리기를 사용해서 편하게 프록시객체를 빈으로 등록할 수 있다.
  • AnnotationAwareAspectJAutoProxyCreator 를 사용하면 위와 같이 굳이 빈 후처리기를 따로 만들 필요가 없다.



AnnotationAwareAspectJAutoProxyCreator 사용

    • AopAutoConfiguration를 통해서 자동으로 AnnotationAwareAspectJAutoProxyCreator가 빈 후처리기가 빈으로 등록된다. (AopAutoConfiguration에 대해서는 뒤에서 좀 더 자세히 알아보자.)

implementation 'org.springframework.boot:spring-boot-starter-aop' 의존성을 추가해 주면 스프링부트가 AopAutoConfiguration를 활성화한다.

  •  
  • AnnotationAwareAspectJAutoProxyCreator는 스프링 컨테이너에서 스프링 빈으로 등록된 Advisor를 자동으로 찾아서 프록시가 필요한 곳에 자동으로 프록시를 적용해 준다.
  • 또한, 뒤에서 설명하는 @Aspect 도 자동으로 인식해서 프록시를 만들고 AOP를 적용해 준다.
  • AnnotationAwareAspectJAutoProxyCreator를 사용하는 예시는 아래와 같다.
@Configuration
public class AopConfig {

    @Bean
    public SampleService sampleService() {
        return new SampleService();
    }

    @Bean
    public Advisor getAdvisor() {
        //포인트컷 생성
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression("execution(* test.aop.service.*.save(..))");

        // 어드바이저 생성 (포인트컷 + 어드바이스)
        return new DefaultPointcutAdvisor(pointcut, new SampleAdvice());
    }
}
  • 위와 같이 어드바이저만 빈으로 등록해 주면 된다.
  • 따로 빈 후처리기를 구현할 필요가 없다. (너무 좋은듯...)



@Aspect

  • Aspect의 사전적 의미는 관점이라는 뜻이다.
  • @Aspect를 사용하면 위에서 직접 구현한 포인트컷과 어드바이스를 쉽게 구현할 수 있다.
  • 즉, 어드바이저를 쉽게 구현할 수 있는 것이다.
  • 위에서 구현한 예시를 @Aspect를 사용해서 바꾸면 아래와 같다.
@Slf4j
@Aspect
public class SampleAspect {

    @Around("execution(* test.aop.service.*.save(..))")
    public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
        // 타겟 메소드 실행 전 로직
        ;
        log.info(joinPoint.getSignature().getName() + " before!");

        // 실제 타겟 메소드 실행
        Object result = joinPoint.proceed();


        // 타겟 메소드 실행 후 로직
        log.info(joinPoint.getSignature().getName() + " after!");

        return result;
    }
}
  • @Around("execution( test.aop.service..save(..))") 이 부분이 포인트컷이다.
  • 그리고 @Around가 붙은 메소드, 즉 execute 메소드 내에 Advice를 구현하면 된다.
  • 그리고 아래와 같이 빈으로 등록해 주면 AOP가 잘 동작한다.
@Configuration
public class AopConfig {

    @Bean
    public SampleService sampleService() {
        return new SampleService();
    }

    @Bean
    public SampleAspect sampleAspect() {
        return new SampleAspect();
    }
}
  • 이전에 언급했지만, AnnotationAwareAspectJAutoProxyCreator가 @Aspect를 발견하면 어드바이저로 변환해서 저장한다.
  • 그렇기 때문에 실무에선 거의 @Aspect를 사용해서 AOP를 구현하면 된다.
    • 포인트컷과 어드바이스를 쉽게 구현할 수 있기 때문이다. (즉 어드바이저를 쉽게 만들 수 있다.)



Spring AOP를 구현하는 방법에 대해서 알아보았으니, 다음편부터는 Spring AOP의 꽃이라고 불리는 Spring Transaction(@Transactional) 의 동작방식을 알아보도록 하자.