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();
}
}
소스 코드
[참고자료]
김영한, "스프링 핵심 원리 - 고급편 ", 인프런
728x90
반응형