
다음 포인트컷 지시자는 단독으로 사용하면 안된다. args, @args, @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();
}
해당 예제를 보면 execution(* hello.aop..*(..)) 를 통해 적용 대상을 줄여준 것을 확인할 수 있다.
= AOP 적용 대상을 hello.aop 하위의 패키지들로 한정한다.
args , @args , @target 은 실제 객체 인스턴스가 생성되고 실행될 때 어드바이스 적용 여부를 확인할 수 있 다. = 런타임 시점에서 Advice 적용 여부를 확인하겠다는 뜻이다.
메소드 실행 시점에 일어나는 포인트컷 적용 여부도 결국 프록시가 있어야 실행 시점에 판단할 수 있다.
프록시가 없다면 판단 자체가 불가능하다. 그런데 스프링 컨테이너가 프록시를 생성하는 시점은 스프링 컨테이너가 만들어지는 애플리케이션 로딩 시점에 적용할 수 있다.
따라서 args , @args , @target 같은 포인트컷 지시자가 있으면 스프링은 모든 스프링 빈에 AOP를 적용하려고 시도한다. 앞서 설명한 것 처럼 프록시가 없으면 실행 시점에 판단 자체가 불가능하다.
문제는 이렇게 모든 스프링 빈에 AOP 프록시를 적용하려고 하면 스프링이 내부에서 사용하는 빈 중에는 final 로 지정된 빈들도 있기 때문에 오류가 발생할 수 있다. 따라서 이러한 표현식은 최대한 프록시 적용 대상을 축소하는 표현식과 함께 사용해야 한다.
Q, @within은 왜 단독으로 사용해도 되는 것일까?
Spring AOP에서 포인트컷(Pointcut)을 설정할 때 @within과 @target은 유사한 기능을 제공하지만, 동작 방식에서 중요한 차이가 있다. 이를 통해 @within은 단독으로 사용해도 되지만, @target은 단독으로 사용하면 안 되는 이유를 설명할 수 있다.
@within의 동작 방식
- 클래스 레벨에서 어노테이션 확인: @within은 프록시가 생성되는 시점에 클래스의 정적 정의(컴파일 타임) 를 기반으로 어노테이션을 확인한다. 즉, 현재 AOP를 적용하고자 하는 클래스 자체에 어노테이션이 존재하면 그 클래스 내의 모든 메서드에 대해 AOP를 적용할 수 있다.
- 부모 클래스는 포함하지 않는다.: @within은 상속 계층에 있는 부모 클래스나 인터페이스를 고려하지 않는다. 즉, 현재 클래스에 어노테이션이 직접 적용되어 있을 때만 AOP 적용 대상이 된다. 이는 클래스 선언부에서 어노테이션을 보고 판단하는 방식이기 때문에 부모 클래스나 인터페이스에 있는 어노테이션은 무시된다.
@target의 동작 방식
- 런타임 객체 기준: @target은 런타임 시점에 객체의 타입(클래스 레벨)에서 특정 어노테이션이 존재하는지를 기준으로 AOP를 적용한다. 여기서 중요한 점은 런타임 시점에 프록시가 감싸고 있는 실제 타겟 객체의 클래스가 AOP 적용 대상인지 확인한다는 것이다.
- 부모 클래스까지 탐색: @target은 타겟 객체가 상속 관계에 있을 때, 부모 클래스나 상위 인터페이스에 있는 어노테이션도 확인할 수 있다. 즉, 자식 클래스에는 어노테이션이 없더라도 부모 클래스나 인터페이스에 어노테이션이 존재하면 해당 어노테이션이 적용된 것으로 간주하고 AOP를 적용할 수 있다. 이는 런타임 시점에 해당 객체의 실제 타입 정보를 확인하기 때문에 가능한 일이다.
Q, 컴파일 시점에도 extends와 같이 상속정보를 같이 컴파일 할텐데, 왜 런타임 시점에서만 상속정보를 확인하여 AOP를 적용하는 것일까?
Spring AOP에서 @within과 @target이 상속 계층을 처리하는 방식에 차이가 있는 이유는 프록시 기반 AOP의 특성과 설계 철학에 기인한다.
상속 정보는 컴파일 시점에도 존재하지만, Spring AOP가 상속된 어노테이션을 확인할 때 런타임 시점에서만 적용하는 이유는 컴파일 타임과 런타임의 역할 분리, 그리고 프록시의 동작 방식에 있다.
이 부분이 좀 어렵게 다가올텐데, 구체적으로 설명하겠다.
1. 프록시 기반 AOP의 특성
Spring AOP는 프록시 패턴을 사용하여 AOP를 구현한다. 여기서 프록시는 실제 객체를 감싸서 메서드 호출을 가로채고, AOP 로직을 주입하는 방식으로 동작하게 되는데, 프록시는 인터페이스 기반 또는 클래스 기반으로 생성될 수 있고, 이 프록시는 런타임 시점에만 객체의 실제 상태나 상속 계층을 참조할 수 있다.
그렇기에 컴파일 시점에 특정 어노테이션이 있는지 확인하는 @within은 부모 클래스에 대해 Advice를 적용하지 못하는 것이다.
2. 컴파일 시점의 한계와 역할
컴파일 시점에도 클래스 상속 정보는 존재한다. 하지만 AOP 적용을 컴파일 시점에만 결정하지 않는 이유는 Spring AOP가 동적 프록시를 활용하기 때문이다. 다음과 같은 이유로 런타임 시점에서 AOP 적용 여부를 결정하게 된다.
- 프록시와 실제 객체 분리: Spring AOP는 프록시 객체를 통해 동작하는데, 이 프록시는 런타임에 동적으로 생성되며 실제 타겟 객체를 감싸게 된다. 컴파일 시점에는 이 프록시 객체가 존재하지 않기 때문에, 상속 관계를 기반으로 한 어노테이션을 컴파일 시점에 적용할 수 없다.
- 유연성: 런타임 시점에 상속 정보를 확인하면, Spring AOP는 좀 더 유연하게 객체의 상속 관계를 다룰 수 있다. 즉, 부모 클래스에 어노테이션이 정의되어 있더라도 런타임에 자식 클래스에서 이를 기반으로 AOP를 적용할 수 있다. 이는 런타임 시점에서 객체 상태를 확인하는 것이 더 유연하고 실제 객체의 상황을 반영할 수 있는 장점을 제공하기 때문이다.
정리
args , @args , @target 같은 포인트컷 지시자가 있으면 스프링은 모든 스프링 빈에 AOP를 적용하려고 시도한다. 앞서 설명한 것 처럼 프록시가 없으면 실행 시점에 판단 자체가 불가능하다.
그렇다면 @within은 어떨까?
이전의 설명대로 라면,
@within은 컴파일 시점에 어노테이션을 기반으로 적용할 클래스가 이미 정해지므로, 모든 클래스에 대해 프록시를 생성하지는 않는다. 즉, @within은 어노테이션이 달린 클래스에 대해서만 프록시를 생성한다.
그래서 단독으로 포인트컷으로 사용할 수 있는 것이다.
'Spring' 카테고리의 다른 글
스프링 트랜잭션 관리: 추상화에서 AOP까지 (0) | 2024.06.03 |
---|---|
안전한 데이터 조작을 위한 스프링 트랜잭션 이해 (0) | 2024.03.21 |
효율적인 JDBC 프로그래밍을 위한 DataSource, Connection Pool 기술 - 2 (1) | 2024.03.13 |
효율적인 JDBC 프로그래밍을 위한 DataSource, Connection Pool 기술 - 1 (0) | 2024.03.13 |
API마다 다르게 예외처리하는 방법 (0) | 2024.03.09 |

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!