티스토리 뷰

Spring

[Spring] Advice 종류

hyuuny 2022. 5. 29. 13:11

Advice 종류

@Around : 메서드 호출 전후에 수행, 가장 강력한 어드바이스, 조인 포인트 실행 여부 선택, 반환 값 변환, 예외 변환 등이 가능

@Before : 조인 포인트 실행 이전에 실행된다.

@AfterReturning : 조인 포인트가 정상 완료후 실행된다.

@AfterThrowing : 메서드가 예외를 던지는 경우 실행된다.

@After : 조인 포인트가 정상 또는 예외에 관계없이 실행(finally)된다.


아래 예제를 살펴보면서 좀 더 깊이 학습해보자!


@Slf4j
@Aspect
public class AspectAdvice {

  @Around("com.hyuuny.advanced.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());
    }

  }

  @Before("com.hyuuny.advanced.order.aop.Pointcuts.orderAndService()")
  public void doBefore(JoinPoint joinPoint) {
    log.info("[before] {}", joinPoint.getSignature());
  }

  @AfterReturning(value = "com.hyuuny.advanced.order.aop.Pointcuts.orderAndService()", returning = "result")
  public void doReturn(JoinPoint joinPoint, Object result) {
    log.info("[return] {}, return={}", joinPoint.getSignature(), result);
  }

  @AfterThrowing(value = "com.hyuuny.advanced.order.aop.Pointcuts.orderAndService()", throwing = "ex")
  public void doThrowing(JoinPoint joinPoint, Exception ex) {
    log.info("[ex] {}, message={}", joinPoint.getSignature(), ex.getMessage());
  }

  @After(value = "com.hyuuny.advanced.order.aop.Pointcuts.orderAndService()")
  public void doAfter(JoinPoint joinPoint) {
    log.info("[after] {}", joinPoint.getSignature());
  }

}

doTransaction() 메서드에 남겨둔 주석을 보자.
복잡해 보이지만 사실 @Around를 제외한 나머지 어드바이스들은 @Around가 할 수 있는 일의 일부만 제공할 뿐이다. 따라서 @Around어드바이스만 사용해도 필요한 기능을 모두 수행할 수 있다.


참고

모든 어드바이스는 org.aspectj.lang.JoinPoint를 첫번째 파라미터에 사용할 수 있다. (생략해도 된다.)
단, @AroundProceedingJoinPoint을 사용해야 한다. ProceedingJoinPointorg.aspectj.lang.JoinPoint의 하위 타입이다.


@Around

메서드의 실행의 주변에서 실행. 메서드 실행 전후에 작업을 수행.

  @Around("com.hyuuny.advanced.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());
    }

  }

가장 강력한 어드바이스. (거즘 @Around로 처리한다고 보면 된다.)
조인 포인트 실행 여부 선택, 전달 값 변환, 반환 값 변환, 예외 변환, 트랜잭션 처럼 try ~ catch~ finally 모두 들어가는 구문 처리가 가능하다. 어드바이스의 첫 번째 파라미터는 ProceedingJoinPoint를 사용해야 하고, proceed()를 통해 대상을 실행한다. proceed()를 여러번 실행할 수도 있다.


@Before

조인 포인트 실행 전

  @Before("com.hyuuny.advanced.order.aop.Pointcuts.orderAndService()")
  public void doBefore(JoinPoint joinPoint) {
    log.info("[before] {}", joinPoint.getSignature());
  }

@Around와 다르게 작업 흐름을 변경할 수는 없다.
@BeforeProceedingJoinPoint.proceed() 자체를 사용하지 않으며, 메서드 종료시 자동으로 다음 타켓이 호출된다. 물론 예외가 발생하면 다음 코드가 호출되지는 않는다.


@AfterReturning

메서드 실행이 정상적으로 반환될 때 실행

  @AfterReturning(value = "com.hyuuny.advanced.order.aop.Pointcuts.orderAndService()", returning = "result")
  public void doReturn(JoinPoint joinPoint, Object result) {
    log.info("[return] {}, return={}", joinPoint.getSignature(), result);
  }

returning 속성에 사용된 이름은 어드바이스 메서드의 매개변수 이름과 일치해야 한다.
returning절에 지정된 타입의 값을 반환하는 메서드만 대상으로 실행한다. (부모 타입을 지정하면 모든 자식 타입은 인정된다.) @Around와 다르게 반환되는 객체를 변경할 수는 없지만, 조작할 수 는 있다.


