포인트컷 지시자
AspectJ 는 포인트컷을 편리하게 표현하기 위해 특별한 표현식을 사용한다.
포인트컷 표현식은 AspectJ가 제공하는 포인트컷 표현식을 줄여서 말하는 것으로
execution 같은 포인트컷 지시자(Pointcut Designator, PCD)로 시작한다.
지시자 종류
- execution : 메소드 실행 조인 포인트를 매칭한다. 스프링 AOP 에서 가장 많이 사용하고 기능도 복잡하다.
- within : 특정 타입 내의 조인 포인트를 매칭한다.
- args : 인자가 주어진 타입의 인스턴스인 조인 포인트
- this : 스프링 AOP 프록시를 대상으로 하는 포인트
- target : 스프링 AOP 프록시가 가리키는 실제 대상으로 하는 조인 포인트
- @target : 실행 객체의 클래스에 주어진 타입의 어노테이션이 있는 조인 포인트
- @within : 주어진 어노테이션이 있는 타입 내 조인 포인트
- @annotation : 메서드가 주어진 어노테이션을 가지고 있는 조인 포인트를 매칭
- @args : 전달된 실제 인수의 런타임 타입이 주어진 타입의 어노테이션을 갖는 조인 포인트
- bean : 스프링 전용 포인컷 지시자, 빈의 이름으로 포인트컷을 지정
execution 을 가장 많이 사용하고 나머지는 자주 사용하지 않는다.
예제
@Target(ElementType.TYPE) // 클래스에 붙인 어노테이션
@Retention(RetentionPolicy.RUNTIME) // 실제 어플리케이션이 실행(runtime)할때까지 해당 어노테이션이 살아있음
public @interface ClassAop {
}
@Target(ElementType.METHOD)// 메서드에 붙일 수 있는 어노테이션
@Retention(RetentionPolicy.RUNTIME) // 실제 어플리케이션이 실행(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);
}
@Test
void printMethod() { // MemberServiceImpl.hello(String) 메서드의 정보 출력
// public java.lang.String hello.aop.member.MemberServiceImpl.hello(java.lang.String)
log.info("helloMethod={}", helloMethod);
}
}
@Test
void exactMatch() {
//public java.lang.String hello.aop.member.MemberServiceImpl.hello(java.lang.String)
pointcut.setExpression("execution(public String hello.aop.member.MemberServiceImpl.hello(String))");
// 해당 결과가 true로 반환되야 매칭됨
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue(); // (메서드, 대상 클래스)
}
매칭 조건이 정확하게 일치해야 반환된다.
- public / String / hello.aop.member.MemberServiceImpl / hello / (String)
execution
// 모든 메서드
execution(* *(..))
// 메서드 이름 매칭 : hel, el 이름으로 시작하는 메서드 실행
execution(* hel*(..))
execution(* *el*(..))
// . : 정확하게 해당 위치의 패키지, member패키지에 정의된 메서드 실행
execution(* hello.aop.member.*.*(..))
// .. : 해당 위치의 패키지와 그 하위 패키지도 포함, aop 혹은 그 하위 패키지에 정의된 메서드 실행
execution(* hello.aop..*.*(..))
// 부모 타입을 선언해도 자식 타입은 매칭됨 (MemberServiceImpl 의 부모 MemberService)
// MemberServiceImpl 에 선언한 internal 는 안됨)
execution(* hello.aop.member.MemberService.*(..))
// 파라미터 매칭
//String 타입의 파라미터 허용
execution(* *(String))
// 숫자와 무관하게 모든 파라미터, 모든 타입 허용, 파라미터가 없어도 됨
execution(* *(*))
// String 타입으로 시작, 숫자와 무관하게 모든 파라미터, 모든 타입 허용
execution(* *(String, ..))
execute 파라미터 매칭 규칙
- (String) : 정확하게 String 타입 파라미터
- () : 파라미터가 없어야 함
- (*) : 정확히 하나의 파라미터, 단 모든 타입을 허용
- (*, *) : 정확히 두개의 파라미터, 단 모든 타입을 허용
- (..) : 숫자와 무관한 모든 파라미터, 모든 타입 허용, 파라미터가 없어도 되는데 없으면 0..*로 이해하면 된다.
- (String, ..) : String 타입으로 시작해야함, 숫자와 무관하게 모든 파라미터, 모든 타입 허용
within
특정 타입이 매칭이 되면 그 안의 메서드들이 자동으로 매칭된다.
문법은 단순한데 execution 에서 타입 부분만 사용한다고 보면 된다.
execution과 다른점은 within사용 시 표현식에 부모타입을 지정하면 안된다. 정확하게 타입이 맞아야 한다.
within은 하나만 지정할 수 있다보니 잘 사용안하고 execution 을 더 잘 사용한다.
within(hello.aop.member.MemberServiceImpl)
within(hello.aop.member.*Service*)
within(hello.aop..*)
args
기본 문법은 execution의 args 부분과 같다.
둘의 차이점으로는
execution는 파라미터 타입이 정확하게 매칭되어야 하며, 클래스에 선언된 정보를 기반으로 판단한다.
args 는 부모 타입을 허용하고 실제 넘어온 파라미터 객체 인스턴스를 보고 판단한다.
args는 단독으로 사용되기 보다 파라미터 바인딩에서 주로 사용된다.
/**
* execution(* *(java.io.Serializable)): 메서드의 시그니처로 판단 (정적)
* args(java.io.Serializable): 런타임에 전달된 인수로 판단 (동적)
*/
@Test
void argsVsExecution() {
//Args
assertThat(pointcut("args(String)") // String 은 Object, java.io.Serializable 의 하위 타입
.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();
}
주의할 점으로는 args, @args, @target은 단독으로 사용하면 안된다.
@target, @within
@target 은 부모 클래스의 메서드까지 어드바이스를 다 적용하고, @within 은 자기자신의 클래스에 정의된 메서드에만 어드바이스를 적용한다.
@target, @within 도 파라미터 바인딩에서 함께 사용된다.
//@target: 인스턴스 기준으로 모든 메서드의 조인 포인트를 선정, 부모 타입의 메서드도 적용
@Around("execution(* hello.aop..*(..)) && @target(hello.aop.member.annotation.ClassAop)")
//@within: 선택된 클래스 내부에 있는 메서드만 조인 포인트로 선정, 부모 타입의 메서드는 적용 되지 않음
@Around("execution(* hello.aop..*(..)) && @within(hello.aop.member.annotation.ClassAop)")
@annotation, @args
@annotation 은 메서드가 주어진 어노테이션을 가지고 있는 조인포인트를 실행한다.
다음과 같이 메서드(조인 포인트)에 어노테이션이 있으면 매칭한다.
public class MemberServiceImpl {
@MethodAop("test value")
public String hello(String param) {
return "ok";
}
}
// MethodAop 가 있는곳에 적용
@Around("@annotation(hello.aop.member.annotation.MethodAop)")
public Object doAtAnnotation(ProceedingJoinPoint joinPoint) throws Throwable {
}
bean
스프링 빈의 이름으로 AOP 적용 여부를 지정한다.
// orderService, *Repository(OrderRepository) 의 메서드에 AOP가 적용됨
@Around("bean(orderService) || bean(*Repository)")
매개변수 전달
this, target, args,@target, @within, @annotation, @args 를 사용해서 어드바이스에 매개변수를 전달할 수 있다.
@Before("allMember() && args(arg,..)")
public void logArgs3(String arg) {
log.info("[logArgs3] arg={}", arg);
}
this, target
적용 타입 하나를 정확하게 지정해야 한다.
* 패턴을 사용할 수 없고 부모 타입을 허용한다.
this(hello.aop.member.MemberService)
target(hello.aop.member.MemberService)
두개의 차이는 무엇일까
실제 target 객체 대신에 프록시 객체가 스프링 빈으로 등록된다.
'공부 > Spring' 카테고리의 다른 글
[Spring] ReactiveMongoTemplate vs ReactiveMongoRepository (0) | 2024.11.07 |
---|---|
[Spring] MongoDB 조회 - Stream, Aggregation 비교 (0) | 2024.11.04 |
[Spring] Spring AOP 구현 예제 (0) | 2024.05.17 |
[Spring] AOP 개념 및 용어 정리 (0) | 2024.05.13 |
[Spring] @Aspect란, 생성 과정 설명 (0) | 2024.05.12 |