Back-End/Spring Advance & Boot

스프링 고급편 - 포인트컷

Meluu_ 2024. 11. 11. 19:59

 

✔️ 포인트컷 지시자(Pointcut Designator) : PCD


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

@Pointcut("execution(* hello.aop.order..*(..))")
  • execution : 메서드 실행 조인 포인트를 매칭
  • within : 특정 타입 내의 조인 포인트를 매칭
  • args : 인자가주어진 타입의 인스턴스인 조인 포인트  
  • this : 스프링 빈 객체(스프링 AOP 프록시)를 대상으로 하는 조인 포인트
  •  target : Target 객체(스프링 AOP 프록시가 가리키는 실제 대상)를 대상으로 하는 조인 포인트
  • @target : 실행 객체의클래스에주어진 타입의 애노테이션이 있는 조인 포인트    
  • @within : 주어진 애노테이션이있는 타입 내 조인 포인트
  • @annotation : 메서드가 주어진 애노테이션을 가지고 있는 조인 포인트를 매칭
  • @args : 전달된 실제 인수의 런타임 타입이 주어진 타입의 애노테이션을 갖는 조인포인트
  • bean : 스프링 전용 포인트컷지시자, 빈의 이름으로 포인트컷을 지정   

execution 을 가장 많이 사용하고 나머지는 자주 사용 X

 

예제 만들기

