[TIL] [Spring AOP] 포인트 컷 지시자 PCD - 종류 ,사용법,주의점,매개변수 정리

SlowAnd·2024년 1월 5일
0

Spring AOP

목록 보기
3/5

[배경 지식] - 포인트컷 용어 설명한 포스트

포인트컷 표현식은 AspectJ pointcut expression 즉 애스펙트J가 제공하는 포인트컷 표현식을 줄여서 말하는 것이다.

애스펙트J는 포인트컷을 편리하게 표현하기 위한 특별한 표현식을 제공한다.

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

포인트컷 지시자

포인트컷 표현식은 execution 같은 포인트컷 지시자(Pointcut Designator)로 시작한다. 줄여서 PCD라 한다.

포인트컷 지시자의 종류

  • execution : 메소드 실행 조인 포인트를 매칭한다. 스프링 AOP에서 가장 많이 사용하고, 기능도 복잡하다.
  • within : 특정 타입 내의 조인 포인트를 매칭한다.
  • args : 인자가 주어진 타입의 인스턴스인 조인 포인트
  • this : 스프링 빈 객체(스프링 AOP 프록시)를 대상으로 하는 조인 포인트
  • target : Target 객체(스프링 AOP 프록시가 가리키는 실제 대상)를 대상으로 하는 조인 포인트
  • @target : 실행 객체의 클래스에 주어진 타입의 애노테이션이 있는 조인 포인트
  • @within : 주어진 애노테이션이 있는 타입 내 조인 포인트
  • @annotation : 메서드가 주어진 애노테이션을 가지고 있는 조인 포인트를 매칭
  • @args : 전달된 실제 인수의 런타임 타입이 주어진 타입의 애노테이션을 갖는 조인 포인트
  • bean : 스프링 전용 포인트컷 지시자, 빈의 이름으로 포인트컷을 지정한다.

예제 만들기

@ClassAop, @MethodAop , interface MemberService ,
class MemberServiceImpl, 테스트 코드 ExecutionTest

 @Target(ElementType.TYPE)
 @Retention(RetentionPolicy.RUNTIME)
 public @interface ClassAop {
}

---------------------------------------------
@Target(ElementType.METHOD)
 @Retention(RetentionPolicy.RUNTIME)
 public @interface MethodAop {
     String value();
}
---------------------------------------------
 public interface MemberService {
     String hello(String param);
}
---------------------------------------------
 @ClassAop
 @Component
 public class MemberServiceImpl implements MemberService {
     @Override
     @MethodAop("test value")
     public String hello(String param) {
         return "ok";
     }
     public String internal(String param) {
         return "ok";
  } 
}
---------------------------------------------
 -테스트 코드 - 
 
 @Slf4j
 public class ExecutionTest {
     AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
     Method helloMethod;
     
     @BeforeEach
     public void init() throws NoSuchMethodException {
         helloMethod = MemberServiceImpl.class.getMethod("hello", String.class);
     }
     
     @Test
     void printMethod() {
         //public java.lang.String
         hello.aop.member.MemberServiceImpl.hello(java.lang.String)
         log.info("helloMethod={}", helloMethod);
     }
}

execution

execution 문법

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern)
           throws-pattern?)
         
---

execution(접근제어자? 반환타입 선언타입?메서드이름(파라미터) 예외?)
  • 메소드 실행 조인 포인트를 매칭한다.
  • ?는 생략할 수 있다.
  • * 같은 패턴을 지정할 수 있다.

1.표현 - 가장 정확한 포인트컷으로 표현하기

먼저 MemberServiceImpl.hello(String) 메서드와 가장 정확하게 모든 내용이 매칭되는 표현식이다.

AspectJExpressionPointcutpointcut.setExpression 을 통해서 포인트컷 표현식을 적용할 수 있다.

매칭 조건

  • 접근제어자?: public
  • 반환타입: String
  • 선언타입?: hello.aop.member.MemberServiceImpl
  • 메서드이름: hello
  • 파라미터: (String)
  • 예외?: 생략

결과
MemberServiceImpl.hello(String) 메서드와 포인트컷 표현식의 모든 내용이 정확하게 일치한다. 따라서 true 를 반환한다.

1.표현 - 가장 많이 생략한 포인트컷

가장 많이 생략한 포인트컷이다.

딱 3가지만 하면 된다.
= 반환타입(*), 메서드이름(*) , 파라미터((..))

매칭 조건

  • 접근제어자?: 생략
  • 반환타입: *
  • 선언타입?: 생략
  • 메서드이름: *
  • 파라미터: (..)
  • 예외?: 없음
    • * 은 아무 값이 들어와도 된다는 뜻이다.
    • 파라미터에서 .. 은 파라미터의 타입과 파라미터 수가 상관없다는 뜻이다.
      ( 0..* ) 파라미터는 뒤에 자세히 정리하겠다.

