Spring Boot

스프링 AOP - 어드바이스 종류 (@Around, @Before, @AfterReturning, @AfterThrowing, @After)

작은별._. 2024. 10. 1. 00:14
728x90

스프링 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)를 이용하여 테스트하면 아래와 같이 결과가 나오는 것을 확인할 수 있습니다.

 

success() 메소드 실행

 

exception() 메소드 실행

 

호출 flow


 

@Aspect 안에서 위 Advice들이 존재할 때, 위와 같은 순서로 실행됩니다. 단, 호출 순서와 리턴 순서는 반대입니다.

 

소스 코드

https://github.com/eunhwa99/SpringAOP/blob/main/advanced/src/main/java/hello/aop/order/aop/AspectV6Advice.java

 

SpringAOP/advanced/src/main/java/hello/aop/order/aop/AspectV6Advice.java at main · eunhwa99/SpringAOP

SpringAOP 구현 코드. Contribute to eunhwa99/SpringAOP development by creating an account on GitHub.

github.com

 

 


[참고자료]

김영한, "스프링 핵심 원리 - 고급편 ", 인프런

728x90
반응형