POJO 구성 방식
14. JoinPoint 정보 가져오기
AOP 에서 어드바이스는 여러 조인포인트, 즉 프로그램 실행지점 곳곳에 적용된다. 어드바이스가 정확하게 작동하려면 조인포인트에 대한 세부 정보가 필요한 경우가 존재한다.
어드바이스 메소드의 Signature 에 org.aspectj.lang.JoinPoint 형 인수를 선언하면 조인포인트 정보를 얻을 수 있다. 다음과 같이 logJoinPoint 어드바이스에서 조인포인트 정보를 액세스한다고 하자. 필요한 정보는 조인포인트 유형, 메소드 시그니처, 인수값, 대상 객체와 프록시 객체이다.
@Aspect
@Component
public class CalculatorLoggingAspect {
private Logger log = LoggerFactory.getLogger(this.getClass());
@Before("execution(* *.*(..))")
public void logJoinPoint(JoinPoint joinPoint) {
log.info("Join point kind : {}", joinPoint.getKind());
log.info("Signature declaring type : {}", joinPoint.getSignature().getDeclaringTypeName());
log.info("Signature name : {}", joinPoint.getSignature().getName());
log.info("Arguments : {}", Arrays.toString(joinPoint.getArgs()));
log.info("Target class : {}", joinPoint.getTarget().getClass().getName());
log.info("This class : {}", joinPoint.getThis().getClass().getName());
}
}
프록시로 감싼 원본 빈은 Target Object 라고 하며 프록시 객체는 this 로 참조한다. 대상 객체와 프록시 객체는 각각 조인포인트에서 getTarget(), getThis() 메소드로 가져올 수 있다. 실행 결과는 다음과 같다.
Join point kind : method-execution
Signature declaring type : com.apress.springrecipes.calculator.ArithmeticCalculator
Signature name : add
Arguments : [1.0, 2.0]
Target class : com.apress.springrecipes.calculator.ArithmeticCalculatorImpl
This class : com.sun.proxy.$Proxy18
15. @Order 로 애스펙트 우선순위 설정
같은 조인포인트에 애스펙트를 여러 개 적용할 경우, 우선순위를 정해야 한다. 애스펙트 간 우선순위는 Ordered 인터페이스를 구현하거나 @Order 어노테이션을 붙여 지정할 수 있다.
@Aspect
@Component
public class CalculatorVaildationAspect {
@Before("execution(* *.*(double, double))")
public void validateBefore(JoinPoint joinPoint) {
for (Object arg : joinPoint.getArgs()) {
validate((Double) arg);
}
}
private void validate(double a) {
if (a < 0) {
throw new IllegalArgumentException("Positive Numbers only");
}
}
}
@Aspect
@Component
public class CalculatorLoggingAspect {
@Before("execution(* *.*(..))")
public void logBefore(JoinPoint joinPoint) {
// ...
}
}
위와 같이 두 애스펙트가 존재할 경우 어느 쪽을 먼저 적용해야 할 지 알 수가 없다. 이런 경우 어느 한 애스팩트가 다른 것보다 먼저 실행되게 하기 위해 우선순위를 설정해야 한다. 두 애스펙트 모두 Ordered 인터페이스를 구현하거나 @Order 어노테이션을 활용하면 된다.
먼저 Ordered 인터페이스를 구현할 경우, getOrder() 메소드가 반환하는 값이 작을수록 우선순위가 높다. 다음과 같이 작성할 경우 검증 애스펙트가 로깅 애스펙트보다 우선순위가 더 높아진다.
@Aspect
@Component
public class CalculatorVaildationAspect implements Ordered {
...
public int getOrder() {
return 0;
}
}
@Aspect
@Component
public class CalculatorLoggingAspect implements Ordered {
...
public int getOrder() {
return 1;
}
}
아래와 같이 @Order 에 우선순위값을 넣으면 더 깔끔하게 구현이 가능하다.
@Aspect
@Component
@Order(0)
public class CalculatorVaildationAspect { ... }
@Aspect
@Component
@Order(1)
public class CalculatorLoggingAspect { ... }
16. 애스펙트 포인트컷 재사용
포인트컷 표현식을 여러 번 되풀이해 사용할 경우에, 어드바이스 어노테이션에 직접 써넣는 것보다 재사용할 방법을 필요로 하게 된다. @Pointcut 어노테이션을 활용하면 포인트컷만 따로 정의해 여러 어드바이스에서 재사용이 가능하다.
애스펙트에서 포인트컷은 @Pointcut 을 붙인 단순 메소드로 선언할 수 있다. 포인트컷과 어플리케이션 로직이 뒤섞이는 것은 바람직하지 않으니 메소드 바디는 보통 비워두고 포인트컷의 가시성은 메소드의 수정자로 조정한다. 이렇게 선언한 포인트컷은 다른 어드바이스가 메소드명으로 참조할 수 있다.
@Aspect
@Component
public class CalculatorLogginAspect {
...
@Pointcut("execution(* *.*(..))")
private void loggingOperation() {}
@Before("loggingOperation()")
public void logBefore() { ... }
@After("loggingOperation()")
public void logAfter() { ... }
}
여러 애스펙트가 포인트컷을 공유하는 경우라면 공통 클래스 한 곳에 포인트컷을 모아두는 편이 좋다. 이 때 포인트컷 메소드는 public 으로 선언한다.
@Aspect
public class CalculatorPointcuts {
@Pointcut("execution(* *.*(..))")
public void loggingOperation() {}
}
외부 클래스에 있는 포인트컷을 참조할 때는 클래스명도 함께 적는다. 만약 다른 패키지에 있는 경우는 패키지명까지 기재한다.
@Aspect
@Component
public class CalculatorLoggingAspect {
@Before("CalculatorPointcuts.loggingOperation()")
public void logBefore() { ... }
@After("CalculatorPointcuts.loggingOperation()")
public void logAfter() { ... }
}
#Reference.
'Study > Spring' 카테고리의 다른 글
[Spring 5 Recipes] Spring 5 Recipes 2장 정리 #8 (0) | 2021.09.28 |
---|---|
[Spring 5 Recipes] Spring 5 Recipes 2장 정리 #7 (0) | 2021.09.27 |
[Spring] AOP 란? (0) | 2021.09.24 |
[Spring 5 Recipes] Spring 5 Recipes 2장 정리 #5 (0) | 2021.09.23 |
[Spring 5 Recipes] Spring 5 Recipes 2장 정리 #4 (0) | 2021.09.21 |