@AfterThrowing

메서드 실행이 예외를 던져서 종료될 때 실행

  @AfterThrowing(value = "com.hyuuny.advanced.order.aop.Pointcuts.orderAndService()", throwing = "ex")
  public void doThrowing(JoinPoint joinPoint, Exception ex) {
    log.info("[ex] {}, message={}", joinPoint.getSignature(), ex.getMessage());
  }

throwing 속성에 사용된 이름은 어드바이스 메서드의 매개변수 이름과 일치해야 한다.
throwing절에 지정된 타입과 맞은 예외를 대상으로 실행한다. (부모 타입을 지정하면 모든 자식 타입은 인정된다.)


@After

메서드 실행이 종료되면 실행

  @After(value = "com.hyuuny.advanced.order.aop.Pointcuts.orderAndService()")
  public void doAfter(JoinPoint joinPoint) {
    log.info("[after] {}", joinPoint.getSignature());
  }

정상 및 예외 반환 조건을 모두 처리하며, 일반적으로 리소스를 해제할 때 사용한다.


Test code

@Import(AspectAdvice.class)
public class AopTest {

  @Autowired
  OrderService orderService;

  @Autowired
  OrderRepository orderRepository;

  @Test
  void success() {
    orderService.orderItem("itemA");
  }

  @Test
  void exception() {
    Assertions.assertThatThrownBy(() -> orderService.orderItem("ex"))
        .isInstanceOf(IllegalStateException.class);
  }

}

success 결과

정상적으로 실행되어 @Before, @AfterReturning, @After가 호출된 것을 확인할 수 있다.


exception 결과

@Before 실행 후, 예외가 발생하여 @AfterThrowing, @After가 호출된 것을 확인할 수 있다


Advice 호출 순서

  • 스프링은 5.2.7 버전부터 동일한 @Aspect안에서 동일한 조인포인트의 우선순위를 정했다.
  • 실행 순서: @Around -> @Before -> @After -> @AfterReturning -> @AfterThrowing
  • 어드바이스가 적용되는 순서는 이렇게 적용되지만, 호출 순서와 리턴 순서는 반대이다.
  • @Aspect안에 동일한 종류의 어드바이스가 2개 있으면 순서가 보장되지 않는다. @Aspect를 분리하고 @Order를 적용하자.

@Around 외에 다른 어드바이스가 존재하는 이유가 뭘까?

  @Around("com.hyuuny.advanced.order.aop.Pointcuts.orderAndService()")
  public void doBefore(JoinPoint joinPoint) {
    log.info("[before] {}", joinPoint.getSignature());
  }

이 코드는 타켓을 호출하지 않는 문제가 있다.
이 코드를 개발한 의도는 타켓 실행 전에 로그를 출력하는 것이다. @Around는 항상 joinPoint.proceed()를 호출해야 한다. 만약 실수로 호출하지 않으면 타켓이 호출되지 않는 치명적인 버그가 발생한다.


  @Before("com.hyuuny.advanced.order.aop.Pointcuts.orderAndService()")
  public void doBefore(JoinPoint joinPoint) {
    log.info("[before] {}", joinPoint.getSignature());
  }

반면에 @BeforejoinPoint.proceed()를 호출하는 고민을 하지 않아도 된다.

@Around가 가장 넓은 기능을 제공하는 것은 맞지만, 실수할 가능성이 있다. 반면에 @Before, @After 같은 어드바이스는 기능은 적지만 실수할 가능성이 낮고, 코드도 단순하다. 그리고 가장 중요한 점이 있는데, 바로 이 코드를 작성한 의도가 명확하게 드러난다는 점이다.



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

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B3%A0%EA%B8%89%ED%8E%B8#

'Spring' 카테고리의 다른 글

[Spring] AOP @target, @within  (0) 2022.06.06
[Spring] AOP Pointcut execution  (0) 2022.05.31
[Spring] AOP 용어  (0) 2022.05.25
[Spring] AOP는 어떻게 적용되는걸까?  (0) 2022.05.24
[Spring] @Aspect AOP  (0) 2022.05.23
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
글 보관함