2. 메서드 매칭 - 메서드 이름 매칭 관련 포인트컷

메서드이름앞뒤에 * 을사용해서매칭할수있다.

매칭 조건

  • 접근제어자?: 생략
  • 반환타입: *
  • 선언타입?: 생략
  • 메서드이름: hello hel* *el* nono 처럼 설정 가능
  • 파라미터: (..)
  • 예외?: 없음
    • * 은 아무 값이 들어와도 된다는 뜻이다.
    • 파라미터에서 .. 은 파라미터의 타입과 파라미터 수가 상관없다는 뜻이다.

3. 패키지 매칭 - 패키지 매칭 관련 포인트컷


매칭 조건

  • 접근제어자?: 생략

  • 반환타입: *

  • 선언타입?: 생략

  • 메서드이름(패키지까지 다적음): hello.aop.member.*(1).*(2) 처럼 설정 가능

  • 파라미터: (..)

  • 예외?: 없음

    • * 은 아무 값이 들어와도 된다는 뜻이다.
    • 파라미터에서 .. 은 파라미터의 타입과 파라미터 수가 상관없다는 뜻이다.)

1.hello.aop.member.*(1).*(2)

  • (1): 타입
  • (2): 메서드 이름

2.패키지에서 . , .. 의 차이를 이해해야 한다.

  • . : 정확하게 해당 위치의 패키지
  • .. : 해당 위치의 패키지와 그 하위 패키지도 포함

4. 타입 매칭 - 부모 타입 허용



typeExactMatch() 는 타입 정보가 정확하게 일치하기 때문에 매칭된다.
typeMatchSuperType() 을 주의해서 보아야 한다.

execution 에서는 MemberService 처럼 부모 타입을 선언해도 그 자식 타입은 매칭된다. 다형성에서 부모타입 = 자식타입 이 할당 가능하다는 점을 떠올려보면 된다.

4. 타입 매칭 - 부모 타입에 있는 메서드만 허용



typeMatchInternal() 의 경우 MemberServiceImpl 를 표현식에 선언했기 때문에 그 안에 있는 internal(String) 메서드도 매칭 대상이 된다.
typeMatchNoSuperTypeMethodFalse() 를 주의해서 보아야 한다.

이 경우 표현식에 부모 타입인 MemberService 를 선언했다. 그런데 자식 타입인 MemberServiceImplinternal(String) 메서드를 매칭하려 한다.
이 경우 매칭에 실패한다. MemberService 에는 internal(String) 메서드가 없다!
부모 타입을 표현식에 선언한 경우 부모 타입에서 선언한 메서드가 자식 타입에 있어야 매칭에 성공한다.

그래서 부모 타입에 있는 hello(String) 메서드는 매칭에 성공하지만, 부모 타입에 없는 internal(String) 는 매칭에 실패한다.

5. 파라미터 매칭


매칭 조건

  • 접근제어자?: 생략
  • 반환타입: *
  • 선언타입?: 생략
  • 메서드이름: *
  • 파라미터: ( 위의 파라미터 매칭 규칙에 따라)
  • 예외?: 없음
    • * 은 아무 값이 들어와도 된다는 뜻이다.
    • 파라미터에서 .. 은 파라미터의 타입과 파라미터 수가 상관없다는 뜻이다.

execution 파라미터 매칭 규칙은 다음과 같다.

  • (String) : 정확하게 String 타입 파라미터
  • () : 파라미터가 없어야 한다.
  • (*) : 정확히 하나의 파라미터, 단 모든 타입을 허용한다.
  • (*, *) : 정확히 두 개의 파라미터, 단 모든 타입을 허용한다.
  • (..) : 숫자와 무관하게 모든 파라미터, 모든 타입을 허용한다. 참고로 파라미터가 없어도 된다. 0..* 로 이해하면 된다.
  • (String, ..) : String 타입으로 시작해야 한다. 숫자와 무관하게 모든 파라미터, 모든 타입을 허용한다.
    • 예) (String) , (String, Xxx) , (String, Xxx, Xxx) 허용

within

타입 매칭

within 지시자는 특정 타입 내의 조인 포인트들로 매칭을 제한한다.
문법은 단순한데 execution 에서 타입 부분만 사용한다고 보면 된다.

주의
그런데 within 사용시 주의해야 할 점이 있다. 표현식에 부모 타입을 지정하면 안된다는 점이다. 정확하게 타입이 맞 아야 한다. 이 부분에서 execution 과 차이가 난다.

args

파라미터 매칭

args : 인자가 주어진 타입의 인스턴스인 조인 포인트로 매칭

