Search
Duplicate
📒

[Spring Study] 09-7. Pointcut 어노테이션 종류

상태
수정중
수업
Spring Study
주제
AOP
4 more properties
참고

포인트컷 지시자 종류

NOTE
포인트컷 표현식execution같은 포인트컷 지시자(Pointcut Designator) 줄여서 PCD라고 한다.
AspectJExpressionPointcut은 포인트컷 표현식을 처리해주는 클래스! (자세히 보면 위에 Pointcut을 받음)
execution (사실 이것만 알아도 됨!)
메소드 실행 조인 포인트를 매칭한다.
스프링 AOP에서 가장 많이 사용하고, 기능도 복잡하다.
within
exectuion에서 타입 부분만 사용한다고 보면된다.
args
인자가 주어진 타입의 인스턴스인 조인 포인트로 매칭한다.
this
스프링 빈 객체(스프링 AOP 프록시)를 대상으로 하는 조인 포인트
target
Target 객체(스프링 AOP 프록시가 가르키는 실제 대상)을 대상으로하는 조인 포인트
@target
실행 객체의 클래스에 주어진 타입의 애노테이션이 있는 조인 포인트
@within
주어진 애노테이션이 있는 타입 내 조인 포인트
@annotaion
메서드가 주어진 애노테이션을 가지고 있는 조인포인트를 매칭
@args
전달된 실제 인수의 런타임 타입이 주어진 타입의 애노테이션을 갖는 조인 포인트
bean
스프링 전용 포인트컷 지시자
빈의 이름으로 포인트컷을 지정한다.
여러 지시자중, execution을 가장 많이 사용하고, 나머지는 잘 사용하지 않으니 execution을 중점적으로 학습하자!

AspectJ Pointcut 표현식

NOTE
AspectJExpressionPointcut을 사용하면, 정밀한 Pointcut을 설정할 수 있습니다.
// A만 적용됩니다. @Bean public Advisor timeAdvisor(TimeAdvice timeAdvice) { AspectJExpressionPointcut aspectJPointcut = new AspectJExpressionPointcut(); aspectJPointcut.setExpression("within(com.example.study.aop.AutoProxyConfig.A)"); return new DefaultPointcutAdvisor(aspectJPointcut, timeAdvice); } // A,B 둘다 적용됩니다. @Bean public Advisor timeAdvisor2(TimeAdvice timeAdvice) { AspectJExpressionPointcut aspectJPointcut = new AspectJExpressionPointcut(); aspectJPointcut.setExpression("execution(* com.example.study.aop.AutoProxyConfig..*(..))"); return new DefaultPointcutAdvisor(aspectJPointcut, timeAdvice); } // A,B 둘다 적용되지 않습니다. @Bean public Advisor timeAdvisor3(TimeAdvice timeAdvice) { AspectJExpressionPointcut aspectJPointcut = new AspectJExpressionPointcut(); aspectJPointcut.setExpression("execution(* hello.proxy.app.test..*(..))"); return new DefaultPointcutAdvisor(aspectJPointcut, timeAdvice); }
Java
복사

AspectJ Pointcut 표현식

execution: 메서드 실행(join point)에 대한 매칭을 정의한다.
modifiers-pattern: 메서드의 접근 제어자(public, private 등)
ret-type-pattern: 메서드의 반환 타입
declaring-type-pattern: 메서드를 선언한 타입(클래스)
name-pattern: 메서드 이름 패턴
param-pattern: 메서드의 파라미터 패턴
throws-pattern: 메서드가 던질 수 있는 예외
within: 특정 타입에 대한 매칭을 정의한다.

execution

