Question
- AOP에서 어드바이스의 우선순위를 지정하는 방법은?
- @Around와 @Before 사용의 큰 차이점은?
- @Around로 모든 기능을 수행할 수 있는데 다른 어노테이션이 있는 이유는?
포인트컷을 분리하여 AOP 사용
//Pointcut 분리 하지 않는 방식
@Slf4j
@Aspect
public class AspectV2 {
@Around("execution(* hello.aop.order..*(..))")
public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("[log] {}", joinPoint.getSignature()); // 메서드 시그니처를 로깅
return joinPoint.proceed(); // 실제 메서드를 실행
}
}
//Pointcut 분리 하는 방식
@Slf4j
@Aspect
public class AspectV2 {
@Pointcut("execution(* hello.aop.order..*(..))") // 포인트컷 표현식
private void allOrder() {} // 포인트컷 시그니처
@Around("allOrder()")
public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("[log] {}", joinPoint.getSignature()); // 메서드 시그니처를 로깅
return joinPoint.proceed(); // 실제 메서드를 실행
}
}
- 이 방식을 사용하면 여러 어드바이저에 포인트컷을 재활용 가능
- 함수 이름으로 포인트컷 의도를 전달할 수 있어 가독성도 증가
- 반환 타입은 void, 코드 내용은 비워둬야 함
포인트컷 조합
@Slf4j
@Aspect
@Component
public class AspectV2 {
@Around("allOrder() && allService()")
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());
}
}
}
- && (AND), || (OR), ! (NOT) 3가지 조합이 가능하다.
포인트컷 참조
public class Pointcuts {
// hello.aop.order 패키지와 하위 패키지에 대한 포인트컷 정의
@Pointcut("execution(* hello.aop.order..*(..))")
public void allOrder() {}
// 타입 패턴이 *Service인 메서드에 대한 포인트컷 정의
@Pointcut("execution(* *..*Service.*(..))")
public void allService() {}
// allOrder와 allService의 조합 포인트컷
@Pointcut("allOrder() && allService()")
public void orderAndService() {}
}
public class AspectV3 {
// 로그 어드바이스 정의
@Around("hello.aop.order.aop.Pointcuts.allOrder()")
public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("[log] {}", joinPoint.getSignature());
return joinPoint.proceed();
}
...
}
- 포인트컷을 공용으로 사용하기 위해 별도의 외부 클래스에 모아서 사용도 가능
어드바이스 순서
@Slf4j
public class AspectV5Order {
@Aspect
@Order(2)
@Component
public static class LogAspect {
@Around("hello.aop.order.aop.Pointcuts.allOrder()")
public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("[log] {}", joinPoint.getSignature());
return joinPoint.proceed();
}
}
@Aspect
@Order(1)
@Component
public static class TxAspect {
@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 어노테이션을 사용해 우선순위를 줄 수 있는데, 이는 메서드 단위가 아니라 클래스 단위
- 즉, doLog, doTranscation에 각각 @Order를 붙여줄 수 없고, 각각을 별도의 클래스로 만들어서 @Order를 붙여줘야 의도한대로 동작함
어드바이스 종류
- @Around
- 메서드 호출 전후에 수행, 가장 강력한 어드바이스, 조인 포인트 실행 여부 선택, 반환 값 변환, 예외 변환 등이 가능
- ProceedingJoinPoint.proceed()를 호출 해야 다음 대상이 호출되므로, 호출하지 않는 실수를 하면 큰 문제 발생
- proceed() 를 여러번 실행할 수도 있음(재시도)
- @Before
- 조인 포인트 실행 이전에 실행
- Around는 ProceedingJoinPoint.proceed()를 호출 해야 다음 대상이 호출됨
- Before는 이를 호출하지 않아도 다음 타겟이 자동으로 호출되며, 이는 실수로 호출하지 않는 문제를 방지 할 수 있음
- @AfterReturning : 조인 포인트가 정상 완료후 실행
- @AfterReturning(value = "hello.aop.order.aop.Pointcuts.orderAndService()", returning = "result")
public void doReturn(JoinPoint joinPoint, Object result)
- 타겟 함수의 Return 타입과 매개면수 타입이 맞아야 함(부모타입이면 받을 수 있음)
- returning 속성에 사용된 이름과 파라미터 매개변수 이름이 일치해야 함
- @Around와 다르게 반환 객체 변경이 불가능하므로 반환하려면 @Around를 사용해야 함
- @AfterThrowing
- @AfterThrowing(value = "hello.aop.order.aop.Pointcuts.orderAndService()", throwing = "ex")
public void doThrowing(JoinPoint joinPoint, Exception ex) {
- 메서드가 예외를 던지는 경우 실행
- throwing 속성에 사용된 이름은 어드바이스 메서드의 매개변수 이름과 일치해야 함
- throwing 절에 지정된 타입과 맞는 예외를 대상으로 실행 (부모 타입을 지정하면 모든 자식 타입은 인정)
- @After
- 조인 포인트가 정상 또는 예외에 관계없이 실행(finally)
- 메서드 실행이 종료되면 실행(finally)
- 정상 및 예외 반환 조건을 모두 처리
- 일반적으로 리소스를 해제하는 데 사용
- @Around 하나로 모든 처리가 가능한데 다른 어드바이스도 있는 이유는?
- @Around로 jointPoint.proceed()를 호출하지 않는 문제를 @Before로 방지 가능
- @Before, @After 이런 어노테이션은 의도가 명확하게 들어가 가독성이 좋음