포인트컷 지시자의 종류와 주의사항에 대해서 알아보도록 하겠습니다.
예시로 쓸 클래스
@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";
}
}
링크
특정 타입 내의 조인 포인트를 매칭합니다. 해당 타입이 매칭되면 그안의 조인포인트들이 자동으로 매칭
->execution에서 타입 부분만 사용한다고 생각
void withinExact() {
pointcut.setExpression("within(hello.aop.member.MemberServiceImpl)");
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
within표현식 안에 있는 모든 조인포인트들이 매칭됩니다. 하지만 표현식에 부모 타입을 지정하면 안됩니다. (execution은 가능)
void withinSuperTypeFalse() {
pointcut.setExpression("within(hello.aop.member.MemberService)");
assertThat(pointcut.matches(helloMethod,
MemberServiceImpl.class)).isFalse();
}
인자가 주어진 타입의 인스턴스인 조인 포인트로 매칭이 가능합니다.
문법은 execution의 args 부분이랑 같고 execution은 파라미터 타입이 정확하게 매칭 되어야하지만, args는 부모 타입을 허용합니다.
assertThat(pointcut("args(Object)")
.matches(helloMethod, MemberServiceImpl.class)).isTrue();
assertThat(pointcut("execution(* *(Object))") //매칭 실패
.matches(helloMethod, MemberServiceImpl.class)).isFalse();
execution은 메서드의 시그니쳐로 판단해서 정확하게 매칭이 되어야 하는데 , args는 런타임의 전달된 인수로 판단해서 부모타입도 허용합니다.
this
는 스프링 빈 객체(스프링 aop 프록시)를 대상으로 하는 조인 포인트고
target
은 target객체(스프링 aop 프록시가 가르키는 실제 대상)을 대상으로 하는 조인포인트입니다.
@target
: 실행객체의 클래스에 주어진 타입의 애노테이션이 있는 조인 포인트
@within
: 주어진 애노테이션이 있는 타입 내 조인 포인트
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ClassAop {
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MethodAop {
String value();
}
같이 타입이 있는 애노테이션으로 aop 적용 여부를 판단한다.
@Test
void success() {
log.info("child Proxy={}", child.getClass());
child.childMethod(); //부모, 자식 모두 있는 메서드
child.parentMethod(); //부모 클래스만 있는 메서드
}
static class Parent {
public void parentMethod(){} //부모에만 있는 메서드
}
@ClassAop
static class Child extends Parent {
public void childMethod(){}
}
@Slf4j
@Aspect
static class AtTargetAtWithinAspect {
//@target: 인스턴스 기준으로 모든 메서드의 조인 포인트를 선정, 부모 타입의 메서드도 적용
@Around("execution(* hello.aop..*(..)) &&
@target(hello.aop.member.annotation.ClassAop)")
public Object atTarget(ProceedingJoinPoint joinPoint) throws Throwable
{
log.info("[@target] {}", joinPoint.getSignature());
return joinPoint.proceed();
}
classAop 어노테이션이 있는 클래스들의 모든 조인포인트들에 aop가 걸립니다. 부모타입도 포함
//@within: 선택된 클래스 내부에 있는 메서드만 조인 포인트로 선정, 부모 타입의 메서드는
적용되지 않음
@Around("execution(* hello.aop..*(..)) &&
@within(hello.aop.member.annotation.ClassAop)")
public Object atWithin(ProceedingJoinPoint joinPoint) throws Throwable
{
log.info("[@within] {}", joinPoint.getSignature());
classAop 어노테이션이 있는 클래스들의 모든 조인포인트들에 aop가 걸립니다. 부모타입은 포함X
메서드가 주어진 어노테이션을 가지고 있는 조인 포인트를 매칭합니다.
public class MemberServiceImpl {
@MethodAop("test value")
public String hello(String param) {
return "ok";
}
}
@Around("@annotation(hello.aop.member.annotation.MethodAop)")
public Object doAtAnnotation(ProceedingJoinPoint joinPoint) throws
Throwable {
log.info("[@annotation] {}", joinPoint.getSignature());
return joinPoint.proceed();
}
MemberServiceImpl은 조인포인트에 MethodAop애노테이션을 가지고 있으니 매칭합니다.
전달된 실제 인수의 런타인 타입이 주어진 타입의 애노테이션을 갖는 조인 포인트
@Around("bean(orderService) || bean(*Repository)")
public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("[bean] {}", joinPoint.getSignature());
return joinPoint.proceed();
}
이러면 빈의 이름이 orderService이거나 Repository로 끝나는 곳에 aop가 걸립니다.
- 주의사항
args, @args, @target은 혼자 쓰면 안됩니다. 왜냐하면 얘네들은 실제 객체 인스턴스가 생성되고 실행될때 어드바이스 적용 여부를 알수 있는데 실행 시점에 일어나는 포인트 컷 적용 여부도 프록시가 있어야 실행 시점에 판단할수 있습니다. 즉 얘네들이 있으면 스프링은 모든 스프링 빈에 aop를 적용하기 때문에 스프링 내부 빈중 final로 지정한 빈이 있어서 오류가 발생합니다.