스프링 AOP - 포인트컷 지시자 - 1

현시기얌·2021년 12월 2일
0

AOP

목록 보기
13/19

포인트컷 지시자

AspectJ는 포인트컷을 편리하게 표현하기 위한 특별한 표현식을 제공한다.
ex) @Pointcut("execution ( hello.aop.order..(..))")

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

포인트컷 지시자의 종류

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

예제 코드

@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);
}

@Slf4j
@ClassAop
@Service
@RequiredArgsConstructor
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);
    }
}

execution

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

가장 정확한 포인트컷

    @BeforeEach
    public void init() throws NoSuchMethodException {
        helloMethod = MemberServiceImpl.class.getMethod("hello", String.class);
    }

    @Test
    void exactMatch() {
        // public java.lang.String hello.aop.member.service.MemberServiceImpl.hello(java.lang.String)
        pointcut.setExpression("execution(public String hello.aop.member.service.MemberServiceImpl.hello(String))");
        assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
    }

AspectJExpressionPointcut에 pointcut.setExpression을 통해서 포인트컷 표현식을 적용할 수 있다.
pointcut.matches(메소드, 대상클래스)를 실행하면 지정한 포인트컷 표현식의 매칭 여부를 true, false로 반환한다.

매칭 조건

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

가장 많이 생략한 포인트컷

    @Test
    void allMatch() {
        pointcut.setExpression("execution(* *(..))");
        assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
    }

매칭 조건

  • 접근제어자? : 생략
  • 반환타입 : *
  • 선언타입? : 생략
  • 메소드 이름 : *
  • 파라미터 : (..)
  • 예외? : 없음

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

그 외의 생략한 execution 사용법

    @Test
    void nameMatch() {
        pointcut.setExpression("execution(* hello(..))");
        assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
    }

    @Test
    void nameMatchStar1() {
        pointcut.setExpression("execution(* hel*(..))");
        assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
    }
    
    @Test
    void nameMatchStar2() {
        pointcut.setExpression("execution(* *el*(..))");
        assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
    }

    @Test
    void nameMatchFalse() {
        pointcut.setExpression("execution(* nono(..))");
        assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isFalse();
    }

    @Test
    void packageExactMatch1() {
        pointcut.setExpression("execution(* hello.aop.member.service.MemberServiceImpl.hello(..))");
        assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
    }

    @Test
    void packageExactMatch2() {
        pointcut.setExpression("execution(* hello.aop.member.service.*.*(..))");
        assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
    }

    @Test
    void packageMatchFalse() {
        pointcut.setExpression("execution(* hello.aop.*.*(..))");
        assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isFalse();
    }

    @Test
    void packageMatchSubPackage1() {
        pointcut.setExpression("execution(* hello.aop.member..*.*(..))");
        assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
    }

hello.aop.member.service.(1).(2)
(1) : 타입
(2) : 메소드 이름

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

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

타입 매칭 - 부모 타입 허용

    @Test
    void typeExactMatch() {
        pointcut.setExpression("execution(* hello.aop.member.service.MemberServiceImpl.*(..))");
        assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
    }

    @Test
    void typeMatchSuperType() {
        pointcut.setExpression("execution(* hello.aop.member.service.MemberService.*(..))");
        assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
    }

typeExactMatch()는 타입 정보가 정확하게 일치하기 때문에 매칭된다.
execution에서는 MemberService 처럼 부모 타입을 선언해도 그 자식 타입은 매칭된다. 다형성에서 부모타입 = 자식타입 이 할당 가능 하나는 점을 떠올려 보면 된다.

타입 매칭 - 부모 타입에 있는 메소드만 허용

    @Test
    void typeMatchInternal() throws NoSuchMethodException {
        pointcut.setExpression("execution(* hello.aop.member.service.MemberServiceImpl.*(..))");
        final Method internalMethod = MemberServiceImpl.class.getMethod("internal", String.class);
        assertThat(pointcut.matches(internalMethod, MemberServiceImpl.class)).isTrue();
    }

    @Test
    void typeMatchNoSuperTypeMethodFalse() throws NoSuchMethodException {
        pointcut.setExpression("execution(* hello.aop.member.service.MemberService.*(..))");
        final Method internalMethod = MemberServiceImpl.class.getMethod("internal", String.class);
        assertThat(pointcut.matches(internalMethod, MemberServiceImpl.class)).isFalse();
    }
  • typeMatchInternal()의 경우 MemberServiceImpl를 표현식에 선언했기 때문에 그 안에 있는 internal(STring) 메소드도 매칭 대상이 된다.
  • typeMatchNoSuperTypeMethodFalse()는 표현식에 부모타입인 MemberService를 선언했다.
    그런데 자식 타입인 MemberServiceImpl의 internal(String) 메소드를 매칭하려 한다.
    이 경우 매칭에 실패한다. MemberService에는 internal(String) 메소드가 없기 때문이다.
  • 부모 타입을 표현식에 선언한 경우 부모 타입에서 선언한 메소드가 자식 타입에 있어야 매칭에 성공한다.
    그래서 부모 타입에 있는 hello(String) 메소드는 매칭에 성공하지만 부모 타입에 없는 internal(String)은 매칭에 실패한다.

