스프링은 @Aspect 애노테이션을 통해서 편리하게 AOP 기능을 사용할 수 있도록 구현해놨다. 직접 Aspect를 사용해보면서 다양한 사용 방법을 정리해보려고 한다.
가장 먼저 메서드가 실행될 때 로그를 출력하는 기능을 구현해보자.
@Slf4j
@Aspect
public class AspectV1 {
@Around("execution(* hello.aop.order..*(..))")
public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("[log] {}", joinPoint.getSignature()); // joinPoint 시그니처
return joinPoint.proceed();
}
}
해당 Flow는 아래 그림처럼 흘러간다.
@Around
에 직접 주입한 포인트컷은 메서드로 분리할 수도 있고, 외부 클래스에서 참조하는 방식으로도 변경가능하다.
1. 같은 클래스의 메서드로 분리
@Pointcut("execution(* hello.aop.order..*(..))")
private void allOrder() { //pointcut signature
}
2. 외부 클래스에서 메서드로 분리
public class PointCuts {
@Pointcut("execution(* hello.aop.order..*(..))")
public void allOrder() { //pointcut signature
}
}
포인트 컷을 구현할 때 고려해야할 사항이 몇가지 있다.
하나의 어드바이스에 여러개의 포인트 컷을 부여할 수도 있다.
@Around("allOrder() && allService()")
public Object doTransaction(ProceedingJoinPoint joinPoint) throws Throwable {
&&, ||, ! 등을 통해서 조건을 제시할 수 있다. 작성한 코드는 Order 패키지에 있는 Service 클래스들에 트랜잭션을 붙이는 로직의 애노테이션이다.
현재 Order에 있는 Service에는 doLog와 doTransaction 어드바이스 두개가 존재한다. 순서는 기본적으로 작성된 순서에 의존하는데 순서를 변경하고 싶으면 클래스를 통해서 분리해야한다.
@Slf4j
public class AspectV5Order {
@Aspect
@Order(2)
public static class LogAspect {
@Around("hello.aop.order.aop.PointCuts.allOrder()")
public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("[log] {}", joinPoint.getSignature()); // joinPoint 시그니처
return joinPoint.proceed();
}
}
@Aspect
@Order(1)
public static class TxAspect {
//hello.aop.order 패키지와 하위 패키지 이면서 클래스 이름 패턴이 *Service
@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());
}
}
}
}
만약 어드바이스에 우선순위를 부여하고 싶다고 내부 클래스로 관리하던가, 클래스에서 우선순위를 관리해야한다. 만약 메서드에 @Order
애노테이션을 작성하면 우선순위가 부여되지 않는다.
지금까지 사용한 @Around
를 제외하고
@Before
, @AfterReturning
, @AfterThrowing
, @After
등이 있다.
@Around("hello.aop.order.aop.PointCuts.orderAndService()")
public Object doTransaction(ProceedingJoinPoint joinPoint) throws Throwable {
try {
//@Before
log.info("[트랜잭션 시작] {}", joinPoint.getSignature());
Object result = joinPoint.proceed();
//@AfterReturning
log.info("[트랜잭션 커밋] {}", joinPoint.getSignature());
return result;
} catch (Exception e) {
//@AfterThrowing
log.info("[트랜잭션 롤백] {}", joinPoint.getSignature());
throw e;
} finally {
//@After
log.info("[리소스 릴리즈] {}", joinPoint.getSignature());
}
}
각각의 시점은 해당 메서드에서 찾아볼 수 있다.
어떻게 생각하면 @Around
만 있으면 될 것 같은데 세세한 포인트가 꼭 필요할까 의문을 가질 수 있다. 먼저 각각의 포인트가 필요한 이유는 파라미터에서 찾아볼 수 있다.
@Around
의 경우 ProceedingJoinpoint를 받기 때문에 proceed가 꼭 존재해야한다. 만약 proceed가 없다면 핵심 기능이 작동하지 않는다. 이러한 실수를 방지하기 위해서 각 포인트별로 어드바이스가 있도록 구성되어있다.