스프링 AOP에서 자동 프록시 생성기를 통해 @Aspect로 등록한 클래스를 자동으로 Advisor로 적용할 수 있다는 것을 살펴봤었는데 이번에는 포인트컷 지시자에 대해서 알아보자. 스프링 AOP에서는 AspectJ 문법을 사용한다고 하는데 그래서 그런지 알아야 할 내용이 꽤 많다. JoinPoint는 Aspect가 어느 곳에 적용될지 특정 지점을 나타내는데 보통 메서드 호출, 예외 발생 등 다양한 지점이 JoinPoint가 될 수 있다. Pointcut은 이 JoinPoint를 정의하는데 사용한다.
execution
일반적으로 가장 많이 사용하는 지시자이다. 복잡하지만 그만큼 다양한 기능을 제공한다.
부모 타입으로 지정해도 되지만 메서드는 부모 타입에 있는 메서드만 적용된다.
@Slf4j
@Aspect
static class AspectTest {
@Around("execution(* hello.proxy.myProxy.dynamic.MyTextImpl.*(..))")
public Object logic(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("=================");
Object result = joinPoint.proceed();
log.info("=================");
return result;
}
}
이런 식으로 사용한다. execution 안에 작성하는 내용은 [리턴 타입] [패키지명].[클래스명].[메서드명]([파라미터 타입])이다. 위의 예시에서는 리턴 타입은 모두 허용, hello.proxy.myProxy.dynamic 패키지의 MyTextImpl 클래스의 모든 메서드를 허용하는데 파라미터도 제한 없이 모두 허용한다. 사실 접근제어자, 선언타입, 예외도 포함될 수 있지만 생략 가능하다. 위에서는 선언타입은 포함시켰다.
* - 와일드 카드로써 사용된다. 어떤 타입이나 클래스, 메서드든지 허용한다.
.. - 하위 패키지까지 허용 / 메서드에서 모든 파라미터 허용
within
특정 타입 또는 패키지 내의 모든 조인 포인트에 적용한다.
@Slf4j
@Aspect
static class AspectTest2 {
@Around("within(hello.proxy.myProxy.dynamic.MyTextI*)")
public Object logic(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("=================");
Object result = joinPoint.proceed();
log.info("=================");
return result;
}
}
특정 타입만 지정해주면 된다. 타입명을 일부만 사용하고 *로 와일드카드 역할을 수행하도록 할 수 있다. execution은 부모타입을 지정해도 작동했지만 within은 타입이 완전히 동일해야 제대로 적용된다.
this
스프링 컨테이너에 등록된 빈에 적용한다. (프록시 객체)
@Slf4j
@Aspect
static class AspectTest5 {
@Around("this(hello.proxy.myProxy.dynamic.MyTextImpl)")
public Object logic(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("=================");
Object result = joinPoint.proceed();
log.info("=================");
return result;
}
}
target
실제 구현체를 의미한다. 이전에 JDK 동적 프록시나 CGLIB을 통해 여러 프록시를 구현하면서 내부에 실제 비즈니스 로직이 수행되는 target 객체가 있었는데 이것이라고 생각하면 된다.
@Slf4j
@Aspect
static class AspectTest5 {
@Around("target(hello.proxy.myProxy.dynamic.MyTextImpl)")
public Object logic(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("=================");
Object result = joinPoint.proceed();
log.info("=================");
return result;
}
}
this와 target을 사용할 때, JDK 동적 프록시와 CGLIB에서 각각 적용되는 대상이 달라진다. JDK 동적 프록시에서는 인터페이스를 사용하기 때문에 프록시가 인터페이스를 구현해서 만들어진다. 이 만들어진 구현체는 원래 내가 구현한 비즈니스 로직 클래스와 다른 클래스이기 때문에 구현 클래스로 this를 지정했다면 적용이 되지 않는다. 반면에 target은 말 그대로 target 객체의 타입을 보고 판단하기 때문에 구현 클래스로 사용해도 AOP를 적용 가능하다. 위의 코드가 그 예로 구현 클래스를 사용하기 때문에 this에는 AOP가 적용되지 않을 것이다. 하지만 실제로 테스트해보면 둘 다 적용이 된 것을 확인할 수 있는데, 그 이유는 스프링의 기본 값이 CGLIB이어서 상속을 이용하기 때문이다. application.properties에서
spring.aop.proxy-target-class=false
위와 같이 설정하고 다시 실행해보면 적용이 되지 않는 것을 알 수 있다.
args
메서드의 인자 타입을 통해 적용한다. 동적으로 런타임에 넘어오는 타입을 보고 결정한다. 부모 클래스 타입으로 와도 적용 가능하다. 주의해야 할 점은 args만 단독으로 사용하면 안 된다는 것이다. 이렇게 사용하면 해당 파라미터 타입을 가진 모든 스프링 빈이 프록시로 등록되는데 스프링은 기본으로 CGLIB를 사용하는데 클래스나 메서드에 final 키워드가 붙어 있으면 상속이 안 된다. 그렇기 때문에 프록시를 만들지 못하여 예외가 발생한다.
@Slf4j
@Aspect
static class AspectTest3 {
@Around("execution(* hello.proxy.myProxy.dynamic.MyTextImpl.*(..)) && args(String)")
public Object logic(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("=================");
Object result = joinPoint.proceed();
log.info("=================");
return result;
}
}
근데 위와 같이 사용할 바엔 아래처럼 사용하는게 간단하다...
@Slf4j
@Aspect
static class AspectTest3 {
@Around("execution(* hello.proxy.myProxy.dynamic.MyTextImpl.*(String))")
public Object logic(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("=================");
Object result = joinPoint.proceed();
log.info("=================");
return result;
}
}
@annotation
특정 어노테이션이 붙은 곳에 적용된다.
@Slf4j
public class MyTextImpl implements MyTextInterface {
@MyTextMethodAop
@Override
public void print(String text) {
log.info(text);
}
}
@Slf4j
@Aspect
static class AspectTest4 {
@Around("@annotation(hello.proxy.myProxy.autoproxy.MyTextMethodAop)")
public Object logic(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("=================");
Object result = joinPoint.proceed();
log.info("=================");
return result;
}
}
@within
특정 어노테이션이 붙은 해당 자식 메서드에만 적용한다. 부모 클래스의 메서드에는 적용 x
@Slf4j
@MyTextAop
public class MyTextImpl implements MyTextInterface {
@Override
public void print(String text) {
log.info(text);
}
}
@Slf4j
@Aspect
static class AspectTest4 {
@Around("execution(* hello.proxy.myProxy.dynamic.MyTextImpl.*(..)) && @within(hello.proxy.myProxy.autoproxy.MyTextAop)")
public Object logic(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("=================");
Object result = joinPoint.proceed();
log.info("=================");
return result;
}
}
@target
특정 어노테이션이 붙은 인스턴스 내의 모든 메서드에 Advice를 적용한다. (부모 클래스의 메서드까지도 적용 가능)
@Slf4j
@Aspect
static class AspectTest4 {
@Around("execution(* hello.proxy.myProxy.dynamic.MyTextImpl.*(..)) && @target(hello.proxy.myProxy.autoproxy.MyTextAop)")
public Object logic(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("=================");
Object result = joinPoint.proceed();
log.info("=================");
return result;
}
}
실행 시점에 프록시를 가지고 판단하기 때문에 단독으로 args, @target, @within을 사용하면 모든 스프링 빈을 프록시로 만들어 실행 시점에 판단하기 때문에 단독으로 사용하지 말고 execution을 사용해서 조건을 걸어주어야 한다.
'공부 > Spring' 카테고리의 다른 글
| [Spring] 톰캣 라이브러리로 직접 실행하기 및 jar의 문제점 (0) | 2024.08.05 |
|---|---|
| [Servlet] 서블릿 컨테이너 초기화 (0) | 2024.08.04 |
| [Spring] 자동 프록시 생성기 (0) | 2024.07.29 |
| [Spring] 빈 후처리기 (BeanPostProcessor) (0) | 2024.07.27 |
| [Spring] Advisor 이용해서 Proxy Factory 사용하기 (0) | 2024.07.26 |