≣ 목차
포인트컷 표현식
포인트컷(Pointcut)은 핵심 기능에 부가 기능을 추가할지 여부를 결정하는 필터링 역할을 합니다. 포인트컷 표현식은 이러한 부가 기능을 적용할 때 사용되는 기준을 정의합니다. 이 표현식은 패키지 이름, JDK 정규 표현식, AspectJ 표현식 등 여러 방법으로 설정할 수 있으며, 그중에서도 AspectJ 표현식이 가장 널리 사용됩니다.
포인트컷 지시자 종류
포인트컷 지시자는 포인트컷 표현식을 정의할 때 어떤 방법을 이용해서 작성할 지 도와주는 표현입니다.
포인트컷 표현식은 And, or, not(&&, ||, !)와 같은 논리 연산자를 함께 사용할 수 있으며 * 같은 표현도 사용할 수 있습니다.
execution : 메소드 실행 조인 포인트를 매칭 (가장 많이 사용)
within : 정확한 특정 타입 내의 조인 포인트들로 매칭을 제한
args : 실제 넘어온 파라미터를 기반으로 객체 인스턴스에 대해 판단
this : 스프링 빈 객체(스프링 AOP 프록시)를 대상으로 하는 조인 포인트
target : Target 객체(스프링 AOP 프록시가 적용되는 실제 객체)를 대상으로 하는 조인 포인트
@target : 실행 객체의 클래스에 주어진 타입의 애노테이션이 있는 조인 포인트
@within : 주어진 애노테이션이 있는 타입 내 조인 포인트
@annotation : 메서드가 주어진 애노테이션을 가지고 있는 조인 포인트를 매칭
@args : 전달된 실제 인수의 런타임 타입이 주어진 타입의 애노테이션을 갖는 조인 포인트
bean : 스프링 전용 포인트컷 지시자, 빈의 이름으로 포인트컷을 지정
execution
execution 문법
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
modifiers-pattern = 접근제어자(생략 가능)
ret-type-pattern = 반환타입
declaring-type-pattern = 선언타입 (생략 가능)
name-pattern = 메소드이름
param-pattern = 파라미터
throws-pattern = 예외 (생략 가능)
예시) execution(public String hello.aop.member.MemberServiceImpl.hello(String)) -
예시) execution(* *(..)) - 최대 생략 표현
간단한 예시로 execution 문법에 대해서 알아보겠습니다.
--------- 메서드 이름 매칭 --------------
// 모든 public 메서드 실행
execution(public * *(..))
// tes이름으로 시작하는 모든 메서드 실행
execution(* tes*(..))
--------- 패키지 경로 매칭 --------------
// MemberServiceImpl에 정의된 모든 메서드 실행
execution(* hello.aop.order.member.MemberServiceImpl.*(..))
// member패키지에 정의된 클래스들의 메서드 실행
execution(* hello.aop.order.member.*.*(..)))
// member 하위 패키지에 정의된 클래스들의 메서드 실행
execution(* hello.aop.order.member..*.*(..))
--------------타입 매칭(부모 타입 허용)-------------
// MemberServiceImpl의 부모 타입의 MemberSerivce
//부모 타입을 선언한 메소드만 매칭 가능 즉 오버라이딩한 메소드만 가능
execution(* hello.aop.order.member.MemberService.*(..))
-----------파라미터-------------
//String 타입의 파라미터 한 개만 허용
execution(* *(String))
//파라미터가 없는 경우
execution(* *())
//정확히 하나의 파라미터 허용, 모든 타입 허용
execution(* *(*))
//파라미터 갯수과 상관없이 모든 타입의 파라미터 허용
//파라미터가 없어도 허용
execution(* *(..))
//String 타입으로 시작, 모든 타입의 파라미터 허용
// (String), (String, xxx), (String, xxx, xxx) 가능
execution(* *(String, ..))
- . : 정확하게 해당 위치의 패키지
- .. : 해당 위치의 패키지와 그 하위 패키지도 포함
within
within은 특정(정확한) 타입에 대해 매칭하는 지시자로 해당 타입이 매칭되면 그 안의 메서드(조인 포인트)들이 자동으로 매칭됩니다. 즉 execution에서 선언 타입(declaring-type-pattern) 부분만 작성하면 된다고 생각하면 됩니다.
within을 사용할 때 주의해야 할 점은 execution과는 다르게 부모, 자식 타입에 상속 관계를 고려하지 않는다는 점입니다. 이는 within의 특징상 정확하게 타입이 맞아야 하기 때문입니다.
// 부모 = MemberSerivce 자식 = MemberServiceImpl이고, hello 메소드를 오버라이딩한 상황
public class WithinTest {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
Method helloMethod;
@BeforeEach
public void init() throws NoSuchMethodException {
helloMethod = MemberServiceImpl.class.getMethod("hello", String.class);
}
@Test
void withinTest1() {
pointcut.setExpression("within(hello.aop.order.member.MemberService)");
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isFalse();
}
@Test
void withinTest2() {
pointcut.setExpression("within(hello.aop.order.member.MemberServiceImpl)");
assertThat(pointcut.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}
}
결과

args
args는 실제 넘어온 파라미터를 기반 판단하며, execution의 기본 문법과 동일합니다. 이 두 지사지의 차이점은 execution 지사자가 메서드의 파라미터 타입이 정확히 일치해야 매칭되는 반면, args 지사자는 부모 타입을 허용한다는 점입니다. 즉, execution은 특정 타입의 인자만 사용할 수 있지만, 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;
}
/**
* 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();
}
}
그 외 포인트컷 지시자 사용 예시 및 설명
// @Transactional 애너테이션이 있는 인스턴스의 모든 메서드를 조인 포인트로 적용
// 즉, 부모 타입의 어드바이스(메소드) 적용
@target(org.springframework.transaction.annotation.Transactional)
// @Transactional 애너테이션이 있는 해당 타입 내에 있는 메서드만 조인 포인트로 적용
// 즉 부모 타입의 어드바이스(메소드) 적용x
@within(org.springframework.transaction.annotation.Transactional)
// 실행 메서드에 @Transactional 애너테이션이 있는 조인 포인트
@annotation(org.springframework.transaction.annotation.Transactional)
// 전달된 실제 인수의 런타임 타입이 주어진 타입의 @Classified 애너테이션을 갖는 조인 포인트
@args(com.xyz.security.Classified)
// AccountService !!프록시 객체!!가 구현하는 모든 메소드
this(com.xyz.service.AccountService)
// AccountService !!실제 객체!!가 구현하는 모든 메소드
target(com.xyz.service.AccountService)
// tradeService 라는 이름을 가진 스프링 빈의 모든 메소드
bean(tradeService)
// *Service 라는 이름을 가진 스프링 빈의 모든 메소드
bean(*Service)
args, @args, @target 사용 시 주의사항
args, @args, @target은 실행 시점에서 어드바이스가 적용될 수 있는지를 판단합니다. 이러한 판단은 기본적으로 프록시 객체가 필요합니다. 그러나 특정 상황에서는 실제 객체에 대한 프록시 객체가 존재하지 않을 수 있습니다. 이 경우, 스프링은 모든 빈에 대해 프록시를 생성하려고 시도합니다.
그러나 이 과정에서 final로 선언된 빈들 때문에 오류가 발생할 수 있습니다. 스프링은 기본적으로 CGLIB를 사용하여 프록시 객체를 생성하는데, final 클래스나 메서드는 CGLIB에 의해 프록시 객체를 생성할 수 없기 때문에 문제가 발생합니다.
따라서 args, @args, @target을 사용할 때는 해당 객체에 프록시 객체가 제대로 생성되는지를 확인하는 것과 execution과 같은 다른 지시자를 사용해 범위를 줄이는 방법이 중요합니다.
'Spring' 카테고리의 다른 글
| [Spring] Spring AOP의 한계 (0) | 2024.10.04 |
|---|---|
| [Spring] Spring AOP 사용 시 내부 호출 문제 해결 방법 (0) | 2024.10.03 |
| [Spring] Spring AOP 사용 방법 - @Aspect 활용 (0) | 2024.09.27 |
| [Spring] 스프링 AOP 정리 (0) | 2024.09.26 |
| [Spring] @Aspect 정리 (0) | 2024.09.26 |