본문 바로가기
Spring/spring AOP

스프링 코어 2 - 포인트컷

by 킹차니 2022. 1. 14.

포인트컷 지시자

- 지금부터 포인트컷 표현식을 포함한 포인트컷에 대해서 자세히 알아보자.

 

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 : 스프링 전용 포인트컷 지시자, 빈의 이름으로 포인트컷을 지정한다.

 

 

 

 

포인트컷을 이해하기 위해 예제를 만들어보자.

먼저 아래와 같이 ClassAop, MethodAop라는 두개의 어노테이션을 만든다.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ClassAop {
}

 

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MethodAop {
    String value();
}

 

그리고 아래와 같이 아주 간단한 MemberService인터페이스와 MemberService인터페이스의 구현체인 MemberServiceImpl을 만든다.

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

 

아래와 같은 테스트를 실행시켜보면

@Slf4j
public class ExecutionTest {

    AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
    Method helloMethod;

    @BeforeEach
    public void init() throws NoSuchMethodException {
    	// MemberServiceImpl 클래스에서 메서드명이 "hello", 반환타입이 String인 메서드의 
        // 메타정보를 helloMethod가 참조하도록 한다.
        helloMethod = MemberServiceImpl.class.getMethod("hello", String.class);
    }

    @Test
    void printMethod(){
        log.info("helloMethod = {}",  helloMethod);
    }
}


실행결과:
helloMethod = public java.lang.String hello.aop.member.MemberServiceImpl.hello(java.lang.String)

이제 포인트컷 학습을 위한 준비가 되었다.

 

 

 


 

execution

 

execution은 아래와 같이 생겼다.

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

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

메소드 실행 조인 포인트를 매칭한다.

 ?는 생략할 수 있다.

 * 같은 패턴을 지정할 수 있다.

 

 

코드를 보며 execution을 이해해보자.


 

 

♦️ 가장 정확한 포인트컷

 

MemberServiceImpl.hello(String) 메서드와 가장 정확하게 모든 내용이 매칭되는 표현식은 아래와 같다.

@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 exactMatch() {
    	//execution사용. - 가장 정확한 표현식
        pointcut.setExpression("execution(public String hello.aop.member.MemberServiceImpl.hello(String))");
        assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
    }
    // -> 테스트 성공
}

 AspectJExpressionPointcutpointcut.setExpression을 통해서 포인트컷 표현식을 적용할 수 있다.

 pointcut.matches(메서드,대상 클래스)를 실행하면 지정한 포인트컷 표현식의 매칭 여부를 true, false로 반환한다.

매칭 조건 : 

 접근제어자? : public

 반환타입 : String

 선언타입? : hello.aop.member.MemberServiceImpl

메서드이름: hello
 파라미터 : (String)  <- 원래는 java.lang.String처럼 모든 패키지명을 붙여줘야 하지만 String은 자바에서 제공하므로 생략 가능
 예외? : 생략

 


 

 

♦️ 가장 많이 생략한 포인트컷

@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 allMatch() {
        pointcut.setExpression("execution(* *(..))");// 사실상 모든 메서드에 적용되는 포인트컷이다.
        assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
    }
    // --> 테스트 성공
}

매칭 조건 :

 접근제어자? : 생략

 반환타입 : *

 선언타입? : 생략

 메서드이름 : *

 파라미터 : (..)

예외? : 없음

 * 은 아무 값이 들어와도 된다는 뜻이다.
 파라미터에서 .. 은 파라미터의 타입과 파라미터 수가 상관없다는 뜻이다.  ( ( 0..* ) 파라미터는 뒤에 자세히 정리하겠다. )

 


 

♦️ 메서드 이름 매칭 관련 포인트컷

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

메서드 이름 앞뒤에 *을 사용해서 매칭할 수 있다.

 


 

♦️  패키지 매칭 관련 포인트컷

@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 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();
    }
    // --> 테스트 성공
}

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

(1): 타입
(2): 메서드 이름

 

