어드바이스는 어느 시점에 어떤 기능을 의미한다.
따라서, 이번 포스팅에서는 어드바이스의 시점 지정에 대해 알아볼 것이다.
어드 바이스는 다양한 어노테이션을 AOP 실행 시점을 지정할 수 있다.
어노테이션 | 설명 |
---|---|
@Around | - 메서드 호출 전후에 수행 |
- 가장 강력한 어드바이스 | |
- 조인 포인트 실행 여부 선택 | |
- 반환 값 변환 | |
- 예외 변환 등이 가능 | |
@Before | - 조인 포인트 실행 이전에 실행 |
@After | - 조인 포인트가 정상 또는 예외에 관계없이 실행 (finally와 유사) |
@AfterReturning | - 조인 포인트가 정상 완료 후 실행 |
@AfterThrowing | - 메서드가 예외를 던지는 경우 실행 |
@Aspect
@Order(2)
public static class TxAspect {
@Around("isyoudwn.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 하나만 있어도, AOP의 모든 시점을 구현할 수 있다.
그러나, Target을 직접 실행해야 되는 단점이 있고, Advice의 내부 로직을 이해해야지만, 그 Advice가 어떤 시점에 실행되는지 알 수 있다.
따라서, 아래와 같이 시점을 특정하는 어노테이션이 존재한다.
조인 포인트 실행 전 Advice 실행
// before은 로직 수행 후 joinpoint를 실행한다
@Before("isyoudwn.aop.order.aop.PointCuts.orderAndService()")
public void doBefore(JoinPoint joinPoint) {
log.info("[before] {}", joinPoint.getSignature());
}
메서드 실행 후 정상적으로 Return될 때 Advice 실행
@AfterReturning(value = "isyoudwn.aop.order.aop.PointCuts.orderAndService()", returning = "result")
public void doReturn(JoinPoint joinPoint, Object result) {
log.info("[return] {} return = {}", joinPoint.getSignature(), result);
}
returning
속성에 사용된 이름과 파라미터를 동일한 이름으로 지정하면, 매칭되어서 파라미터에 return 값이 들어온다. 이때, 파라미터 타입은 메서드의 반환 타입과 동일해야 된다(부모 타입을 지정하면 모든 자식 타입은 인정된다)메서드 실행이 예외를 던져서 종료될 때 실행될 때 Advice 실행
@AfterThrowing(value = "isyoudwn.aop.order.aop.PointCuts.orderAndService()", throwing = "ex")
public void doReturn(JoinPoint joinPoint, Exception ex) {
log.info("[ex] message = {}", ex.getMessage());
}
throwing
속성에 사용된 이름은 어드바이스 메서드의 매개변수 이름과 일치해야한다throwing
파라미터에 지정된 타입과 맞는 예외를 대상으로 실행한다(부모 타입을 지정하면, 모든 자식 타입은 인정된다)메서드 실행이 종료되면 Advice가 실행된다. (finally를 생각하면 된다)
@After(value = "isyoudwn.aop.order.aop.PointCuts.orderAndService()")
public void doAfter(JoinPoint joinPoint) {
log.info("[after] {}", joinPoint.getSignature());
}
@Slf4j
@Aspect
public class AspectV6Advice {
// before은 로직 수행 후 joinpoint를 실행한다
@Before("isyoudwn.aop.order.aop.PointCuts.orderAndService()")
public void doBefore(JoinPoint joinPoint) {
log.info("[before] {}", joinPoint.getSignature());
}
@AfterReturning(value = "isyoudwn.aop.order.aop.PointCuts.orderAndService()", returning = "result")
public void doReturn(JoinPoint joinPoint, Object result) {
log.info("[return] {} return = {}", joinPoint.getSignature(), result);
}
@AfterThrowing(value = "isyoudwn.aop.order.aop.PointCuts.orderAndService()", throwing = "ex")
public void doReturn(JoinPoint joinPoint, Exception ex) {
log.info("[ex] message = {}", ex.getMessage());
}
@After(value = "isyoudwn.aop.order.aop.PointCuts.orderAndService()")
public void doAfter(JoinPoint joinPoint) {
log.info("[after] {}", joinPoint.getSignature());
}
}
Around를 사용하지 않고, 시점을 특정하는 어노테이션을 총동원해서 작성한 Advice 들의 실행 결과는 아래와 같다.
Aspect 안에 동일한 종류의 어드바이스가 2개이면 순서가 보장되지 않기 때문에, 이 경우에는 @Order을 사용해서 순서를 지정해주어야 한다!