해당 포스팅에 이어서 @target, @within 지시자도 알아보겠습니다.
- @target : 특정 애너테이션이 적용된 타깃 객체의 메서드에 대해 포인트컷을 설정합니다. 즉, 메서드가 호출되는 클래스의 인스턴스가 특정 애너테이션을 가지고 있을 때 해당 메서드를 가로챌 수 있습니다. 부모 클래스의 메서드까지 어드바이스를 적용할 수 있습니다.
- @within : 특정 애너테이션이 적용된 타입 내의 모든 조인 포인트를 지정합니다. 이는 애너테이션이 붙은 클래스의 메서드뿐만 아니라 그 하위 클래스의 메서드도 포함합니다. 부모 클래스의 메서드까지 어드바이스를 적용할 수 없습니다.
즉, @target과 @within 은 타입에 있는 애노테이션으로 AOP 적용 여부를 판단합니다.
@target
// 애너테이션 정의
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyServiceAnnotation {
}
@MyServiceAnnotation
public class MyService {
public void myMethod() {
// some logic
}
}
@Aspect
public class MyAspect {
// MyServiceAnnotation 이 붙은 타겟 객체를 가리키는 포인트컷
@Pointcut("@target(com.example.annotation.MyServiceAnnotation)")
public void serviceAnnotated() {}
@Before("serviceAnnotated()")
public void beforeAdvice() { // myMethod() 호출을 가로챌 수 있습니다.
// Advice logic
}
}
@within
// 애너테이션 정의
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyAspectAnnotation {
}
@MyAspectAnnotation
public class BaseService {
public void baseMethod() {
// some logic
}
}
public class DerivedService extends BaseService {
public void derivedMethod() {
// some logic
}
}
@Aspect
public class MyAspect {
// MyAspectAnnotation이 적용된 타겟 객체를 표현하는 포인트컷
@Pointcut("@within(com.example.annotation.MyAspectAnnotation)")
public void aspectAnnotated() {}
@Before("aspectAnnotated()")
public void beforeAdvice() {//
//BaseService 클래스와 DerivedService 클래스의 메소드 호출 모두를 가로챌 수 있습니다.
// Advice logic
}
}
추가 예제
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ClassAop {
}
@Slf4j
@Import({AtTargetAtWithinTest.Config.class})
@SpringBootTest
public class AtTargetAtWithinTest {
@Autowired
Child child;
@Test
void success() {
log.info("child Proxy={}", child.getClass());
child.childMethod(); //부모, 자식 모두 있는 메서드
child.parentMethod(); //부모 클래스만 있는 메서드
}
static class Config {
@Bean
public Parent parent() {
return new Parent();
}
@Bean
public Child child() {
return new Child();
}
@Bean
public AtTargetAtWithinAspect atTargetAtWithinAspect() {
return new AtTargetAtWithinAspect();
}
}
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();
}
//@within: 선택된 클래스 내부에 있는 메서드만 조인 포인트로 선정, 부모 타입의 메서드는 적용X
@Around("execution(* hello.aop..*(..)) && @within(hello.aop.member.annotation.ClassAop)")
public Object atWithin(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("[@within] {}", joinPoint.getSignature());
return joinPoint.proceed();
}
}
}
success() 메소드의 실행 결과는 아래와 같습니다.
실행 결과에서 볼 수 있듯이, 부모 클래스인 Parent 클래스에만 정의되어 있는 parentMethod() 에는 @target 만 적용되고, Child 클래스의 모든 메서드에는 @target과 @within 이 모두 적용되었음을 확인할 수 있습니다.
[참고] 주의사항
[args, @args, @target] 이 포인트컷 지시자들은 단독으로 사용하면 안됩니다.
예제에서도 execution(* hello.aop..*(..)) 와 함께 사용하여 적용 대상을 줄여주고 있습니다. args , @args , @target 은 실제 객체 인스턴스가 생성되고 실행될 때 어드바이스 적용 여부를 확인할 수 있습니다.
실행 시점에 일어나는 포인트컷 적용 여부도 결국 프록시가 있어야 실행 시점에 판단할 수 있습니다. 그런데 스프링 컨테이너가 프록시를 생성하는 시점은 스프링 컨테이너가 만들어지는 애플리케이션 로딩 시점입니다.
따라서 프록시가 없으면 실행 시점에 판단 자체가 불가능하기 때문에 args , @args , @target 같은 포인트컷 지시자가 있으면 스프링은 모든 스프링 빈에 AOP를 적용하려고 시도합니다.
문제는 이렇게 모든 스프링 빈에 AOP 프록시를 적용하려고 하면 스프링이 내부에서 사용하는 빈 중에는 final 로 지정된 빈들도 있기 때문에 오류가 발생할 수도 있습니다. 따라서 이러한 표현식은 최대한 프록시 적용 대상을 축소하는 표현식과 함께 사용해야 합니다.
"실행 시점에 일어나는 포인트컷 적용 여부도 결국 프록시가 있어야 실행 시점에 판단할 수 있습니다."에서 말하는 '실행'은 프록시로 생성된 인스턴스의 메서드 실행을 의미합니다. 즉, 애플리케이션의 전체 실행이 아니라, AOP가 적용된 메소드가 호출될 때를 말합니다. 프록시는 이 메소드 호출이 포인트컷에 정의된 패턴과 일치하는지를 실행 시점에 검사하여, 조건에 맞는 경우에만 어드바이스(Advice)를 적용합니다.
@target 지시자는 특정 어노테이션이 붙은 대상 객체에만 AOP를 적용하도록 합니다. 하지만 중요한 점은 스프링이 이런 포인트컷을 사용할 때, 실제 인스턴스가 생성되어 해당 어노테이션이 적용되었는지 확인하기 전에는 정확하게 AOP 적용 여부를 결정할 수 없다는 것입니다. 따라서 스프링은 포인트컷 조건을 충족할 가능성이 있는 모든 빈에 대해 프록시를 생성하고, 실제 메서드 실행 시에 포인트컷 조건이 맞는지를 검사하게 되는 것입니다.
결론, [args, @args, @target] 얘네들은 단독으로 사용하지 말자!!
소스코드
[참고자료]
김영한, "스프링 핵심 원리 - 고급편 ", 인프런
'Spring Boot' 카테고리의 다른 글
스프링 AOP의 내부호출 문제 및 해결법 (1) | 2024.10.03 |
---|---|
Spring AOP - 포인트컷(pointcut) 지시자(@annotation, this, target) (0) | 2024.10.02 |
Spring AOP - 포인트컷(pointcut) 지시자(within, args) (1) | 2024.10.02 |
Spring AOP - 포인트컷(pointcut) 지시자(execution) (0) | 2024.10.02 |
스프링 AOP - 어드바이스 종류 (@Around, @Before, @AfterReturning, @AfterThrowing, @After) (1) | 2024.10.01 |