@Test
void packageExactMatchFalse() {
    pointcut.setExpression("execution(* hello.aop.*.*.*(..))"); // 정확히 hello.aop패키지 이어야함
    assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isFalse();
}

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

@Test
void packageMatchSubPackage2() {
    pointcut.setExpression("execution(* hello.aop..*.*(..))"); // 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();
}

위의 테스트는 당연히 MemberServiceImpl타입으로 포인트 컥으로 지정했으므로 테스트 결과는 성공일 것이다.

 

하지만 아래는 부모타입을 포인트컷으로 지정하였다.

@Test
void typeMatchSuperType() {
    pointcut.setExpression("execution(* hello.aop.member.MemberService.*(..))"); // 부모 인터페이스 타입임에 주목
    assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}

그래도 위의 테스트는 성공한다.

execution에서는 MemberService처럼 부모 타입을 선언해도 그 자식 타입은 매칭된다. 다형성에서 '부모타입 = 자식타입'이 할당 가능하다는 점을 떠올려보면 된다.

하지만 부모타입이라고 해서 자식의 모든 것을 품을 수 있는 것은 아니다. 아래의 MemberServiceImpl을 보면 오버라이드 메서드가 아닌 자신만의 메서드인 internal메서드를 가지고 있는 것을 알 수 있다.

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

하여 internal메서드로 테스트해보면 매칭하지 못한다.

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

부모 타입을 표현식에 선언한 경우 부모 타입에서 선언한 메서드가 자식 타입에 있어야 매칭에 성공한다. 그래서 부모 타입에 있는 hello(String) 메서드는 매칭에 성공하지만, 부모 타입에 없는 internal(String) 는 매칭에 실패한다.

 


 

♦️  파라미터 매칭

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

 

// 파라미터 없는 경우
// ()
@Test
void noArgsMatch(){
    pointcut.setExpression("execution(* *())");
    //MemberServiceImpl안에는 파라미터가 없는 메서드 없음
    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)
@Test
void argsMatchComplex(){
    pointcut.setExpression("execution(* *(String, ..))");
    assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}

 

 

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

 


within

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

코드로 보자.

@Slf4j
public class WithinTest {

    AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
    Method helloMethod;

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

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

    @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();
    }
    @Test
    void withinSubPackage() {
        pointcut.setExpression("within(hello.aop..*)");
        assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
    }
}

하지만 within은 부모타입으로 지정이 불가능하다. (execution은 위에서 학습한 것처럼 가능)

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

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

 


 

args

인자가 주어진 타입의 인스턴스인 조인 포인트로 매칭

기본 문법은 execution의 args 부분과 같다. 하지만 아래와 같은 차이점이 있다.

execution은 파라미터 타입이 정확하게 매칭되어야 한다. execution은 클래스에 선언된 정보를 기반으로 판단한다.

 args 는 부모 타입을 허용한다. args는 실제 넘어온 파라미터 객체 인스턴스를 보고 판단한다.

 

아래 테스트를 보자.

@Slf4j
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)")//String implements Serializable
                .matches(helloMethod, MemberServiceImpl.class)).isTrue();
        assertThat(pointcut("args(Object)")//String extends Serializable
                .matches(helloMethod, MemberServiceImpl.class)).isTrue();

        //--Execution--
        assertThat(pointcut("execution(* *(String))")
                .matches(helloMethod, MemberServiceImpl.class)).isTrue();

        //매칭 실패
        assertThat(pointcut("execution(* *(java.io.Serializable))")//execution에서는 ㄴㄴ
                .matches(helloMethod, MemberServiceImpl.class)).isFalse();
        //매칭 실패
        assertThat(pointcut("execution(* *(Object))")//execution에서는 ㄴㄴ
                .matches(helloMethod, MemberServiceImpl.class)).isFalse();
    }
}

 

 

 pointcut() : AspectJExpressionPointcut 에 포인트컷은 한번만 지정할 수 있다. 이번 테스트에서는 테스트를 편리하게 진행하기 위       해 포인트컷을 여러번 지정하기 위해 포인트컷 자체를 생성하는 메서드를 만들었다.
 자바가 기본으로 제공하는 String Object , java.io.Serializable 의 하위 타입이다.

 정적으로 클래스에 선언된 정보만 보고 판단하는 execution(* *(Object)) 는 매칭에 실패한다.

 동적으로 실제 파라미터로 넘어온 객체 인스턴스로 판단하는 args(Object) 는 매칭에 성공한다. (부모 타입 허용)

 