execution과 args의 차이점
execution 은 파라미터 타입이 정확하게 매칭되어야 한다. execution 은 클래스에 선언된 정보를 기반으로
판단한다.
args 는 부모 타입을 허용한다. args 는 실제 넘어온 파라미터 객체 인스턴스를 보고 판단한다.


Serializable? String의 추상 타입

  • pointcut() : AspectJExpressionPointcut 에 포인트컷은 한번만 지정할 수 있다. 이번 테스트에서는 테스트를 편리하게 진행하기 위해 포인트컷을 여러번 지정하기 위해 포인트컷 자체를 생성하는 메서드를 만들었 다.
  • 자바가 기본으로 제공하는 StringObject , java.io.Serializable 의 하위 타입이다.
  • 정적으로 클래스에 선언된 정보만 보고 판단하는 execution(* *(Object)) 는 매칭에 실패한다.
  • 동적으로 실제 파라미터로 넘어온 객체 인스턴스로 판단하는 args(Object) 는 매칭에 성공한다. (부모 타입 허용)

참고
args 지시자는 단독으로 사용되기 보다는 뒤에서 설명할 파라미터 바인딩에서 주로 사용된다.

@target, @within

메서드 매칭


설명
@target , @within 은 다음과 같이 타입에 있는 애노테이션으로 AOP 적용 여부를 판단한다.

  • @target(hello.aop.member.annotation.ClassAop)
  • @within(hello.aop.member.annotation.ClassAop)
 @ClassAop
 class Target{}

@target vs @within

  • @target 은 부모 클래스의 메서드까지 어드바이스를 다 적용하고,
  • @within 은 자기 자신의 클래스 에 정의된 메서드에만 어드바이스를 적용한다.

예시

(@Traget)는 Parent, Child 까지 영향을 미친다.

(@Within)는 Child 만 영향을 미친다.

테스트

결과 @target이 붙은 child의 메서드를 호출하니, 부모까지 호출됐다.
반면
@within이 붙은 child의 메서드를 호출하니, @within 붙은 child만 호출됐다.

주의

args,@args,@target 포인트컷 지시자는 단독 사용 불가.
이 지시자들은 실행시점(동적 판단)하기 때문에, 이미 만들어져 있는 프록시가 필요함.
그래서 다음과 같이 exectution으로 미리 만들고(정적 포인트컷)과 함께 쓰임

@annotation, @args

메서드 매칭

@annotation

다음과 같이 메서드(조인 포인트)에 애노테이션이 있으면 매칭한다.

적용예시

결과

@args

정의
@args : 전달된 실제 인수의 런타임 타입이 주어진 타입의 애노테이션을 갖는 조인 포인트
설명
전달된 인수의 런타임 타입에 @Check 애노테이션이 있는 경우에 매칭한다.
@args(test.Check)

bean

빈의 이름 매칭

  • 스프링 빈의 이름으로 AOP 적용 여부를 지정한다. 이것은 스프링에서만 사용할 수 있는 특별한 지시자이다.
  • bean(orderService) || bean(*Repository)
  • * 과 같은 패턴을 사용할 수 있다.

    결과
    OrderService , *Repository(OrderRepository) 의 메서드에 AOP가 적용된다.

매개 변수 전달

다음은 포인트컷 표현식을 사용해서 어드바이스에 매개변수를 전달할 수 있다.
this, target, args,@target, @within, @annotation, @args

초기화 작업

args

this

target

@target, @within

@annotation

this,target

객체 매칭

정의
this : 스프링 빈 객체(스프링 AOP 프록시)를 대상으로 하는 조인 포인트
target : Target 객체(스프링 AOP 프록시가 가리키는 실제 대상)를 대상으로 하는 조인 포인트

설명
this , target 은 다음과 같이 적용 타입 하나를 정확하게 지정해야 한다.

  • 같은 패턴을 사용할 수 없다.
  • 부모 타입을 허용한다.

this 주의

주의
프록시를 대상으로하는 this 의경우 구체클래스를 지정하면 프록시 생성 전략에 따라서 다른 결과가 나올 수 있다는 점을 알아두자.

프록시 생성 방식에 따른 차이
스프링은 프록시를 생성할 때 JDK 동적 프록시와 CGLIB를 선택할 수 있다.
둘의 프록시를 생성하는 방식이 다르기 때문에 차이가 발생한다.

  • JDK 동적 프록시: 인터페이스가 필수이고, 인터페이스를 구현한 프록시 객체를 생성한다.
  • CGLIB: 인터페이스가 있어도 구체 클래스를 상속 받아서 프록시 객체를 생성한다.
    -> 따라서 JDK 동적프록시로 생성됐는데 this로 구현체 지정인 상황이면 = AOP적용안됨

사용 예시 코드

참조
스프링 핵심 원리 - 고급편

0개의 댓글