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 : 스프링 전용 포인트컷 지시자, 빈의 이름으로 포인트컷을 지정한다.
@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);
}
@Slf4j
@ClassAop
@Service
@RequiredArgsConstructor
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(접근제어자? 반환타입 선업타입?메소드이름(파라미터) 예외)
- 메소드 실행 조인 포인트를 매칭한다.
- ?는 생략할 수 있다.
*
같은 패턴을 지정할 수 있다.
@BeforeEach
public void init() throws NoSuchMethodException {
helloMethod = MemberServiceImpl.class.getMethod("hello", String.class);
}
@Test
void exactMatch() {
// public java.lang.String hello.aop.member.service.MemberServiceImpl.hello(java.lang.String)
pointcut.setExpression("execution(public String hello.aop.member.service.MemberServiceImpl.hello(String))");
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}
AspectJExpressionPointcut에 pointcut.setExpression을 통해서 포인트컷 표현식을 적용할 수 있다.
pointcut.matches(메소드, 대상클래스)를 실행하면 지정한 포인트컷 표현식의 매칭 여부를 true, false로 반환한다.
- 접근제어자? : public
- 반환타입 : String
- 선언타입? : hello.aop.member.service.MemberServiceImpl
- 메소드 이름 : hello
- 파라미터 : (String)
- 예외? : 생략
@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.service.MemberServiceImpl.hello(..))");
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}
@Test
void packageExactMatch2() {
pointcut.setExpression("execution(* hello.aop.member.service.*.*(..))");
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}
@Test
void packageMatchFalse() {
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();
}
hello.aop.member.service.(1).(2)
(1) : 타입
(2) : 메소드 이름
패키지에서
.
,..
의 차이를 이해해야 한다.
.
: 정확하게 해당 위치의 패키지..
: 해당 위치의 패키지와 그 하위 패키지도 포함
@Test
void typeExactMatch() {
pointcut.setExpression("execution(* hello.aop.member.service.MemberServiceImpl.*(..))");
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}
@Test
void typeMatchSuperType() {
pointcut.setExpression("execution(* hello.aop.member.service.MemberService.*(..))");
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}
typeExactMatch()는 타입 정보가 정확하게 일치하기 때문에 매칭된다.
execution에서는 MemberService 처럼 부모 타입을 선언해도 그 자식 타입은 매칭된다. 다형성에서 부모타입 = 자식타입 이 할당 가능 하나는 점을 떠올려 보면 된다.
@Test
void typeMatchInternal() throws NoSuchMethodException {
pointcut.setExpression("execution(* hello.aop.member.service.MemberServiceImpl.*(..))");
final 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.service.MemberService.*(..))");
final Method internalMethod = MemberServiceImpl.class.getMethod("internal", String.class);
assertThat(pointcut.matches(internalMethod, MemberServiceImpl.class)).isFalse();
}
- typeMatchInternal()의 경우 MemberServiceImpl를 표현식에 선언했기 때문에 그 안에 있는 internal(STring) 메소드도 매칭 대상이 된다.
- typeMatchNoSuperTypeMethodFalse()는 표현식에 부모타입인 MemberService를 선언했다.
그런데 자식 타입인 MemberServiceImpl의 internal(String) 메소드를 매칭하려 한다.
이 경우 매칭에 실패한다. MemberService에는 internal(String) 메소드가 없기 때문이다.- 부모 타입을 표현식에 선언한 경우 부모 타입에서 선언한 메소드가 자식 타입에 있어야 매칭에 성공한다.
그래서 부모 타입에 있는 hello(String) 메소드는 매칭에 성공하지만 부모 타입에 없는 internal(String)은 매칭에 실패한다.
// 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)).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)
void argsMatchComplex() {
pointcut.setExpression("execution(* *(String, **))");
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}
- (String) : 정확하게 String 타입 파라미터
- () : 파라미터가 없어야 한다.
- (
*
) : 정확히 하나의 파라미터, 단 모든 타입을 허용한다.- (
*
,*
) : 정확히 두개의 파라미터, 단 모든 타입을 허용한다.- (..) : 숫자와 무관하게 모든 파라미터, 모든 타입을 허용한다. 참고로 파라미터가 없어도 된다.
0..*
로 이해하면 된다.- (String,
**
) : String 타입으로 시작해야 한다. 숫자와 무관하게 모든 파라미터, 모든 타입을 허용한다.
- ex) (String), (String, Xxx), (String, Xxx, Xxx) 허용
within 지시자는 특정 타입 내의 조인 포인트에 대한 매칭을 제한한다.
쉽게 이야기해서 해당 타입이 매칭되면 그 안에 메소드(조인 포인트)들이 자동으로 매칭된다.
execution에서 타입 부분만 사용한다고 보면 된다.
public class WithinTest {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
Method helloMethod;
@BeforeEach
public void init() throws NoSuchMethodException {
helloMethod = MemberServiceImpl.class.getMethod("hello", String.class);
}
@Test
void withinExact() {
pointcut.setExpression("within(hello.aop.member.service.MemberServiceImpl)");
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}
@Test
void withinStar() {
pointcut.setExpression("within(hello.aop.member.service.*Service*)");
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}
@Test
void withinSubPackage() {
pointcut.setExpression("within(hello.aop.member..*)");
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}
}
within 사용시 주의 해야 할 점이 있다.
표현식에 부모 타입을 지정하면 안된다는 점이다.
정확하게 타입이 맞아야 한다.
이 부분에서 execution과 차이가 난다.
@Test
@DisplayName("타겟의 타입에만 적용 ,인터페이스를 선정하면 안된다.")
void withinSuperTypeFalse() {
pointcut.setExpression("within(hello.aop.member.service.MemberService)");
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isFalse();
}
@Test
@DisplayName("execution은 타입 기반, 인터페이스 선정 가능")
void executionSuperTypeTrue() {
pointcut.setExpression("execution(* hello.aop.member.service.MemberService.*(..))");
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}
부모 타입(MemberService 인터페이스) 지정 시 within은 실패하고 execution은 성공하는 것을 확인할 수 있다.
- args : 인자가 주어진 타입의 인스턴스인 조인 포인트로 매칭
- 기몬 문법은 execution의 args 부분과 같다 .
- execution은 파라미터 타입이 정확하게 매칭되어야 한다.
execution은 클래스에 선언된 정보를 기반으로 판단한다.- args는 부모 타입을 허용한다.
args는 실제 넘어온 파라미터 객체 인스턴스를 보고 판단한다.
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)")
.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();
}
}
- 자바가 기본으로 제공하는 String은 Object, java.io.Serializable의 하위 타입이다.
- 정적으로 클래스에 선언된 정보만 포고 판단하는 execution(* *(Object))는 매칭에 실패한다.
- 동적으로 실제 파라미터로 넘어온 객체 인스턴스로 판단하는 args(Object))는 매칭에 성공한다. (부모 타입 허용)
args 지시자는 단독으로 사용되기 보다는 파라미터 바인딩에서 주로 사용된다.