@target, @within

@target : 실행객체의 클래스에 주어진 타입의 어노테이션이 있는 조인 포인트

@within : 주어진 어노테이션이 있는 타입 내 조인 포인트

 

@target, @within 은 아래와 같이 타입이 있는 어노테이션으로 AOP적용 여부를 판단한다.

@target(hello.aop.member.annotation.ClassAop)

@within(hello.aop.member.annotation.ClassAop)

 

@target, @within의 차이 :

@target은 인스턴스의 모든 메서드를 조인 포인트로 적용한다.

@within은 해당 타입 내에 있는 메서드만 조인 포인트로 적용한다.

즉 @target은 부모 클래스의 메서드까지 어드바이스를 다 적용하고, @within은 자기 자신의 클래스에 정의된 메서드에만 어드바이스를 적용한다.

 

 

아래는 테스트.

@Slf4j
@Import({AtTargetAtWithinTest.Config.class})
@SpringBootTest
public class AtTargetAtWithinTest {

    @Autowired
    Child child;

    @Test
    void success() {
        log.info("child Proxy={}", child.getClass());
        child.childMethod(); //부모, 자식 모두 있는 메서드 child.parentMethod(); //부모 클래스만 있는 메서드
    }

    static class Config { //Child, AtTargetAtWithinAspect을 빈으로 등록
        @Bean
        public Child child() {
            return new Child();
        }

        @Bean
        public AtTargetAtWithinAspect atTargetAtWithinAspect() {
            return new AtTargetAtWithinAspect();
        }
    }

    static class Parent {
        public void parentMethod() {} //부모에만 있는 메서드
    }

    @ClassAop
    static class Child extends Parent {
        public void childMethod() {}
    }

    @Slf4j
    @Aspect
    static class AtTargetAtWithinAspect {
        //@target: 인스턴스 기준으로 모든 메서드의 조인 포인트를 선정, 부모 타입의 메서드도 적용
        @Around("execution(* hello.aop..*(..)) && @target(hello.aop.member.annotation.ClassAop)")
        public Object atTarget(ProceedingJoinPoint joinPoint) throws Throwable {
            log.info("[@target] {}", joinPoint.getSignature());
            return joinPoint.proceed();
        }

        //@within: 선택된 클래스 내부에 있는 메서드만 조인 포인트로 선정, 부모 타입의 메서드는 적용되지 않음
        @Around("execution(* hello.aop..*(..)) && @within(hello.aop.member.annotation.ClassAop)")
        public Object atWithin(ProceedingJoinPoint joinPoint) throws Throwable {
            log.info("[@within] {}", joinPoint.getSignature());
            return joinPoint.proceed();
        }
    }
}


실행결과:

child Proxy=class hello.aop.pointcut.AtTargetAtWithinTest$Child$$EnhancerBySpringCGLIB$$ca36027e
[@target] void hello.aop.pointcut.AtTargetAtWithinTest$Child.childMethod()
[@within] void hello.aop.pointcut.AtTargetAtWithinTest$Child.childMethod()
[@target] void hello.aop.pointcut.AtTargetAtWithinTest$Parent.parentMethod()

parentMethod() Parent 클래스에만 정의되어 있고, Child 클래스에 정의되어 있지 않기 때문에 @within 에서 AOP 적용 대상이 되지 않는다.
실행결과를 보면 child.parentMethod() 를 호출 했을 때 [@within] 이 호출되지 않은 것을 확인할 수 있다.