NOTE
execution(접근제어자? 반환타입 선언타입?메서드이름(파라미터) 예외?)
Java
복사
가장 기본적인 형태!
메소드 실행 조인 포인트를 매칭한다.
?는 생략할 수 있다.
*같은 패턴을 지정할 수 있다.
// 가장 정확하게 매칭하는 방법 @Test void exactMatch() { pointcut.setExpression("execution(public String hello.aop.member.MemberServiceImpl.hello(String))"); Assertions.assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue(); } // 가장 생략한 방법 @Test void allMatch() { pointcut.setExpression("execution(* *(..))"); Assertions.assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue(); }
Java
복사
위의 경우는 1개의 메소드만, 아래의 경우는 모든 메소드가 검색된다.
매칭 조건
접근 제어자? : public
반환타입 : String
선언타입?: hello.aop.member.MemberServiceImpl
메서드: hello
파라미터: (String)
예외?: 생략
// 메서드 이름패턴으로 조회 @Test void nameMatchStar1() { pointcut.setExpression("execution(* hel*(..))"); Assertions.assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue(); } // 메서드 이름패턴으로 조회 @Test void nameMatchStar2() { pointcut.setExpression("execution(* *el*(..))"); Assertions.assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue(); }
Java
복사
메서드 이름의 경우 *를 사용해서 패턴으로 조회가 가능하다.
// 패키지 이름패턴으로 조회 @Test void packageExactMatch2() { pointcut.setExpression("execution(* hello.aop.member.*.*(..))"); Assertions.assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue(); } // 패키지 이름패턴으로 조회 @Test void packageExactSubPackage2() { pointcut.setExpression("execution(* hello.aop..*.*(..))"); Assertions.assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue(); }
Java
복사
패키지의 경우는 .과 ..을 통해서 조회가 가능하다.
. : 정확하게 해당 위치의 패키지
.. : 해당 위치의 패키지와 그 하위 패키지도 포함한다.
// 타입 조회 @Test void typeExactMatch() { pointcut.setExpression("execution(* hello.aop.member.MemberServiceImpl.*(..))"); Assertions.assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue(); } // 부모타입 조회 @Test void typeExactMatchSuperType() { pointcut.setExpression("execution(* hello.aop.member.MemberService.*(..))"); Assertions.assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue(); }
Java
복사
execution의 경우 클래스의 부모타입도 검색이 가능하다!
// String 타입의 파라미터 허용 // (String) @Test void argsMatch() { pointcut.setExpression("execution(* *(String))"); Assertions.assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue(); } // 파라미터가 없는 경우 // () @Test void argsMatchNoArgs() { pointcut.setExpression("execution(* *())"); Assertions.assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isFalse(); } // 정확하게 하나의 파라미터 허용, 모든 타입 허용 // (XXX) @Test void argsMatchStar() { pointcut.setExpression("execution(* *(*))"); Assertions.assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue(); } // 모든 파라미터 타입과 개수 허용 // (), (XXX), (XXX, XXX) @Test void argsMatchAll() { pointcut.setExpression("execution(* *(..))"); Assertions.assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue(); }
Java
복사
파라미터를 사용하는 경우

within

NOTE
execution에서 타입 부분만 사용한다. (별로 안씀)
@Test void withinExact() { pointcut.setExpression("within(hello.aop.member.MemberServiceImpl)"); Assertions.assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue(); } // 클래스 패턴매칭 @Test void withinStar() { pointcut.setExpression("within(hello.aop.member.*Service*"); Assertions.assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue(); }
Java
복사
그냥 참고만 하자 (잘안씀)

args

NOTE
execution에서 인자값만 사용한다. (별로 안씀)
// exection -> 메서드의 시그니처로 판단 (정적) // args -> 런타임에 전달된 인수로 판단 (동적) @Test void argsVsExecution(){ // Args Assertions.assertThat(pointcut("args(String)") .matches(helloMethod, MemberServiceImpl.class)).isTrue(); Assertions.assertThat(pointcut("args(java.io.Serializable)") // Serializable은 String의 부모타입중 하나 .matches(helloMethod, MemberServiceImpl.class)).isTrue(); Assertions.assertThat(pointcut("args(Object)") .matches(helloMethod, MemberServiceImpl.class)).isTrue(); // Execution Assertions.assertThat(pointcut("execution(* *(String))") .matches(helloMethod, MemberServiceImpl.class)).isTrue(); Assertions.assertThat(pointcut("execution(* *(java.io.Serializable))") .matches(helloMethod, MemberServiceImpl.class)).isFalse(); Assertions.assertThat(pointcut("execution(* *(Object))") .matches(helloMethod, MemberServiceImpl.class)).isFalse(); }
Java
복사
String의 상위 타입인 Object의 경우 Args는 검색이되지만, Execution는 검색이 안된다.

@target, @within

