≣ 목차
모든 예제에서 스프링 컨테이너에 빈을 저정하는 코드는 따로 없습니다.ㅎㅎㅎ!!
기본 예제
@Aspect
@Slf4j
public class AspectV1 {
@Around("execution(* hello.aop.order..*(..))")
public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("AspectV1.doLog={}", joinPoint.getSignature());
return joinPoint.proceed();
}
}
포인트컷은 메소드나 클래스에 적용할 부가기능을 결정하는 애노테이션입니다. 이때 메소드 명, JDK 정규 표현식, aspectJ 표현식 등으로 조건을 표현할 수 있습니다.
추가적으로 @Around, @Before, @After 등등 다양한 애노테이션으로 사용할 수 있습니다. 하지만 @Pointcut과 동작 방식이 다르므로 적절한 방법을 선택하여 사용해야 합니다. 그리고 @Around에만 ProceedingJoinPoint만 사용할 수 있고 나머지 @Pointcut 관련 애노테이션들은 JoinPoint를 사용해야 합니다.
포인트 컷 메소드화
@Aspect
@Slf4j
public class AspectV2 {
@Pointcut("execution(* hello.aop.order..*(..))")
private void allOrder() {}// pointcut signature
@Around("allOrder()")
public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("AspectV2.doLog={}", joinPoint.getSignature());
return joinPoint.proceed();
}
}
포인트컷을 메소드화 할 때는 사용 용도에 따라 접근 제어자를 public, private를 사용할 수 있습니다.
포인트컷 메소드 모듈화
@Pointcut 관련 메소드를 모듈화하여 다른 클래스에 작성할 수 있습니다.
@Aspect
@Slf4j // 포인트 컷 관련 메소드 모듈화
public class AspectV4Pointcut {
@Around("hello.aop.order.aop.Pointcuts.allOrder()")
public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("AspectV4.doLog={}", joinPoint.getSignature());
return joinPoint.proceed();
}
}
public class Pointcuts {
@Pointcut("execution(* hello.aop.order..*(..))")
public void allOrder() {}
@Pointcut("execution(* *..*Service.*(..))")
public void allService() {
}
@Pointcut("allOrder() && allService()")
public void orderAndService() {
}
}
다른 클래스에 있는 @Pointcut 메소드를 사용하려면 패키지 절대 경로 + 메소드 명을 작성하면 됩니다.
어드바이스 순서 변경 - 내부 클래스 생성
@Aspect
@Slf4j // 어드바이스 순서 변경
public class AspectV5Order {
@Aspect
@Order(2)
public static class LogAspect {
@Around("hello.aop.order.aop.Pointcuts.allOrder()")
public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("AspectV5.doLog={}", joinPoint.getSignature());
return joinPoint.proceed();
}
}
@Aspect
@Order(1)
public static class TxAspect {
@Around("hello.aop.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());
}
}
}
}
@Order 애노테이션을 메소드에 작성하면 원하는 결과를 도출할 수 있지 않을까 라고 생각할 수 있지만 @Order 애노테이션은 클래스 단위로 적용되므로, 메소드에 작성해도 원하는 결과를 얻을 수 없습니다. 순서를 변경하고 싶다면 어드바이스를 클래스화해야 합니다.
클래스화하는 방법에는 내부 클래스를 사용하는 방법과 별도의 클래스를 생성하는 방법이 있습니다. 클래스를 세분화했기 때문에, 스프링 빈을 등록할 때 세분화한 클래스를 등록해야 합니다.
어드바이스에서 전달 받은 매개변수 꺼내오기
this, target, args,@target, @within, @annotation, @args를 사용해서 전달 받은 매개변수를 어드바이스에서 확인할 수 있습니다.
// ProceedingJoinPoint를 이용한 방식
@Around("allMember()")
private Object logArgs1(ProceedingJoinPoint joinPoint) throws Throwable {
Object arg = joinPoint.getArgs()[0];
log.info("[logArgs1]{}, arg={}", joinPoint.getSignature(), arg);
return joinPoint.proceed();
}
// args를 이용한 방식
@Around("allMember() && args(arg,..)")
public Object logArgs2(ProceedingJoinPoint joinPoint, Object arg) throws Throwable {
log.info("[logArgs2]{}, arg={}", joinPoint.getSignature(), arg);
return joinPoint.proceed();
}
// this를 이용한 방식
@Before("allMember() && this(obj)")
public void thisArgs(JoinPoint joinPoint, MemberService obj) {
log.info("[this]{}, obj={}", joinPoint.getSignature(), obj.getClass());
}
// target를 이용한 방식
@Before("allMember() && target(obj)")
public void targetArgs(JoinPoint joinPoint, MemberService obj) {
log.info("[target]{}, obj={}", joinPoint.getSignature(), obj.getClass());
}
// @target를 이용한 방식
@Before("allMember() && @target(annotation)")
public void atTarget(JoinPoint joinPoint, ClassAop annotation) {
log.info("[@target]{}, obj={}", joinPoint.getSignature(), annotation);
}
// @within를 이용한 방식
@Before("allMember() && @within(annotation)")
public void atWithin(JoinPoint joinPoint, ClassAop annotation) {
log.info("[@within]{}, obj={}", joinPoint.getSignature(), annotation);
}
// @annotation를 이용한 방식, annotation에 있는 멤버 변수의 값을 가져옴
@Before("allMember() && @annotation(annotation)")
public void atAnnotation(JoinPoint joinPoint, MethodeAop annotation) {
log.info("[@annotation]{}, annotationValue={}", joinPoint.getSignature(), annotation.value());
}
사용 시 주의사항
- 포인트컷에서 사용한 이름과 매개변수의 이름이 같아야 합니다.
- 지시자 종류에 따라서 타입이 메서드에 지정한 타입으로 제한될 수 있고 부모 타입을 사용할 수 있습니다.
참고
[1] - 스프링 핵심 원리 고급편 - 김영한
'Spring' 카테고리의 다른 글
[Spring] Spring AOP 사용 시 내부 호출 문제 해결 방법 (0) | 2024.10.03 |
---|---|
[Spring] 스프링 포인트컷 지시자 정리 (0) | 2024.10.01 |
[Spring] 스프링 AOP 정리 (0) | 2024.09.26 |
[Spring] @Aspect 정리 (0) | 2024.09.26 |
[Spring] 빈 후처리기 (0) | 2024.09.24 |