스프링 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개의 댓글

관련 채용 정보