참고
@target , @within 지시자는 뒤에서 설명할 파라미터 바인딩에서 함께 사용된다.
주의
다음 포인트컷 지시자는 단독으로 사용하면 안된다. args, @args, @target .
이번 예제를 보면 execution(* hello.aop..*(..)) 를 통해 적용 대상을 줄여준 것을 확인할 수 있다. args , @args , @target 은 실제 객체 인스턴스가 생성되고 실행될 때 어드바이스 적용 여부를 확인할 수 있다. 실행 시점에 일어나는 포인트컷 적용 여부도 결국 프록시가 있어야 실행 시점에 판단할 수 있다. 프록시가 없다면 판단 자체가 불가능하다. 그런데 스프링 컨테이너가 프록시를 생성하는 시점은 스프링 컨테이너가 만들어지는 애플리케이션 로딩 시점에 적용할 수 있다. 따라서 args , @args , @target 같은 포인트컷 지시자가 있으면 스프링은 모든 스프링 빈에 AOP를 적용하려고 시도한다. 앞서 설명한 것 처럼 프록시가 없으면 실행 시점에 판단 자체가 불가능하다.  문제는 이렇게 모든 스프링 빈에 AOP 프록시를 적용하려고 하면 스프링이 내부에서 사용하는 빈 중에는 final 로 지정된 빈들도 있기 때문에 오류가 발생할 수 있다. 따라서 이러한 표현식은 최대한 프록시 적용 대상을 축소하는 표현식과 함께 사용해야 한다.

 

 


@annotation, @args

@annotation : 메서드가 주어진 어노테이션을 가지고 있는 조인 포인트를 매칭

ex) @annotation(hello.aop.member.annotation.MethodAop)

 

다음과 같이 메서드(조인 포인트)에 어노테이션이 있으면 매칭한다. (해당 포스트 맨 위에 직접 만든 어노테이션과 MemberServiceImpl참고)

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

 

이를 테스트해보자.

@Slf4j
@SpringBootTest
public class AnnotationTest {

    @Autowired
    MemberService memberService;

    @Test
    void success(){
        log.info("memberService Proxy = {}", memberService.getClass());
        memberService.hello("helloA");
    }

    @Slf4j
    @Component
    @Aspect
    static class AtAnnotationAspect{
        @Around("@annotation(hello.aop.member.annotation.MethodAop)")
        public Object doAtAnnotation(ProceedingJoinPoint joinPoint) throws Throwable {
            log.info("[@annotation] {}", joinPoint.getSignature());
            return joinPoint.proceed();
        }
    }
}


실행결과:

memberService Proxy = class hello.aop.member.MemberServiceImpl$$EnhancerBySpringCGLIB$$f95ff287
[@annotation] String hello.aop.member.MemberServiceImpl.hello(String)

 

 

@args

전달된 실제 인수의 런타임 타입이 주어진 타입의 어노테이션을 갖는 조인 포인트

-> 전달된 인수의 런타임 타입에 @Check 어노테이션이 있는 경우에 매칭한다.

ex) @args(test.Check)

 


bean

스프링 전용 포인트컷 지시자, 빈의 이름으로 지정한다.

 

스프링 빈의 이름으로 AOP 적용 여부를 지정한다. 이것은 스프링에서만 사용할 수 있는 특별한 지시자이다.

• bean(orderService) || bean(*Repository)

• *과 같은 패턴을 사용할 수 있다.

 

이를 테스트해보자.

@Slf4j
@Import(BeanTest.BeanAspect.class)
@SpringBootTest
public class BeanTest {
    @Autowired OrderService orderService;

    @Test
    void success(){
        orderService.orderItem("ItemA");
    }

    @Slf4j
    @Aspect
    static class BeanAspect{
        @Around("bean(orderService) || bean(*Repository)")
        public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable {
            log.info("[bean] {}", joinPoint.getSignature());
            return joinPoint.proceed();
        }
    }
}

실행결과:

[bean] String hello.aop.order.OrderService.orderItem(String)
[orderService] 실행
[bean] String hello.aop.order.OrderRepository.save(String)

 

 

 

 

 

 

 

 

 

 

 

김영한님의 인프런 강의와 PDF를 바탕으로 정리하였습니다.