작성자: 권지원

Spring AOP 체이닝 시 JoinPointMatch 바인딩 버그 해결

<aside>


1. 문제 상황

@DistributedLock(분산락 AOP)과 @TrackExecutionTime(모니터링 AOP)을 같은 메서드에 붙였더니 서버 오류가 발생했다.

@DistributedLock(key = "'reservation:' + #reservationId")
@TrackExecutionTime("reservation.complete")
@Transactional
public ReservationResponseDto complete(Long memberId, Long reservationId) {
    // ...
}

에러 메시지:

java.lang.IllegalStateException: Required to bind 2 arguments, but only bound 1
(JoinPointMatch was NOT bound in invocation)
  at org.springframework.aop.aspectj.AbstractAspectJAdvice.argBinding(...)
  at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(...)
  at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(...)

2. 용어 정리

에러를 이해하려면 먼저 용어부터 짚고 넘어간다.

용어 의미
Advice @Around가 붙은 메서드 그 자체. 원래 메서드 전후에 끼어들어 실행되는 코드. lock(), measure() 같은 메서드를 말함
Advice Method Parameter Advice 메서드의 파라미터. ProceedingJoinPoint joinPoint, DistributedLock distributedLock
바인딩 (Binding) Spring이 Advice 메서드 파라미터에 값을 자동으로 채워주는 것. joinPointdistributedLock에 Spring이 알아서 값을 넣어주는 과정
체이닝 (Chaining) 한 메서드에 Advice가 여러 개 붙으면 줄줄이 호출되는 형태
JoinPointMatch 어떤 파라미터가 몇 번째 인덱스인지 Spring이 내부적으로 관리하는 매핑 정보

체이닝 실행 순서 예시:

RedisLockAspect.lock()
  └─ joinPoint.proceed() 호출
       └─ ExecutionTimeAspect.measure()
            └─ joinPoint.proceed() 호출
                 └─ 실제 complete() 실행

3. 왜 이 버그가 발생하는가

두 Advice 모두 파라미터 바인딩 형태로 어노테이션 인스턴스를 받고 있었다.

// 분산락 AOP
@Around("@annotation(distributedLock)")
public Object lock(ProceedingJoinPoint joinPoint,
                   DistributedLock distributedLock) throws Throwable { }

// 모니터링 AOP
@Around("@annotation(trackExecutionTime)")
public Object measure(ProceedingJoinPoint joinPoint,
                      TrackExecutionTime trackExecutionTime) throws Throwable { }

@annotation(distributedLock) 처럼 소문자 변수명을 쓰면 Spring에게 두 가지를 동시에 요청하는 것이다.