[Spring AOP] 포인트컷 표현식 - args

목차

포인트컷 표현식 - args

args 는 메서드의 매개변수를 기준으로 AOP 를 적용

args는 메서드의 매개변수를 기준으로 매칭하기 위한 표현식입니다. 메서드의 매개변수 타입이나 값을 기준으로 특정 메서드에만 AOP를 적용할 수 있습니다.

기본 문법

args(타입1, 타입2, ...)
표현식 설명
args() 파라미터가 없는 메서드
args(*) 파라미터가 정확히 1개인 메서드
args(..) 파라미터 개수 및 타입 제한 없음
args(String) 파라미터가 String 타입 1개인 메서드
args(String,..) 첫 번째 파라미터가 String 이고 나머지는 제한 없음
args(Object) 파라미터가 Object 또는 그 하위 타입 1개인 메서드 (런타임 판단)

테스트를 위한 객체

@ClassAop
@Component
public class MemberServiceImpl implements MemberService {

@Override
@MethodAop("test value")
public String hello(String param) {
return "ok";
}

private String internal(String param) {
return "ok";
}
}

args 를 이용해 적용 여부 판단

@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;
}

@Test
void args() {
// hello(String)과 매칭
assertThat(pointcut("args(String)")
.matches(helloMethod, MemberServiceImpl.class)).isTrue();
// Object 는 String 의 상위 타입
assertThat(pointcut("args(Object)")
.matches(helloMethod, MemberServiceImpl.class)).isTrue();
assertThat(pointcut("args()")
.matches(helloMethod, MemberServiceImpl.class)).isFalse();
assertThat(pointcut("args(..)")
.matches(helloMethod, MemberServiceImpl.class)).isTrue();
assertThat(pointcut("args(*)")
.matches(helloMethod, MemberServiceImpl.class)).isTrue();
assertThat(pointcut("args(String,..)")
.matches(helloMethod, MemberServiceImpl.class)).isTrue();
}

execution 과 args 의 차이점

execution 은 컴파일 시점에 적용 대상을 판단합니다. 따라서, 파라미터 타입이 정확하게 일치돼야 하며 클래스에 선언된 정보를 기반으로 AOP 를 적용합니다.

args 는 런타임 시점에 전달받은 객체를 대상으로 적용여부를 판단합니다. 따라서 전달 받은 객체가 캐스팅이 될 경우 다시 말해, 포인트 컷 표현식이 객체의 상위 타입인 경우 AOP 를 적용할 수 있습니다.

구분 execution args
판단 시점 컴파일 시점 (정적) 런타임 시점 (동적)
타입 매칭 선언된 타입과 정확히 일치 상위 타입도 허용 (instanceof 방식)
주요 용도 메서드 시그니처 기반 포인트컷 파라미터 타입 기반 포인트컷, 파라미터 바인딩
/**
* execution(* *(java.io.Serializable)): 메서드의 시그니처로 판단 (정적)
* args(java.io.Serializable): 런타임에 전달된 인수로 판단 (동적)
*/
@Test
void argsVsExecution() {
assertThat(pointcut("args(String)")
.matches(helloMethod, MemberServiceImpl.class)).isTrue();
// args 는 상위 타입도 허용합니다.
// Serializable 는 String 의 상위 타입
assertThat(pointcut("args(java.io.Serializable)")
.matches(helloMethod, MemberServiceImpl.class)).isTrue();
// Object 는 String 의 상위 타입
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();
}

args 를 이용한 파라미터 바인딩

args 의 가장 강력한 기능 중 하나는 Advice 메서드에서 실제 전달된 파라미터 값을 직접 받아올 수 있다는 점입니다.

포인트컷 표현식에서 타입 대신 변수명을 작성하고, Advice 메서드의 파라미터와 이름을 맞추면 됩니다.

@Aspect
@Component
public class MemberServiceAspect {

// args(arg,..) 에서 arg 는 변수명이며, Advice 메서드의 파라미터 이름과 일치해야 합니다.
@Before("execution(* com.example.aop.member.MemberServiceImpl.*(..)) && args(arg,..)")
public void logArgs(Object arg) {
log.info("[logArgs] arg={}", arg);
}

// 파라미터 타입을 명시하면 해당 타입의 메서드에만 매칭됩니다.
@Before("execution(* com.example.aop.member.MemberServiceImpl.*(..)) && args(arg,..)")
public void logStringArgs(String arg) {
log.info("[logStringArgs] arg={}", arg);
}
}

@Around Advice 에서도 동일하게 파라미터 바인딩을 사용할 수 있습니다.

@Around("execution(* com.example.aop.member.MemberServiceImpl.*(..)) && args(arg,..)")
public Object aroundArgs(ProceedingJoinPoint joinPoint, Object arg) throws Throwable {
log.info("[aroundArgs] arg={}", arg);
return joinPoint.proceed();
}

주의사항

args 단독으로는 스프링 빈에 등록된 메서드 이외의 호출에도 매칭될 수 있습니다. 실무에서는 보통 execution 과 함께 && 로 조합해서 사용합니다.

// args 단독 사용 (범위가 너무 넓어질 수 있음)
@Before("args(String)")
public void tooWide(String arg) { ... }

// execution 과 함께 사용 (권장)
@Before("execution(* com.example.aop..*(..)) && args(arg,..)")
public void recommended(Object arg) { ... }
Share