@Aspect 를 통해서 스프링 AOP 를 적용해보았다. 또한 포인트컷을 시그니처로 분리하거나, 어드바이스의 순서를 설정하는 방식에 대해 알아보고 여러 어드바이스에 대해서도 확인하였다.
이 때 포인트컷 설정시 포인트컷 표현식을 사용하였는데, 이에대해 알아보자.
AspectJ 는 포인트컷을 편리하게 표현하기 위한 표현식을 제공하며, 스프링도 이를 차용한다.
// 예시
@Pointcut("execution(* hello.aop.order..*(..))")
포인트컷 표현식은 exection 같은 포인트컷 지시자로 시작한다. 포인트컷 지시자는 execution, within, args 등이 존재하는데 지시자마자 매칭시 확인하는 정보가 다르다.
execution(접근제어자? 반환타입 선언타입?메서드이름(파라미터) 예외?)
execution 은 메서드의 접근제어자, 반환타입, 선언타입(패키지+클래스), 메서드이름, 파라미터타입 등으로 매칭을 수행하는 지시자이다.
접근제어자와 선언타입 예외는 생략이 가능하다.
* 와 .. 를 사용할 수 있다. * 는 아무값이나 상관없다는 뜻이며, .. 는 개수와 값이 상관없다는 뜻이다.
// 메서드명 hello 매칭
@Pointcut("execution(* hello(..))")
// 메서드명 hel 로 시작 매칭
@Pointcut("execution(* hel*(..))")
// 메서드명 사이 el 있을시 매칭
@Pointcut("execution(* *el*(..))")
반환타입, 메서드명, 파라미터는 필수이다. 반환타입은 * 로 무관. 파라미터도 .. 로 개수,타입 무관이므로 메서드명으로만 매칭한다. 메서드명 앞뒤에 *를 사용할 수 있다.
// 패키지.클래스.메서드명 매칭
@Pointcut("execution(* hello.aop.member.MemberService.hello(..))")
// member 패키지내의 모든메서드 매칭
@Pointcut("execution(* hello.aop.member.*.*(..))")
// member 패키지&하위패키지의 모든메서드 매칭
@Pointcut("execution(* hello.aop.member..*.*(..))")
메서드명 앞에 선언타입 매칭을 통해 패키지명 매칭이 가능하다. 이 때 .를 사용하면 해당 패키지내의 클래스와 메서드만 적용하며. ..를 사용하면 해당패키지와 하위패키지를 모두 적용한다.
// 클래스명매칭. 자식도 매칭됨
@Pointcut("execution(* hello.aop.member.MemberServiceInterface.hello(..))")
클래스명으로 매칭을 수행할수 있다. MemberServiceInterface 에 매칭을 수행하면, 그 자식인 MemberService 도 매칭된다. 다만 명시한 hello 라는 메서드가 자식에도 존재해야 해당 메서드에 매칭된다.
// 파라미터 1개 타입 매칭
@Pointcut("execution(* *(String))")
// 파라미터X 매칭
@Pointcut("execution(* *())")
// 파라미터 무관 매칭
@Pointcut("execution(* *(..))")
// 숫자무관 첫번째 파라미터 타입 매칭
@Pointcut("execution(* *(String, ..))")
execution 은 파라미터의 개수나 타입으로 매칭을 수행할 수도 있다. ..를 사용하면 개수와 타입에 무관하게 매칭된다.
@Pointcut("within(hello.aop.member.memberSerivce)")
wihtin 은 클래스명으로만 매칭을 수행한다. execution 의 클래스명 매칭과는 다른점은 부모클래스명을 입력해도 자식클래스는 매칭이 되지 않는다.
@Pointcut("args(String, ..)")
args 는 파라미터 타입으로만 매칭을 수행한다. execution 의 파라미터 매칭과 다른점은 부모타입을 입력하면 자식타입의 파라미터도 매칭된다.
@Pointcut("@target(hello.aop.member.annotation.ClassAop)")
@target 은 클래스의 어노테이션으로 매칭을 수행한다. 예제에서 @ClassAop 어노테이션이 추가된 클래스의 모든 메서드를 매칭한다. 이때 자식클래스에 해당 어노테이션이 추가되어있으면 부모클래스도 함께 매칭시킨다.
@Pointcut("@within(hello.aop.member.annotation.ClassAop)")
@within 도 @target 처럼 클래스의 어노테이션으로 매칭을 수행하는데, 자식클래스에 해당 어노테이션이 있어도 부모클래스는 매칭시키지 않는다.
@Pointcut("@annotation(hello.aop.member.annotation.MethodAop)")
@annotation 은 메서드에 해당 어노테이션 있는 경우 매칭한다.
@Pointcut("@args(hello.aop.member.annotation.ParamAop)")
@args 는 메서드의 파라미터에 해당 어노테이션이 있는 경우 매칭한다.
@Pointcut("bean(orderService) || bean(orderRepository)")
bean 은 스프링 전용 포인트컷 지시자로서 빈 이름으로 매칭을 수행한다. 해당 빈의 모든 메서드가 매칭된다.
참고로 포인트컷 지시자는 && || ! 같은 논리연산자를 사용할 수 있다.
포인트컷 표현식을 통해 실제메서드의 매개변수를 어드바이스로 전달할 수 있다.
// 타겟 메서드 첫번째 파라미터 전달
@Around("allMember() && args(arg,..)")
public Object args(ProceedingJoinPoint joinPoint, Object arg)
// 프록시객체 전달
@Around("allMember() && this(obj)")
public Object args(ProceedingJoinPoint joinPoint, Object obj)
// 타겟객체 전달
@Around("allMember() && target(obj)")
public Object args(ProceedingJoinPoint joinPoint, Object obj)
// 타겟객체 메서드 어노테이션 전달.
@Around("allMember() && @annotation(annotation)")
public Object args(ProceedingJoinPoint joinPoint, MethodAop annotation)
포인트컷을 바탕으로 스프링빈 객체들의 모든 메서드들을 뒤져 매칭을 수행하므로. 매칭되면 해당 객체와 메서드 정보를 알고 있다. 따라서 이를 advice 에 넘겨줄 수 있는 것이다.
args 에 타입명이 아닌 변수를 입력하면 매칭이 아닌 매개변수 전달에 활용한다. 표현식의 변수명과 advice 파라미터 변수명을 일치시켜야한다.
this 로 프록시객체를 전달받거나 target 으로 타겟객체를 전달받을 수 있다.
@annotation 으로 메서드의 어노테이션을 전달받을 수 있다. advice는 annotation.value() 로 어노테이션의 값도 사용할 수 있다.
this(hello.aop.member.MemberService)
target(hello.aop.member.MemberService)
this 는 스프링빈 객체. 즉 프록시객체가 해당 타입의 부모나 자식일 때 매칭되고.
target 은 실제객체가 해당 타입의 부모나 자식일 때 매칭된다.
target 은 실제객체로 매칭을 수행하므로 문제가 없는데, 프록시객체로 매칭을 수행하는 this 은 JDK 동적프록시와 CGLIB 방식에서 다른 결과를 낳는다.
JDK 동적 프록시
JDK 동적프록시에서 memberServiceImpl 의 프록시객체는 JDK Proxy 이다. 그리고 JDK Proxy 는 memberService 의 자식이므로 JDK Proxy는 매칭이 수행된다.
JDK 동적프록시에서 memberServiceImpl 의 프록시객체는 JDK Proxy 이다. 그런데 JDK Proxy 와 memberServiceImpl 은 부모자식 관계가 아니다. 따라서 JDK Proxy 는 매칭이 되지 않아 어드바이스가 적용되지 않는다.
CGLIB
CGLIB 에서 memberServiceImpl 의 프록시객체는 CGLIB Proxy 이다. 그리고 CGLIB Proxy 는 memberService 의 자식의 자식이므로 CGLIB Proxy 는 매칭이 수행된다.
CGLIB 에서 memberServiceImpl 의 프록시객체는 CGLIB Proxy 이다. 그런데 CGLIB Proxy 와 memberServiceImpl 은 부모자식 관계이다. 따라서 CGLIB Proxy 는 매칭이 수행된다.
즉, 프록시객체를 기준으로 판별하는 this 에서. 타겟객체와 프록시객체가 부모자식 관계가 아닌 JDK 동적 프록시에서. this(타겟객체) 를 하면 매칭이 되지 않는다.
포인트컷을 생성할때는 포인트컷 표현식을 사용한다.
반환형, 패키지명.클래스명.메서드명 파라미터 로 매칭을 수행하는 execution 지시자를 가장 많이 사용한다.
* 값이 무관할 경우, .. 는 개수와 값이 무관할 경우 사용한다.
표현식에서 args(arg) 로 advice 에게 실제객체 메서드의 파라미터를 전달할 수 있다.