[응용] 11. 포인트컷

kiwonkim·2021년 12월 3일
0
post-custom-banner

[ 이전 포스팅 ]

@Aspect 를 통해서 스프링 AOP 를 적용해보았다. 또한 포인트컷을 시그니처로 분리하거나, 어드바이스의 순서를 설정하는 방식에 대해 알아보고 여러 어드바이스에 대해서도 확인하였다.

이 때 포인트컷 설정시 포인트컷 표현식을 사용하였는데, 이에대해 알아보자.


[ 포인트컷 지시자 ]

AspectJ 는 포인트컷을 편리하게 표현하기 위한 표현식을 제공하며, 스프링도 이를 차용한다.

// 예시
@Pointcut("execution(* hello.aop.order..*(..))")

포인트컷 표현식은 exection 같은 포인트컷 지시자로 시작한다. 포인트컷 지시자는 execution, within, args 등이 존재하는데 지시자마자 매칭시 확인하는 정보가 다르다.


[ exeuction ]

execution 이란

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 은 파라미터의 개수나 타입으로 매칭을 수행할 수도 있다. ..를 사용하면 개수와 타입에 무관하게 매칭된다.


[ 다른 지시자들 ]

within

@Pointcut("within(hello.aop.member.memberSerivce)")

wihtin 은 클래스명으로만 매칭을 수행한다. execution 의 클래스명 매칭과는 다른점은 부모클래스명을 입력해도 자식클래스는 매칭이 되지 않는다.

args

@Pointcut("args(String, ..)")

args 는 파라미터 타입으로만 매칭을 수행한다. execution 의 파라미터 매칭과 다른점은 부모타입을 입력하면 자식타입의 파라미터도 매칭된다.

@target

@Pointcut("@target(hello.aop.member.annotation.ClassAop)")

@target 은 클래스의 어노테이션으로 매칭을 수행한다. 예제에서 @ClassAop 어노테이션이 추가된 클래스의 모든 메서드를 매칭한다. 이때 자식클래스에 해당 어노테이션이 추가되어있으면 부모클래스도 함께 매칭시킨다.

@within

@Pointcut("@within(hello.aop.member.annotation.ClassAop)")

@within 도 @target 처럼 클래스의 어노테이션으로 매칭을 수행하는데, 자식클래스에 해당 어노테이션이 있어도 부모클래스는 매칭시키지 않는다.

@annotation

@Pointcut("@annotation(hello.aop.member.annotation.MethodAop)")

@annotation 은 메서드에 해당 어노테이션 있는 경우 매칭한다.

@args

@Pointcut("@args(hello.aop.member.annotation.ParamAop)")

@args 는 메서드의 파라미터에 해당 어노테이션이 있는 경우 매칭한다.

bean

@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, target ]

this(hello.aop.member.MemberService)
target(hello.aop.member.MemberService)

this 는 스프링빈 객체. 즉 프록시객체가 해당 타입의 부모나 자식일 때 매칭되고.
target 은 실제객체가 해당 타입의 부모나 자식일 때 매칭된다.

target 은 실제객체로 매칭을 수행하므로 문제가 없는데, 프록시객체로 매칭을 수행하는 this 은 JDK 동적프록시와 CGLIB 방식에서 다른 결과를 낳는다.

JDK 동적 프록시

  1. this(memberSerivce)

JDK 동적프록시에서 memberServiceImpl 의 프록시객체는 JDK Proxy 이다. 그리고 JDK Proxy 는 memberService 의 자식이므로 JDK Proxy는 매칭이 수행된다.

  1. this(memberServiceImpl)

JDK 동적프록시에서 memberServiceImpl 의 프록시객체는 JDK Proxy 이다. 그런데 JDK Proxy 와 memberServiceImpl 은 부모자식 관계가 아니다. 따라서 JDK Proxy 는 매칭이 되지 않아 어드바이스가 적용되지 않는다.

CGLIB

  1. this(memberSerivce)

CGLIB 에서 memberServiceImpl 의 프록시객체는 CGLIB Proxy 이다. 그리고 CGLIB Proxy 는 memberService 의 자식의 자식이므로 CGLIB Proxy 는 매칭이 수행된다.

  1. this(memberServiceImpl)

CGLIB 에서 memberServiceImpl 의 프록시객체는 CGLIB Proxy 이다. 그런데 CGLIB Proxy 와 memberServiceImpl 은 부모자식 관계이다. 따라서 CGLIB Proxy 는 매칭이 수행된다.

즉, 프록시객체를 기준으로 판별하는 this 에서. 타겟객체와 프록시객체가 부모자식 관계가 아닌 JDK 동적 프록시에서. this(타겟객체) 를 하면 매칭이 되지 않는다.


[ 결론 ]

포인트컷을 생성할때는 포인트컷 표현식을 사용한다.
반환형, 패키지명.클래스명.메서드명 파라미터 로 매칭을 수행하는 execution 지시자를 가장 많이 사용한다.
* 값이 무관할 경우, .. 는 개수와 값이 무관할 경우 사용한다.
표현식에서 args(arg) 로 advice 에게 실제객체 메서드의 파라미터를 전달할 수 있다.

post-custom-banner

0개의 댓글