FireDrago
[Spring] 스프링 AOP - 포인트컷 1 본문
포인트컷
애스펙트J는 포인트컷을 편리하게 표현하기 위한 특별한 표현식을 제공한다
포인트컷 지시자의 종류
- execution : 메소드 실행 조인 포인트를 매칭한다. 스프링 AOP에서 가장 많이 사용하고, 기능도 복잡하다.
- within : 특정 타입 내의 조인 포인트를 매칭한다.
- args : 인자가 주어진 타입의 인스턴스인 조인 포인트
- this : 스프링 빈 객체(스프링 AOP 프록시)를 대상으로 하는 조인 포인트
- target : Target 객체(스프링 AOP 프록시가 가리키는 실제 대상)를 대상으로 하는 조인 포인트
- @target : 실행 객체의 클래스에 주어진 타입의 애노테이션이 있는 조인 포인트
- @within : 주어진 애노테이션이 있는 타입 내 조인 포인트
- @annotation : 메서드가 주어진 애노테이션을 가지고 있는 조인 포인트를 매칭
- @args : 전달된 실제 인수의 런타임 타입이 주어진 타입의 애노테이션을 갖는 조인 포인트
- bean : 스프링 전용 포인트컷 지시자, 빈의 이름으로 포인트컷을 지정한다
// 클래스에 부착하는 어노테이션
@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";
}
}
애노테이션 ClassAop는 클래스에 붙이고, MethodAop는 메소드에 붙인다.
String value() 를 통해 애노테이션 설정에 value 값을 설정할 수 있도록 했다.
@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() {
// public java.lang.String
// hello.aop.member.MemberServiceImpl.hello(java.lang.String)
log.info("helloMethod={}", helloMethod);
}
}
AspectJExpressionPointcut 이 바로 포인트컷 표현식을 처리해주는 클래스다. 포인트컷 표현식을 지정하면 된다.
AspectJExpressionPointcut 는 상위에 Pointcut 인터페이스를 가진다.
execution
execution (접근제어자? 반환타입 선언타입?메서드이름(파라미터) 예외?)
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
?는 생략할 수 있다. * 같은 패턴을 지정할 수 있다.
/**
* 메서드 이름 포인트컷
*/
@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 packageExactMathc1() {
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 packageExactFalse() {
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();
}
hello.aop.member.*(1).*(2)
- (1) : 타입
- (2) : 메서드
. : 정확하게 해당 위치의 패키지
.. : 해당 위치의 패키지와 그 하위 패키지도 포함
@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 {
pointcut.setExpression(
"execution(* hello.aop.member.MemberService.*(..))"
);
Method internalMethod = MemberServiceImpl.class.getMethod("internal", String.class);
// 부모 타입을 표현식에 선언한 경우 부모 타입에서 선언한 메서드가 자식 타입에 있어야 매칭에 성공한다
assertThat(pointcut.matches(internalMethod, MemberServiceImpl.class)).isFalse();
}
execution 에서는 MemberService 처럼 부모 타입을 선언해도 그 자식 타입은 매칭된다.
typeMatchNoSuperTypeMethodFalse() 표현식에 부모 타입인 MemberService 를 선언했다.
그런데 자식 타입인 MemberServiceImpl 의 internal(String) 메서드를 매칭하려 한다.
이 경우 매칭에 실패한다. MemberService 에는 internal(String) 메서드가 없다.
/**
* 파라미터 매칭
*/
// String 타입의 파라미터 허용
@Test
void argsMatch() {
pointcut.setExpression(
"execution(* *(String))"
);
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}
// 파라미터가 없어야 함
@Test
void argsMatchNoArgs() {
pointcut.setExpression(
"execution(* *())"
);
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}
// 정확히 하나의 파라미터 허용, 모든 타입 허용
@Test
void argsMatchStar() {
pointcut.setExpression(
"execution(* *(*))"
);
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}
// 파라미터의 갯수와 무관하고, 모든 타입 허용
// 파라미터가 없어도 된다.
@Test
void argsMatchAll() {
pointcut.setExpression(
"execution(* *(..))"
);
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}
// String 타입으로 시작, 숫자와 무관하게 모든 파라미터, 모든 타입 허용
@Test
void argsMatchComplex() {
pointcut.setExpression(
"execution(* *(String,..))"
);
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}
execution 파라미터 매칭 규칙
- (String) : 정확하게 String 타입 파라미터
- () : 파라미터가 없어야 한다.
- (*) : 정확히 하나의 파라미터
- (*,*) : 정확히 두개의 파라미터, 모든 타입 허용
- (..) : 갯수와 무관하게 모든 파라미터, 모든 타입 허용 (파라미터가 없어도 된다.)
within
within 지시자는 특정 타입 내의 조인 포인트들로 매칭을 제한한다. 쉽게 이야기해서 해당 타입이 매칭되면 그 안의
메서드(조인 포인트)들이 자동으로 매칭된다.
/**
* within은 표현식에 부모 타입을 지정하면 안된다. 정확하게 타입이 맞아야 한다.
* execution과 차이가 난다.
*/
@BeforeEach
public void init() throws NoSuchMethodException {
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() {
pointcut.setExpression(
"within(hello.aop.member.*Service*)"
);
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}
within 사용시 표현식에 부모 타입을 지정하면 안된다.
'프로그래밍 > Spring' 카테고리의 다른 글
| [Spring] 스프링 AOP - 실무 주의사항 (0) | 2024.05.22 |
|---|---|
| [Spring] 스프링 AOP - 포인트컷 2 (0) | 2024.05.20 |
| [Spring] 스프링 AOP 구현 (0) | 2024.05.18 |
| [Spring] 스프링 AOP 개념 (0) | 2024.05.17 |
| [Spring] 스프링이 지원하는 프록시 (0) | 2024.05.15 |