스프링 AOP 구현하기 - 2 (포인트컷 분리, @Order)
위 포스팅에 이어서 작성하는 AOP 구현하기 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 {
}
아래는 수행결과 입니다.
스프링 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
[참고자료]
김영한, "스프링 핵심 원리 - 고급편 ", 인프런