NOTE
특정 조건의 애노테이션이 있는지 확인한다!
@target → 인스턴스의 모든 메서드를 조인 포인트로 적용 @within → 해당 타입 내의 메서드만 조인 포인트로 사용
@Around("execution(* hello.aop..*(..)) && @target(hello.aop.member.annotation.ClassAop)") public Object atTarget(ProceedingJoinPoint joinPoint) throws Throwable { log.info("[@target] {}", joinPoint.getSignature()); return joinPoint.proceed(); } //@within: 선택된 클래스 내부에 있는 메서드만 조인 포인트로 선정, 부모 타입의 메서드는적용되지 않음 @Around("execution(* hello.aop..*(..)) && @within(hello.aop.member.annotation.ClassAop)") public Object atWithin(ProceedingJoinPoint joinPoint) throws Throwable { log.info("[@within] {}", joinPoint.getSignature()); return joinPoint.proceed(); }
Java
복사
target은 상위 타입까지 log가 나오지만, within은 안나옴
execution을 사용하는 이유는 @target의 경우 실행 시점에 일어나는 포인트컷 적용 여부도 결국 프록시가 있어야 실행 시점에 판단할 수 있다.
프록시를 생성하는 시점은 스프링 컨테이너가 만들어지는 애플리케이션 로딩 시점에 적용할 수 있으며, @target과 같은 포인트컷 지시자가 있으면 스프링은 모든 스프링 빈에 AOP를 적용하려고 시도한다.
내부에서 사용하는 빈 중에는 final로 지정된 빈들도 있기 때문에 오류가 생길 수 있어, 프록시 적용 대상을 축소하는 표현식을 사용한다.

@annotation, @args

NOTE
메서드가 주어진 애노테이션을 가지고 매칭한다.
@Override @MethodAop("test value") public String hello(String param) { return "ok"; }
Java
복사
메소드에 애노테이션 적용
@Aspect static class AtAnnotationAspect{ @Around("@annotation(hello.aop.member.annotation.MethodAop)") public Object doAtAnnotation(ProceedingJoinPoint joinPoint) throws Throwable { log.info("[@annotation] {}", joinPoint.getSignature()); return joinPoint.proceed(); } }
Java
복사
애노테이션이 적용된 메소드에 AOP를 적용한다.
@args(test.Check)
Java
복사
@Check가 있는지 확인
@args는 파라미터에 애노테이션이 있으면 검색하는 용도

bean

NOTE
스프링 빈 이름으로 포인트컷을 매칭한다!
@Aspect static class BeanAspect { @Around("bean(orderSerivce) || bean(*Repository)") public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable { log.info("[bean] {}", joinPoint.getSignature()); return joinPoint.proceed(); } }
Java
복사
스프링 빈 이름으로 포인트컷 적용

매개변수 전달

NOTE
위에서 사용한 개념들을 스프링의 Advice에 매개변수로 사용할 수 있다!
@Pointcut("execution(* hello.aop.member..*.*(..))") private void allMember() { } @Around("allMember()") public Object logArgs1(ProceedingJoinPoint joinPoint) throws Throwable { Object arg1 = joinPoint.getArgs()[0]; log.info("[logArgs1]{}, arg={}", joinPoint.getSignature(), arg1); return joinPoint.proceed(); } @Around("allMember() && args(arg, ..)") public Object logArgs2(ProceedingJoinPoint joinPoint, Object arg) throws Throwable { log.info("[logArgs2]{}, arg={}", joinPoint.getSignature(), arg); return joinPoint.proceed(); } @Before("allMember() && args(arg, ..)") public void logArgs3(String arg) { log.info("[logArgs3]{}, arg={}", arg); } @Before("allMember() && this(obj)") public void thisArgs(JoinPoint joinPoint, MemberService obj){ log.info("[this]{}, obj={}", joinPoint.getThis(), obj.getClass()); } @Before("allMember() && target(obj)") public void targetArgs(JoinPoint joinPoint, MemberService obj){ log.info("[target]{}, obj={}", joinPoint.getThis(), obj.getClass()); } @Before("allMember() && @target(annotation)") public void atTarget(JoinPoint joinPoint, ClassAop annotation){ log.info("[@target]{}, obj={}", joinPoint.getSignature(), annotation); } @Before("allMember() && @within(annotation)") public void atWithin(JoinPoint joinPoint, ClassAop annotation){ log.info("[@within]{}, obj={}", joinPoint.getSignature(), annotation); } @Before("allMember() && @annotation(annotation)") public void atWithin(JoinPoint joinPoint, MethodAop annotation){ log.info("[@annotation]{}, annotationValue={}", joinPoint.getSignature(), annotation.value()); }
Java
복사
단독으로 잘 사용하지 않고, 이렇게 매개변수로 자주사용한다.

this, target

NOTE
JDK 동적 프록시 → proxy(this)의 impl을 찾을 수 없음
CGLIB → proxy(this)의 impl을 찾을 수 있음!
this
스프링 빈 객체(스프링 AOP 프록시)를 대상으로 하는 조인 포인트
target
target 객체(스프링 AOP 프록시가 가르키는 실제 대상)을 대상으로 하는 조인 포인트