[AOP] AOP 어노테이션, Pointcut 표현식

POKUDING·2024년 6월 20일
post-thumbnail

AOP 어노테이션

이전에 우리는 @Before 어노테이션을 사용하여 AOP에 대한 개념을 알아보았다. 이제 AOP의 다른 어노테이션들도 알아보자

  • @Before - 메소드가 불리면 선행으로 실행된다.
  • @After -메소드의 동작이 정상적으로 끝나거나 예외를 던져서 중단된 이후 실행된다.
  • @AfterReturning - 메소드가 정상적으로 끝났을 경우에만 실행된다.
  • @AfterThrowing - 메소드가 예외를 던지면 실행된다.
  • @Around - 메소드의 실행 전과 후의 동작을 정의할 수 있다.
  • @Pointcut - Pointcut을 관리할 수 있다.

이제 각 어노테이션의 사용법을 보자

@Before("execution(* com.junhyupa.learnspringaop.aopexample.*.*.*(..))")  
public void logMethodCallBefore(JoinPoint joinPoint) {  
    logger.info("Before Aspect - {} is called with arguments: {}", joinPoint, joinPoint.getArgs());  
}  
  
@After("execution(* com.junhyupa.learnspringaop.aopexample.*.*.*(..))")  
public void logMethodCallAfter(JoinPoint joinPoint) {  
    logger.info("After Aspect - {} has executed", joinPoint);  
}  
  
@AfterThrowing(pointcut = "execution(* com.junhyupa.learnspringaop.aopexample.*.*.*(..))",  
throwing = "exception")  
public void logMethodCallAfterException(JoinPoint joinPoint, Exception exception) {  
    logger.info("After Aspect - {} has thrown an exception ", joinPoint, exception);  
}  
  
@AfterReturning(pointcut = "execution(* com.junhyupa.learnspringaop.aopexample.*.*.*(..))",  
returning = "resultValue")  
public void logMethodCallAfterException(JoinPoint joinPoint, Object resultValue) {  
    logger.info("AfterReturning Aspect - {} has returned {} ", joinPoint, resultValue);  
}

@After

@Before는 이전 포스트와 동일한 코드이고 @After 또한 같은 방식으로 사용된다. 하지만 @AfterThrowing과@AfterReturning 은 사용방법이 약간 다르다.


@AfterThrowing

AfterThrowing은 pointcut을 지정해주고 던져진 exception을 받을 매개변수 이름을 지정해준다. 이후 동작을 하면 exception 변수로 pointcut에서 던져진 throw를 매개변수로 받아 활용할 수 있다.


@AfterReturning

AfterReturning 또한 AfterThrowing 과 유사하나 return 값을 받을 매개변수를 지정해준다. 이후 동작을 하면 Object 형식으로 메소드가 리턴한 값을 받아 볼 수 있다.


@Aspect  
@Configuration  
public class PerformanceTrackingAspect {  
  
    private Logger logger = LoggerFactory.getLogger(this.getClass());  
    @Around("com.junhyupa.learnspringaop.aopexample.aspects.CommonPointcutConfig.businessAndDataPackageConfig()")  
    public Object findExecutionTime(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {  
        //Start a timer  
        long startTimeMillis = System.currentTimeMillis();  
  
        //Execute the method  
        Object returnValue = proceedingJoinPoint.proceed();  
  
        //Stop the timer  
        long stopTimeMillis = System.currentTimeMillis();  
  
        long executionDuration = stopTimeMillis - startTimeMillis;  
  
        logger.info("Around Aspect - {} Method executed in {} ms", proceedingJoinPoint, executionDuration);  
  
        return returnValue;  
    }  
}

@Around

메소드의 실행 전과 후 의 작동을 정의할 수 있는 어노테이션이다. 다른 어노테이션과 다르게 ProceedingJoinPoint 를 매개변수로 받는데 이는 procced() 를 통해 메소드를 실행시키기 위함이다.


public class CommonPointcutConfig {  
  
    @Pointcut("execution(* com.junhyupa.learnspringaop.aopexample.*.*.*(..))")  
    public void businessAndDataPackageConfig() {  
  
    }
      
	@Pointcut("bean(*Service*)")  
	public void beanPackageConfig() {  
	}
}

@Pointcut

위 Around 어노테이션의 예제를 보면 Join Point의 표현식이 다른 예시들과 다르게 되어있다. 다른 예시들 처럼 일일히 Join Point를 입력해주면 메소드나 패키지 혹은 클래스 이름을 바꾸게 되면 모든 설정을 바꿔주어야 한다. 하지만 Pointcut 어노테이션을 이용하여 관리를 해주고 다른 어노테이션 안에는 Pointcut 이 적용된 메소드를 넣어주면 한 곳에서 관리를 해줄 수 있다.

또한 빈의 이름으로 관리해줄 수 있는데 예시와 같이 할 경우 이름에 Service 가 들어가는 모든 빈을 PointCut으로 관리할 수 있다.

커스텀 어노테이션 만들기

실행시간을 측정하는 Advice를 특정 어노테이션이 붙은 메소드마다 측정하고 싶으면 어떻게 해야할까?