@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";
    }
}
@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(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
=
execution(접근제어자? 반환타입 선언타입?메서드이름(파라미터) 예외?)
  • 메서드 실행 조인 포인트를 매칭
  • ? 는 생략 가능
  • * 같은 패턴을 지정 가능

 

가장 정확한 포인트 컷

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

 

  • 접근제어자? : public
  • 반환타입 : String
  • 선언타입? : hello.aop.member.MemberServiceImpl
  • 메서드 이름 : hello
  • 파라미터: (String)
  • 예외? : 생략

실행 결과 : True

 

가장 많이 생략한 포인트 컷

@Test
void allMatch() {
    pointcut.setExpression("execution(* *(..))");
    assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}
  • 접근제어자? : 생략
  • 반환타입 : *    [any]
  • 선언타입? : 생략
  • 메서드 이름 : *  [any]
  • 파라미터: (..)    [파라미터 타입과 수 상관 X]
  • 예외? : 없음

 

메서드 이름 앞 뒤에 *을 사용해서 매칭 가능 (마치LIKE %pple% 같은)

 

선언 타입에도 중간에 * 사용 가능

hello.aop.member.*.*(..)

첫번재 * : 타입

두번째 * : 메서드 이름

 

pointcut.setExpression("execution(* hello.aop..*.*(..))");

패키지에서 ..해당 위치의 패키지와그 하위 패키지도 포함 한다는 의미  

aop 하위 패키지에 * : 모든 타입의 * : 모든 메서드 (..) : 파라미터 아무거나 

 

부모 타입 매칭 허용 (단, 부모타입에 있는 메서드만 허용)

 

 

// 정확히 하나의 파라미터 허용, 모든 타입 허용
pointcut.setExpression("execution(* *(*)");

// 파라미터 X
pointcut.setExpression("execution(* *()");

// String 타입 파라미터 허용
pointcut.setExpression("execution(* *(*)");

// String 타입으로 시작, 모든 파라미터
pointcut.setExpression("execution(* *(String, ..)");

// 모든 파라미터 허용
pointcut.setExpression("execution(* *(..)");

 

 

✔️ within


특정 타입 내의 조인 포인트들로 매칭을 제한

해당 타입이 매칭되면 그 안 메서드(조인 포인트)들이 자동으로 매칭

 

pointcut.setExpression("within(hello.aop.member.MemberServiceImpl)");

// within(hello.aop.member.*Service*)  
// within(hello.aop..*)

 

❗주의

표현식에 부모 타입 지정 금지❌

→ execution과 차이

 

 

 

✔️ args


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

 

execution과 차이점

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

args 는 부모 타입을 허용, 실제 넘어온 파라미터 객체 인스턴스를 보고 판단 (런타임)

  

// String 타입 args를 넘긴다면

// O
pointcut("args(Object)") // String, (*), (..), (String, ..)

// X        
pointcut("args()")
/**
 * 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은 파라미터 타입이 정확하게 맞아야하기 때문에 이러한 결과가 난다.

 

 

args 지시자는 파라미터 바인딩에서 주로 사용

 

 

✔️ @target, @within


@target : 실행 객체의 클래스에 주어진 타입의 애노테이션이 있는 조인 포인트 (부모 클래스의 메서드까지 다 적용)

@within : 주어진 애노테이션이 있는 타입 내 조인 포인트 (자신의 클래스에 정의된 메서드) 

@ClassAop
class Target{}
@target(hello.aop.member.annotation.ClassAop)
@within(hello.aop.member.annotation.ClassAop)

이들 또한 파라미터 바인딩에서 함께 사용

 

주의

args, @args, @target 포인트 지시자는 단독으로 사용해서는 절대 안됨 

스프링 내부에서 사용하는 final 빈들에도 적용하려 하기에 오류 발생

따라서 최대한 프록시 적용 대상을 축소하는 표현식과 함께 사용  

 

 

 

✔️ @annotation, @args


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

 

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

 

 

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

전달된인수의 런타임 타입에 @Check 애노테이션이 있는 경우에 매칭

 

@args(test.Check)

 

간단히 말해서 전달된 인수 클래스 안에 @Check 애노테이션이 있는 경우에 매칭된다는 의미이다.

 

 

✔️ bean


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

@Around("bean(orderService)" || bean(*Repository)") // orderService 빈과Repository로 끝나는 모든 빈

 

 

✔️매개변수 전달


포인트 컷 표현식을 사용해서 어드바이스에 매개변수 전달 가능

this, target, args, @target, @within, @annotation, @args

 

 

@Slf4j
@Import(ParameterTest.ParameterAspect.class)
@SpringBootTest
public class ParameterTest {

    @Autowired
    MemberService memberService;

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

    @Slf4j
    @Aspect
    static class ParameterAspect {

        @Pointcut("execution(* hello.aop.member..*.*(..))")
        private void allMember() {
        }

        @Around("allMember()")
        public Object logArgs1(ProceedingJoinPoint joinPoint) throws Throwable {
            Object arg1 = joinPoint.getArgs()[0];
        }

        @Around("allMember() && args(arg,..)")
        public Object logArgs2(ProceedingJoinPoint joinPoint, Object arg) throws Throwable {
        }

        @Before("allMember() && args(arg,..)")
        public void logArgs3(String arg) {
        }

        // 프록시
        @Before("allMember() && this(obj)")
        public void thisArgs(JoinPoint joinPoint, MemberService obj) {
        }

        // 실제 타겟
        @Before("allMember() && target(obj)")
        public void targetArgs(JoinPoint joinPoint, MemberService obj) {
        }

        // annotation
        @Before("allMember() && @target(annotation)")
        public void atTarget(JoinPoint joinPoint, ClassAop annotation) {
        }

        // annotation
        @Before("allMember() && @within(annotation)")
        public void atWithin(JoinPoint joinPoint, ClassAop annotation) {
        }

        // MethodAnnotation
        @Before("allMember() && @annotation(annotation)")
        public void atWithin(JoinPoint joinPoint, MethodAop annotation) {
        }
    }
}
  • 포인트컷의 이름과 매개변수의 이름을 맞춰야함 

  

 

✔️this, target


this : 스프링 AOP 프록시 

target : 실제 대상 

 

// proxy 객체를 보고 판단
this(hello.aop.member.MemberService)

// target 객체를 보고 판단
target(hello.aop.member.MemberService)

this, target은 적용 타입 하나를 정확하게 지정해야한다.

  • * 패턴 사용 불가
  • 부모 타입 허용

 

JDK 프록시의 경우

프록시는 Impl을 알 수 없다.  프록시는 단지 인터페이스만 알고 있을 뿐, 그 구현객체는 모른다. 따라서 this에서 자식타입으로 지정하면 AOP 적용대상에서 벗어난다.

 

CGLIB 의 경우

this여도 AOP 적용 대상이 된다. 자식은 부모를 상속하였기에 this로 자식타입을 지정해도 AOP 적용 대상이 된다. 

 

스프링 부트 기본 옵션은 CGLIB로 프록시 생성

JDK 를 사용하고 싶다면 

// application.properties
spring.aop.proxy-target-class=false //JDK 동적프록시

 

 

 

 

참고

this, target 지시자는 파라미터 바인딩에서 주로 사용

 

  

 

🔖 학습내용 출처


스프링 핵심 원리 - 고급편 / 김영한