FireDrago

[Spring] 스프링 AOP - 포인트컷 1 본문

프로그래밍/Spring

[Spring] 스프링 AOP - 포인트컷 1

화이용 2024. 5. 20. 16:00

포인트컷

 

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

 

포인트컷 지시자의 종류

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

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

애노테이션 ClassAop는 클래스에 붙이고, MethodAop는 메소드에 붙인다.

String value() 를 통해 애노테이션 설정에 value 값을 설정할 수 있도록 했다.

 

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

 

AspectJExpressionPointcut 이 바로 포인트컷 표현식을 처리해주는 클래스다.  포인트컷 표현식을 지정하면 된다. 

AspectJExpressionPointcut 는 상위에 Pointcut 인터페이스를 가진다.

 

execution

execution (접근제어자?  반환타입  선언타입?메서드이름(파라미터)  예외?)

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

 

?는 생략할 수 있다. * 같은 패턴을 지정할 수 있다.

 

/**
* 메서드 이름 포인트컷 
*/
@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 packageExactMathc1() {
    pointcut.setExpression(
            "execution(* hello.aop.member.MemberServiceImpl.hello(..))"
    );
    assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}

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

@Test
void packageExactFalse() {
    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();
}

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

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

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

. : 정확하게 해당 위치의 패키지

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

@Test
void typeMatchInternal() throws NoSuchMethodException {
    pointcut.setExpression(
            "execution(* hello.aop.member.MemberServiceImpl.*(..))"
    );
    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.MemberService.*(..))"
    );
    Method internalMethod = MemberServiceImpl.class.getMethod("internal", String.class);
    // 부모 타입을 표현식에 선언한 경우 부모 타입에서 선언한 메서드가 자식 타입에 있어야 매칭에 성공한다
    assertThat(pointcut.matches(internalMethod, MemberServiceImpl.class)).isFalse();
}

execution 에서는 MemberService 처럼 부모 타입을 선언해도 그 자식 타입은 매칭된다.

 

typeMatchNoSuperTypeMethodFalse() 표현식에 부모 타입인 MemberService 를 선언했다. 

그런데 자식 타입인 MemberServiceImplinternal(String) 메서드를 매칭하려 한다. 

이 경우 매칭에 실패한다. MemberService 에는 internal(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)).isTrue();
}

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

// 파라미터의 갯수와 무관하고, 모든 타입 허용
// 파라미터가 없어도 된다.
@Test
void argsMatchAll() {
    pointcut.setExpression(
            "execution(* *(..))"
    );
    assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}

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

execution 파라미터 매칭 규칙

  • (String) : 정확하게 String 타입 파라미터
  • () : 파라미터가 없어야 한다.
  • (*) : 정확히 하나의 파라미터
  • (*,*) : 정확히 두개의 파라미터, 모든 타입 허용
  • (..) : 갯수와 무관하게 모든 파라미터, 모든 타입 허용 (파라미터가 없어도 된다.)

 

within

within 지시자는 특정 타입 내의 조인 포인트들로 매칭을 제한한다. 쉽게 이야기해서 해당 타입이 매칭되면 그 안의
메서드(조인 포인트)들이 자동으로 매칭된다.

 

/**
 *  within은 표현식에 부모 타입을 지정하면 안된다. 정확하게 타입이 맞아야 한다.
 *  execution과 차이가 난다.
 */
@BeforeEach
public void init() throws NoSuchMethodException {
    helloMethod = MemberServiceImpl.class.getMethod("hello", String.class);
}

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

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

within 사용시 표현식에 부모 타입을 지정하면 안된다.