Spring Boot

스프링 AOP 구현하기 - 2 (포인트컷 분리, @Order)

작은별._. 2024. 9. 30. 09:00
728x90

https://silver-programmer.tistory.com/entry/%EC%8A%A4%ED%94%84%EB%A7%81-AOP-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0-1

 

위 포스팅에 이어서 작성하는 AOP 구현하기 2편입니다. 이번 포스팅에는 아래와 같은 내용을 정리하였습니다.

  1. 포인트컷을 분리하여 외부에서 어떻게 참조할 수 있는지
  2. Advice 적용 순서를 어떻게 바꿀 수 있는지

스프링 AOP 구현 - 3 (Pointcut 참조)

포인트컷을 공용으로 사용하기 위해 별도의 외부 클래스에 모아둘 수 있습니다. 외부에서 호출할 때는 포인트컷의 접근 제어자를 public으로 설정합니다!


package hello.aop.order.aop;

import org.aspectj.lang.annotation.Pointcut;

public class Pointcuts {

    //hello.aop.order 패키지와 하위 패키지
    @Pointcut("execution(* hello.aop.order..*(..))")
    public void allOrder(){} //pointcut signature

    //클래스 이름 패턴이 *Service
    @Pointcut("execution(* *..*Service.*(..))")
    public void allService(){}

    //allOrder && allService
    @Pointcut("allOrder() && allService()")
    public void orderAndService() {}

}

 


위 포인트컷을 이용해 Advisor를 생성합니다.

아래와 같이 @Around 내부에 포인트컷이 들어있는 클래스 경로를 패키지명을 포함하여 작성합니다. 현재 AspectV4의 클래스는 Pointcuts 클래스와 같은 패키지 내부에 있어서 패키지 경로를 포함시키지 않아도 AOP 가 문제없이 적용됩니다.


package hello.aop.order.aop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;

@Slf4j
@Aspect
public class AspectV4 {
    @Around("Pointcuts.allOrder()") // 현재 pointcuts는 이 패키지와 같은 패키지라서 이렇게만 명시해도 프록시 적용된다.
    public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("[log] {}", joinPoint.getSignature()); //join point 시그니처
        return joinPoint.proceed();
    }

    @Around("hello.aop.order.aop.Pointcuts.orderAndService()")
    public Object doTransaction(ProceedingJoinPoint joinPoint) throws Throwable {

        try {
            log.info("[트랜잭션 시작] {}", joinPoint.getSignature());
            Object result = joinPoint.proceed();
            log.info("[트랜잭션 커밋] {}", joinPoint.getSignature());
            return result;
        } catch (Exception e) {
            log.info("[트랜잭션 롤백] {}", joinPoint.getSignature());
            throw e;
        } finally {
            log.info("[리소스 릴리즈] {}", joinPoint.getSignature());
        }
    }

}

 

AopTest 클래스를 아래와 같이 @Import 를 수정한 후 실행하면 기존과 동일한 결과가 출력됩니다.


@Import(AspectV4.class)
@SpringBootTest
public class AopTest {
}

 

 

아래는 수행결과 입니다.


success() 메소드 실행 (커밋)

 

exception() 메소드 실행 (롤백)

 


스프링 AOP 구현 - 4 (Advice 순서 정하기)

Advice는 기본적으로 순서를 보장하지 않습니다. 순서를 지정하기 위해서는 @Aspect 적용 단위, 즉 클래스 및 인터페이스 단위로 org.springframework.core.annotation.@Order 애노테이션을 적용해야 합니다. 따라서 지금처럼 하나의 Aspect에 여러 Advice가 있으면 Order 애노테이션이 제대로 적용되지 않아 순서를 보장할 수 없습니다. 즉, Aspect를 별도의 클래스로 분리해야 하여 Order를 적용하여야 합니다.

 

[doTransaction() → doLog()] 순서로, 트랜잭션이 먼저 처리되고, 이후에 로그가 남도록 순서를 지정하겠습니다. (숫자가 작을수록 먼저 실행)


package hello.aop.order.aop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.core.annotation.Order;

@Slf4j
public class AspectV5{

    @Aspect
    @Order(2)
    public static class LogAspect { // 별도 class 생성, 분리
        @Around("hello.aop.order.aop.Pointcuts.allOrder()")
        public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable {
            log.info("[log] {}", joinPoint.getSignature()); //join point 시그니처
            return joinPoint.proceed();
        }
    }

    @Aspect
    @Order(1)
    public static class TxAspect { // 별도 class 생성, 분리
        @Around("hello.aop.order.aop.Pointcuts.orderAndService()")
        public Object doTransaction(ProceedingJoinPoint joinPoint) throws Throwable {

            try {
                log.info("[트랜잭션 시작] {}", joinPoint.getSignature());
                Object result = joinPoint.proceed();
                log.info("[트랜잭션 커밋] {}", joinPoint.getSignature());
                return result;
            } catch (Exception e) {
                log.info("[트랜잭션 롤백] {}", joinPoint.getSignature());
                throw e;
            } finally {
                log.info("[리소스 릴리즈] {}", joinPoint.getSignature());
            }
        }
    }


}

 

AopTest 클래스의 @Import를 아래와 같이 변경 후 실행해 보겠습니다.

@Import({AspectV5.LogAspect.class, AspectV5.TxAspect.class})
@SpringBootTest
public class AopTest {
}

 

아래 결과를 보면, TxAspect이 먼저 적용되고 LogAspect이 적용되었음을 확인할 수 있습니다.

 

전체 소스코드

https://github.com/eunhwa99/SpringAOP/tree/main/advanced/src/main/java/hello/aop

 

SpringAOP/advanced/src/main/java/hello/aop at main · eunhwa99/SpringAOP

SpringAOP 구현 코드. Contribute to eunhwa99/SpringAOP development by creating an account on GitHub.

github.com

 


[참고자료]

김영한, "스프링 핵심 원리 - 고급편 ", 인프런

 

728x90
반응형