Spring Boot

Spring AOP - 포인트컷(pointcut) 지시자(execution)

작은별._. 2024. 10. 2. 21:00
728x90

@Aspect를 사용해서 스프링 AOP를 구현할 때, 아래와 같이 표현식을 사용합니다.

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

 

포인트컷 표현식은 execution 같은 포인트컷 지시자(Pointcut Designator)로 시작합니다.(일명 PCD)  Spring AOP에서 포인트컷(Pointcut) 지시자는 Aspect(관점)에서 어떤 JoinPoint를 적용할지 정의하는 역할을 합니다.  이런 포인트컷 지시자의 종류에는 아래의 것들이 있습니다.

  • execution(): 메소드 실행 시점을 포인트컷으로 지정합니다. 메서드 이름, 매개변수, 반환 타입 등을 기반으로 필터링할 수 있습니다. 가장 많이 사용합니다.
    • @Pointcut("execution(* com.example.service.*.*(..))")
  • within(): 특정 타입이나 패키지 내의 모든 조인 포인트를 지정합니다. 즉, 해당 타입이 매칭되면 그 안의 메서드(JoinPoint)들이 자동으로 매칭됩니다.
    • @Pointcut("within(com.example.service.*)")
  • this(): 특정 타입의 프록시 객체(스프링 AOP 프록시)를 기준으로 포인트컷을 설정합니다.
    • @Pointcut("this(com.example.service.MyService)")
  • target(): 특정 타입의 타겟 객체(스프링 AOP 프록시가 가리키는 실제 대상)를 기준으로 포인트컷을 설정합니다.
    • @Pointcut("target(com.example.service.MyService)")
  • args(): 메소드의 매개변수 타입을 기준으로 포인트컷을 설정합니다.
    • @Pointcut("args(String,..)")
  • @annotation(): 특정 애너테이션이 적용메서드에 대해 포인트컷을 설정합니다.
    • @Pointcut("@annotation(com.example.annotation.MyAnnotation)")
  • @within(): 특정 애너테이션이 적용타입 내의 모든 조인 포인트를 지정합니다다.
    • @Pointcut("@within(com.example.annotation.MyAspect)")
  • @target(): 특정 애너테이션이 적용타깃 객체의 메서드에 대해 포인트컷을 설정합니다.
    • @Pointcut("@target(com.example.annotation.MyServiceAnnotation)")

execution 지시자에 대해서 알아보겠습니다.

execution

execution 문법을 먼저 보겠습니다.

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

 

? 는 생략이 가능한 표현식입니다.

 

아래 예시는 정확하게 패키지 경로와 클래스, 메서드 이름까지 표현한 포인트컷입니다.

execution(public String hello.aop.member.MemberServiceImpl.hello(String))

 

아래 예시는 가장 많이 생략한 포인트컷입니다. 

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

 

실제 코드를 구현하면서 테스트해보며 execution 표현식을 익혀보겠습니다.


MemberService 클래스

package hello.aop.member;

public interface MemberService {
    String hello(String param);
}

 

MemberServiceImpl 클래스

package hello.aop.member;
import org.springframework.stereotype.Component;

@Component
public class MemberServiceImpl implements MemberService {

    @Override
    public String hello(String param) {
        return "ok";
    }

    public String internal(String param) {
        return "ok";
    }
}

 

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


 

@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(){
        log.info("helloMethod={}", helloMethod);
    }

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

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

    @Test
    void allMatch() { // 가장 많이 생략한 포인트컷
        // 접근 제어자(생략), 반환타입(*), 선언타입(생략), 메서드이름(*), 파라미터(..), 에러(생략)
        pointcut.setExpression("execution(* *(..))");
        assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
    }


    // 메서드 이름 매칭 관련 포인트컷
    @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.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 packageExactMatchFalse() {
        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();
    }




    // 타입 매칭 - 부모 타입 허용
    @Test
    void typeExactMatch() {
        pointcut.setExpression("execution(* hello.aop.member.MemberServiceImpl.*(..))");
        assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
    }
    @Test
    void typeMatchSuperType() {
        pointcut.setExpression("execution(* hello.aop.member.MemberService.*(..))");
        assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
    }

    // 타입 매칭 - 부모 타입에 있는 메서드만 허용
    @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 {
        // 포인트컷으로 지정한 MemberService 는 internal 이라는 이름의 메서드가 없다.
        pointcut.setExpression("execution(* hello.aop.member.MemberService.*(..))");
        Method internalMethod = MemberServiceImpl.class.getMethod("internal",
                String.class);
        assertThat(pointcut.matches(internalMethod,
                MemberServiceImpl.class)).isFalse();
    }


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

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

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

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

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

 

 

소스 코드

https://github.com/eunhwa99/SpringAOP/blob/main/advanced/src/test/java/hello/aop/pointcut/ExecutionTest.java

 

SpringAOP/advanced/src/test/java/hello/aop/pointcut/ExecutionTest.java at main · eunhwa99/SpringAOP

SpringAOP 구현 코드. Contribute to eunhwa99/SpringAOP development by creating an account on GitHub.

github.com

 


[참고자료]

김영한, "스프링 핵심 원리 - 고급편 ", 인프런

728x90
반응형