Spring Boot

Spring AOP - 포인트컷(pointcut) 지시자(within, args)

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

해당 포스팅에 이어서 포인트컷 지시자에 대해서 작성하였습니다.

 

within

within 지시자는 특정 타입 내의 조인 포인트들로 매칭을 제한합니다. 즉, 해당 타입이 매칭되면 그 안의 메서드(조인 포인트)들자동으로 매칭됩니다.

문법은 execution에서 타입 부분만 사용한다고 보면 됩니다. 참고로 execution 문법은 아래와 같았습니다.

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

 

즉, 여기에서 [선언 타입?] 부분을 사용합니다. 즉, within(패키지명..*)

 

지난 ExecutionTest 에 이어서 WithinTest 코드를 작성해 보겠습니다.


public class WithinTest {

    AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
    Method helloMethod;
    
    @BeforeEach
    public void init() throws NoSuchMethodException {
       // hello 이름을 가진 메소드 가지고 옵니다.
        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() {
        // Service 이름이 들어간 클래스
        pointcut.setExpression("within(hello.aop.member.*Service*)");
        assertThat(pointcut.matches(helloMethod,
                MemberServiceImpl.class)).isTrue();
    }
    
    @Test
    void withinSubPackage() {
        // hello.aop 패키지와 하위 패키지들 포함
        pointcut.setExpression("within(hello.aop..*)");
        assertThat(pointcut.matches(helloMethod,
                MemberServiceImpl.class)).isTrue();
    }

    @Test
    void withinSuperTypeFalse() {
        // within은 부모 타입을 지정하면 X, 정확하게 타입이 맞아야 합니다.
        pointcut.setExpression("within(hello.aop.member.MemberService)");
        assertThat(pointcut.matches(helloMethod,
                MemberServiceImpl.class)).isFalse(); // False!!
    }
    
    @Test
    void executionSuperTypeTrue() {
        // execution은 부모 타입을 지정해도 가능합니다.
        pointcut.setExpression("execution(* hello.aop.member.MemberService.*(..))");
        assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
    }
}

 

마지막 2개 테스트에서 확인할 수 있듯이, execution과 달리 within 표현식에는 부모 타입이 아닌 정확한 타입을 표현해 주어야 합니다.


args

args 는 인자가 주어진 타입의 인스턴스인 Joinpoint로 매칭합니다. 기본 문법은 execution의 매개변수 부분과 같습니다. 차이점은, execution은 파라미터 타입이 정확하게 매칭되어야 하지만, args는 부모 타입을 허용합니다. 코드를 통해 확인해 보겠습니다.


package hello.aop.member;

@Component
public class MemberServiceImpl implements MemberService {

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

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



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();
                
        // Object은 String의 부모 타입으로 args로는 hello(String) 과 매칭 가능
        assertThat(pointcut("args(Object)")
                .matches(helloMethod, MemberServiceImpl.class)).isTrue();
                
        // 파라미터가 없으므로(void) hello(String)과 매칭 X
        assertThat(pointcut("args()")
                .matches(helloMethod, MemberServiceImpl.class)).isFalse();
                
        // 파라미터가 (..)로, 어떤 타입의 파라미터가 몇 개여도 상관없다는 의미 
        // -> hello(String)과 매칭 가능
        assertThat(pointcut("args(..)")
                .matches(helloMethod, MemberServiceImpl.class)).isTrue();
                
        // * 는 모든 파라미터 허용하므로 hello(String)과 매칭 가능
        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();
    }
}

 

  • 정적으로 클래스에 선언된 정보만 보고 판단하는 execution(* *(Object)) 는 매칭에 실패합니다.
  • 동적으로 실제 파라미터로 넘어온 객체 인스턴스로 판단하는 args(Object)는 매칭에 성공합니다. 

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

 

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

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

github.com

 


[참고자료]

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

728x90
반응형