본문 바로가기
부트캠프 개발일기/Spring

39일차: Spring Framework(Advice, JoinPoint)

by shyun00 2023. 4. 7.

❯ Advice

특정 조인포인트에서 수행되는 코드를 의미한다. 

어드바이스는 순서를 보장하지는 않으므로, 순서를 지정하고 싶다면 @Aspect 적용 단위별로 @Order 애너테이션을 적용해야한다.

@Aspect는 각 어드바이스 단위가 아니라 클래스에 적용하게 되므로, 하나의 Aspect에 여러개의 어드바이스가 존재하면 순서 보장이 안된다.

이럴 경우 해당 어드바이스를 별도의 클래스로 구분해 @Aspect로 설정하고, @Order 어노테이션을 적용해 순서를 지정할 수 있다.

@Aspect
@Order(1)
public class LoggingAspect {
    // Pointcut과 Advice 정의
    // ...
}

@Aspect
@Order(2)
public class SecurityAspect {
    // Pointcut과 Advice 정의
    // ...
}

위 코드에서 LoggingAspect가 SecurityAsepct보다 먼저 실행된다.

Advice의 종류

  • @Before : 조인포인트 메서드 실행 전에 Advice를 실행한다. 일반적으로 리턴타입이 void인 메서드에 적용된다. 만약 Before 어드바이스에서 예외가 발생하면, 타겟 메서드가 호출되지 않고 예외가 바로 발생한다.(다음 타겟이 자동으로 호출됨)
  • @After : 조인포인트 동작 완료에 관계 없이 Advice가 실행된다. 보통 리소스를 해제하거나 로그를 출력하는 등의 동작에 사용된다. 
  • @AfterReturning : 조인포인트 정상 완료 후 Advice를 실행한다.
  • @AfterThrowing : 조인포인트 실행 중 예외가 발생한 경우에 Advice를 실행한다. 예외를 로그에 남기거나 예외 메시지를 전달할 때 사용 가능하다.
  • @Around : 메서드 실행 전/후, 예외 발생시 등 모든 시점에서 로직을 수행할 수 있는 강력한 어드바이스이다.
          ProceedingJoinPoint 인터페이스를 이용하여 대상 메서드를 실행해야한다.
          (어드바이스 첫번째 파라미터가 ProceedingJoinPoint 이어야함)
    <Around 예시>
@Around("execution(* hello.aop.order.service.OrderService.get(..))")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
    long startTime = System.currentTimeMillis();

    Object proceed = joinPoint.proceed(); // 대상 메서드 실행

    long endTime = System.currentTimeMillis();
    log.info(joinPoint.getSignature() + " 실행시간: " + (endTime - startTime) + "ms");

    return proceed;
}

위 코드는 hello.aop.order.service.OrderService의 get메서드 실행시간을 측정하는 코드이다.

ProceedingJoinPoint 객체를 이용해서 OrderService의 get 메서드를 실행하고, 시작시간과 종료시간을 측정하여 계산한다.

joinPoint.proceed() 를 통해서 타겟 메서드를 실행할 수 있다. 이 메서드가 없다면 조인포인트 메서드는 실행되지 않는다.

proceed()는 여러번 실행할수도 있다. 이처럼 동작을 제어할 수 있으므로 Around를 사용해 추가적인 작업을 할 수 있다.

❯ Pointcut 표현식

포인트컷은 어드바이스가 실행되는 조인포인트를 결정한다. 

스프링 프레임워크에서는 AspectJ 스타일의 포인트컷 표현식을 사용하며, 메서드(어드바이스) 호출 시점을 지정하는데 사용된다.

포인트컷 지시자로 시작되며 대표적으로 execution 를 사용한다.

더보기

<포인트컷 지시자>

스프링 AOP에서는 execution, within, this, target, args, @target, @args, @within, @annotation 등의 다양한 포인트컷 지시자를 제공합니다.

execution은 메서드 실행 지점에 대한 포인트컷을 정의합니다. 메서드 시그니처를 기반으로 메서드의 접근 제어자, 리턴 타입, 메서드 이름, 파라미터 타입 및 갯수 등의 정보를 조합하여 표현식을 작성할 수 있습니다.

within은 클래스나 패키지를 기반으로 포인트컷을 정의합니다. within(com.example.myapp.service.*)와 같이 패키지 경로를 지정하여 해당 패키지 내부의 모든 클래스에 적용할 수 있습니다.

this는 프록시 객체의 런타임 타입이 특정 인터페이스를 구현한 경우를 포인트컷으로 정의합니다. this(com.example.myapp.service.MyService)와 같이 프록시 객체의 런타임 타입이 MyService 인터페이스를 구현한 경우에만 어드바이스를 실행합니다.

target은 프록시 객체가 감싸고 있는 타겟 객체에 대한 포인트컷을 정의합니다. target(com.example.myapp.service.MyServiceImpl)와 같이 타겟 객체의 타입이 MyServiceImpl인 경우에만 어드바이스를 실행합니다.

args는 메서드 파라미터의 타입을 기반으로 포인트컷을 정의합니다. args(java.lang.String)와 같이 메서드 파라미터 타입이 String인 경우에만 어드바이스를 실행합니다.

@target, @args, @within, @annotation 등은 어노테이션 정보를 기반으로 포인트컷을 정의합니다. 예를 들어 @target(com.example.myapp.annotation.MyAnnotation)와 같이 프록시 대상 객체가 @MyAnnotation 어노테이션을 가지고 있는 경우에만 어드바이스를 실행합니다.

이러한 포인트컷 지시자들을 조합하여 보다 복잡하고 정교한 포인트컷 표현식을 만들 수 있습니다.

 JoinPoint

AOP를 수행하는 메서드는 JoinPoint 인스턴스를 인자로 받아 조인포인트 지점 정보를 얻게된다.

* JoinPoint는 org.aspectj.lang.JoinPoint 인터페이스를 구현한 인스턴스이다.

프록시 방식을 사용하는 스프링 AOP는 메서드 실행지점에서만  AOP를 적용할 수 있다.

AspectJ를 사용하는 AOP는 바이트코드를 실제 조작하므로 해당 기능을 모든 지점에서 사용할 수 있다.