  1. 우선 어노테이션을 만들어 준다.
//메소드에만 적용 가능  
//런타임에 사용  
@Target(ElementType.METHOD)  
@Retention(RetentionPolicy.RUNTIME)  
public @interface TrackTime {  
}
  1. 이 어노테이션이 붙은 메소드를 Pointcut으로 설정해준다.
@Pointcut("@annotation(com.junhyupa.learnspringaop.aopexample.annotations.TrackTime)")  
public void trackTimeAnnotationConfig() {}
  1. Advice의 어노테이션에 Pointcut 메소드를 넣어준다
@Around("com.junhyupa.learnspringaop.aopexample.aspects.CommonPointcutConfig.trackTimeAnnotationConfig()")  
public Object findExecutionTime(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {  
    //Start a timer  
    long startTimeMillis = System.currentTimeMillis();  
  
    //Execute the method  
    Object returnValue = proceedingJoinPoint.proceed();  
  
    //Stop the timer  
    long stopTimeMillis = System.currentTimeMillis();  
  
    long executionDuration = stopTimeMillis - startTimeMillis;  
  
    logger.info("Around Aspect - {} Method executed in {} ms", proceedingJoinPoint, executionDuration);  
  
    return returnValue;  
}

이제 @TrackTime 어노테이션이 붙어있는 모든 메소드들의 실행 시간이 로그에 찍히게 된다.


Pointcut 표현식

execution 명시자
- Advice를 적용할 메서드 지정

- 기본 형식 : 
 -> "*" 는 모든 값을 의미 
 -> ".."0개 이상 의미
 
execution([수식어] [리턴타입] [클래스이름] [이름]([파라미터])

수식어 
- 생략가능 
- public, protected 등등 

리턴타입 
- 메서드의 리턴타입 지정 

클래스이름, 이름 
- 클래스의 이름 및 메서드의 이름 지정 

파라미터 
- 메서드 파라미터 지정 

ex)

execution(* some.package.*.*())
- some.package 패키지 내
- 파라미터가 없는 모든 메서드 호출

execution(* some.package..*.*(..)) 
- some.package 패키지와 하위 패키지에 있는 
- 파라미터가 0개 이상인 모든 메서드 호출

execution(String some.package.SomeService.someMethod(..))
- 리턴 타입이 String
- some.package.SomeService 인터페이스 내
- 파라미터가 0개 이상인 someMethod 메서드 호출
 
execution(* some*(*)) 
- 메서드 이름이 some으로 시작되고, 
- 파라미터가 1개인 메서드 호출
 
execution(* some*(*, *))
- 메서드 이름이 some으로 시작되고
- 파라미터가 2개인 메서드 호출

execution(* some*(String, ..)) 
- 메서드 이름이 some으로 시작되고 
- 첫번째 파라미터 타입이 String 
- 파라미터가 1개 이상인 메서드 호출 
  
within 명시자 
- 특정 타입(Interface, Class)에 속하는 메서드를 Pointcut으로 지정 
ex) 
within(some.package.SomeService) 
- SomeService 인터페이스 내 모든 메서드 호출 
within(some.package.*) 
- some.package 패키지 내 모든 메서드 호출 
within(some.package..*) 
- some.package 패키지 및 하위 패키지 내 모든 메서드 호출 
 
bean 명시자 
- Spring 2.5부터 제공 
- 스프링 빈 이름을 이용하여 Pointcut 지정 
- 빈 이름의 패턴을 설정
ex) 
bean(someServiceBean) 
- 이름이 someServiceBean인 빈의 메서드 호출 
bean(*SomeService) 
- 이름이 SomeService로 끝나는 빈의 메서드 호출 

- 표현식에는 '&&''||' 연산자를 이용
-> 각각의 표현식을 연결 
-> and 연산과 or 연산 
-> and면 양쪽 표현식을 모두 만족하는 
-> or이면 양쪽 표현식중 어느 하나를 만족하는
ex)
@Before("execution(public !void get*(..)) || execution(public !void set*(..))")
public void someBeforeAdviceMethod(JoinPoint joinPoint){ 
// 대상 객체 메서드 실행전 수행할 공통기능 
} 

@After("execution(public !void get*(..)) && execution(public * get*(..))")
public void someAfterAdviceMethod(JoinPoint joinPoint){
// 대상 객체 메서드 실행후 수행할 공통기능
}
profile
세상을 바꾸는 꿈을 꾸는 개발자

0개의 댓글