스프링 AOP - 어드바이스 종류 (@Around, @Before, @AfterReturning, @AfterThrowing, @After)
스프링 AOP의 Advice에는 @Around 외에도 아래와 같은 종류가 있습니다.
- Before Advice(@Before): 타깃 메서드(join point)가 실행되기 전에 실행되는 advice입니다. 주로 로그를 남기거나, 사전 검증을 수행하는 데 사용됩니다.
- After Advice: 타겟 메서드가 실행된 후에 실행되는 advice입니다. 성공적으로 실행된 후에 또는 예외가 발생한 후 등에 적용될 수 있습니다.
- @AfterReturning: 메서드가 정상적으로 종료된 후에 실행됩니다.
- @AfterThrowing: 메서드 실행 중 예외가 발생했을 때 실행됩니다.
- @After : 메서드가 정상 또는 예외에 관계없이 실행(finally)됩니다. 일반적으로 리소스를 해제하는 데 사용합니다.
- Around Advice(@Around): 타겟 메서드의 호출 전후에 모두 실행되는 advice 입니다. 메서드의 실행을 제어할 수 있어, 메서드의 실행을 중단시키거나, 실행 시간을 측정하는 등의 다양한 용도로 사용됩니다.
예제 코드
package hello.aop.order.aop;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
@Slf4j
@Aspect
public class AspectV6Advice {
@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());
}
}
}
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() {}
}
위 코드의 주석을 보면, 사실 @Around를 제외한 나머지 advice들은 @Around가 할 수 있는 일의 일부만 제공할 뿐입니다. 즉, @Around만 사용해도 나머지 advice들을 대체하여 수행할 수 있습니다.
그런데 왜 @Around 외에 다른 advice 가 존재하는 걸까요? 아래와 같은 이유들로 정리할 수 있습니다.
- 단순성과 가독성: 각 advice는 특정한 역할을 가지고 있어, 코드가 더 간결하고 읽기 쉬워집니다. @Around advice를 사용하면 모든 로직을 한곳에 집중해야 하므로 코드가 복잡해질 수 있습니다.
- 성능 최적화: @Around advice는 타겟 메서드의 실행 전후에 코드를 실행하므로, 그 내부에서 모든 처리를 하게 됩니다. 만약 특정 조건에서 메서드를 실행하지 않아야 할 경우, 추가적인 조건 검사를 넣어야 하므로 복잡해질 수 있습니다. 반면, @Before나 @After를 사용하면 조건을 미리 체크하여 필요한 경우에만 로직을 실행할 수 있습니다. 예를 들어, 조건이 맞지 않으면 아예 메서드 실행을 하지 않고 조기 리턴할 수 있습니다.
- 의도의 명확성: @Before, @After와 같은 advice를 사용하면 이 코드를 작성한 의도가 명확하게 드러납니다. @Before을 통해 이 코드는 타깃 메서드 실행 전에 한정해서 어떤 일을 하는 코드라는 것을 개발자가 명확히 인지할 수 있습니다.
- 유지보수 용이성: 다양한 advice를 사용하면 코드의 관심사를 분리할 수 있어, 특정 기능을 변경하거나 확장할 때 더 유리합니다. 이를 통해 코드의 유지보수성을 높일 수 있습니다.
이제 @Around 외의 Advice를 작성해 보겠습니다. 위 AspectV6Advice 클래스 하위에 아래 코드를 추가합니다.
@Before("hello.aop.order.aop.Pointcuts.orderAndService()")
public void doBefore(JoinPoint joinPoint) {
// joinPoint.proceed() 자체를 사용하지 않습니다.
// @Around의 경우는 joinPoint.proceed()를 사용해야 다음 대상이 호출되지만,
// @Before는 메서드가 종료되면 다음 대상이 호출됩니다.
log.info("[before] {}", joinPoint.getSignature());
}
// returning 속성에 사용된 이름(result)은 Advice 메서드의 파라미터 이름과 동일해야 합니다.
@AfterReturning(value = "hello.aop.order.aop.Pointcuts.orderAndService()", returning = "result")
public void doReturn(JoinPoint joinPoint, Object result) {
// returning 절에 지정된 타입의 값을 반환하는 메서드만 대상으로 실행합니다.
// 즉 타겟 메서드가 반환한 값이 여기에서는 Object형 이어야 합니다.
log.info("[return] {} return={}", joinPoint.getSignature(), result);
}
// throwing 속성에 사용된 이름(ex)은 Advice 메서드의 파라미터 이름과 동일해야 합니다.
@AfterThrowing(value = "hello.aop.order.aop.Pointcuts.orderAndService()", throwing = "ex")
public void doThrowing(JoinPoint joinPoint, Exception ex) {
// throwing 절에 지정된 타입과 맞는 예외를 대상으로 실행합니다.
log.info("[ex] {} message={}", ex);
}
@After(value = "hello.aop.order.aop.Pointcuts.orderAndService()")
public void doAfter(JoinPoint joinPoint) {
log.info("[after] {}", joinPoint.getSignature());
}
모든 Advice의 메서드 파라미터로 org.aspectj.lang.JoinPoint 를 첫 번째로 받는 것을 확인할 수 있습니다. 이 파라미터는 생략 가능합니다. 단, @Around는 ProceedingJoinPoint를 사용해야 합니다. (ProceedingJoinPoint 는 org.aspectj.lang.JoinPoint의 하위 타입입니다.)
참고
JoinPoint 인터페이스의 주요 기능
- getArgs() : 메서드 인수를 반환합니다.
- getThis() : 프록시 객체를 반환합니다.
- getTarget() : 대상 객체를 반환합니다.
- getSignature() : 조언되는 메서드에 대한 설명을 반환합니다.
- toString() : 조언되는 방법에 대한 유용한 설명을 인쇄합니다.
ProceedingJoinPoint 인터페이스의 주요 기능
- proceed() : 다음 Advice나 타깃을 호출합니다.
스프링 AOP 구현하기 - 1에서 사용한 코드(OrderService, OrderRepository, AopTest)를 이용하여 테스트하면 아래와 같이 결과가 나오는 것을 확인할 수 있습니다.
호출 flow
@Aspect 안에서 위 Advice들이 존재할 때, 위와 같은 순서로 실행됩니다. 단, 호출 순서와 리턴 순서는 반대입니다.
소스 코드
[참고자료]
김영한, "스프링 핵심 원리 - 고급편 ", 인프런