파라미터 매칭

    // String 타입의 파라미터 허용
    // (String)
    @Test
    void argsMatch() {
        pointcut.setExpression("execution(* *(String))");
        assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
    }

    //파라미터가 없어야함
    //()
    @Test
    void argsMatchNoArgs() {
        pointcut.setExpression("execution(* *())");
        assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isFalse();
    }

    //정확히 하나의 파라미터 허용, 모든 타입 허용
    //(Xxx)
    @Test
    void argsMatchStar() {
        pointcut.setExpression("execution(* *(*))");
        assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
    }

    // 숫자와 무관하게 모든 파라미터, 모든 타입 허용
    // (), (Xxx), (Xxx, Xxx)
    @Test
    void argsMatchAll() {
        pointcut.setExpression("execution(* *(..))");
        assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
    }

    // String 타입으로 시작, 숫자와 무관하게 모든 파라미터, 모든 타입 허용
    // (String), (String, Xxx), (String, Xxx, Xxx)
    void argsMatchComplex() {
        pointcut.setExpression("execution(* *(String, **))");
        assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
    }

execution 파라미터 매칭 규칙

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

within

within 지시자는 특정 타입 내의 조인 포인트에 대한 매칭을 제한한다.
쉽게 이야기해서 해당 타입이 매칭되면 그 안에 메소드(조인 포인트)들이 자동으로 매칭된다.
execution에서 타입 부분만 사용한다고 보면 된다.

예제 코드

public class WithinTest {

    AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
    Method helloMethod;

    @BeforeEach
    public void init() throws NoSuchMethodException {
        helloMethod = MemberServiceImpl.class.getMethod("hello", String.class);
    }

    @Test
    void withinExact() {
        pointcut.setExpression("within(hello.aop.member.service.MemberServiceImpl)");
        assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
    }

    @Test
    void withinStar() {
        pointcut.setExpression("within(hello.aop.member.service.*Service*)");
        assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
    }

    @Test
    void withinSubPackage() {
        pointcut.setExpression("within(hello.aop.member..*)");
        assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
    }
}

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

    @Test
    @DisplayName("타겟의 타입에만 적용 ,인터페이스를 선정하면 안된다.")
    void withinSuperTypeFalse() {
        pointcut.setExpression("within(hello.aop.member.service.MemberService)");
        assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isFalse();
    }

    @Test
    @DisplayName("execution은 타입 기반, 인터페이스 선정 가능")
    void executionSuperTypeTrue() {
        pointcut.setExpression("execution(* hello.aop.member.service.MemberService.*(..))");
        assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
    }

부모 타입(MemberService 인터페이스) 지정 시 within은 실패하고 execution은 성공하는 것을 확인할 수 있다.

args

  • args : 인자가 주어진 타입의 인스턴스인 조인 포인트로 매칭
  • 기몬 문법은 execution의 args 부분과 같다 .

execution과 args의 차이점

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

예제 코드

public class ArgsTest {

    Method helloMethod;

    @BeforeEach
    public void init() throws NoSuchMethodException {
        helloMethod = MemberServiceImpl.class.getMethod("hello", String.class);
    }

    private AspectJExpressionPointcut pointcut(String expression) {
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression(expression);
        return pointcut;
    }

    @Test
    void args() {
        //hello(String)과 매칭
        assertThat(pointcut("args(String)")
                .matches(helloMethod, MemberServiceImpl.class)).isTrue();
        assertThat(pointcut("args(Object)")
                .matches(helloMethod, MemberServiceImpl.class)).isTrue();
        assertThat(pointcut("args()")
                .matches(helloMethod, MemberServiceImpl.class)).isFalse();
        assertThat(pointcut("args(..)")
                .matches(helloMethod, MemberServiceImpl.class)).isTrue();
        assertThat(pointcut("args(*)")
                .matches(helloMethod, MemberServiceImpl.class)).isTrue();
        assertThat(pointcut("args(String, ..)")
                .matches(helloMethod, MemberServiceImpl.class)).isTrue();
    }

    /**
     * execution(* *(java.io.Serializable)) : 메소드의 시그니처로 판단 (정적)
     * args(java.io.Serializable) : 런타임에 전달된 인수로 판단 (동적)
     */
     @Test
     void argsVsExecution() {
         //args
         assertThat(pointcut("args(String)")
                 .matches(helloMethod, MemberServiceImpl.class)).isTrue();
         assertThat(pointcut("args(java.io.Serializable)")
                 .matches(helloMethod, MemberServiceImpl.class)).isTrue();
         assertThat(pointcut("args(Object)")
                 .matches(helloMethod, MemberServiceImpl.class)).isTrue();

         //execution
         assertThat(pointcut("execution(* *(String))")
                 .matches(helloMethod, MemberServiceImpl.class)).isTrue();
         assertThat(pointcut("execution(* *(java.io.Serializable))")
                 .matches(helloMethod, MemberServiceImpl.class)).isFalse();
         assertThat(pointcut("execution(* *(Object))")
                 .matches(helloMethod, MemberServiceImpl.class)).isFalse();
     }
}
  • 자바가 기본으로 제공하는 String은 Object, java.io.Serializable의 하위 타입이다.
  • 정적으로 클래스에 선언된 정보만 포고 판단하는 execution(* *(Object))는 매칭에 실패한다.
  • 동적으로 실제 파라미터로 넘어온 객체 인스턴스로 판단하는 args(Object))는 매칭에 성공한다. (부모 타입 허용)

cf) 참고

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

profile
현시깁니다

0개의 댓글