Java/Spring

[Spring] 순서 의존 AOP로 인해 파라미터 순서만 바꿔도 코드가 동작하지 않은 이유

범데이 2025. 10. 21. 08:23

1. 문제 상황

Spring MVC 기반 프로젝트에서 로그아웃 기능을 점검하던 중, 동일한 기능을 하는 두 메서드 중 하나는 정상 동작하고 다른 하나는 아무 반응이 없었다.

 

두 메서드는 코드 한 줄 다르지 않았고, 단지 파라미터 순서만 달랐다.

 

// 정상 동작
@RequestMapping("/logout")
public String logout(HttpServletRequest request,
                     @ModelAttribute UserContext userContext,
                     HttpServletResponse response) { ... }

// 동작 안 함
@RequestMapping("/logout")
public String logout(HttpServletRequest request,
                     HttpServletResponse response,
                     @ModelAttribute UserContext userContext) { ... }

 

 

 

2. 원인 분석

프로젝트에는 모든 Controller 호출 전 실행되는 AOP 어드바이스가 있었다.

메서드 실행 전에 세션 정보를 UserContext 객체에 주입하는 역할을 하는 코드였다.

 

public void beforeControllerCall(JoinPoint joinPoint) {
    Object[] args = joinPoint.getArgs();

    if (args.length > 1) {
        if (args[0] instanceof HttpServletRequest && args[1] instanceof UserContext) {
            HttpServletRequest request = (HttpServletRequest) args[0];
            UserContext context = (UserContext) args[1];

            // 세션 값 주입 로직
            context.setUserSession(request.getSession().getAttribute("USER_SESSION"));
        }
    }
}

 

 

즉, AOP는 첫 번째 인자는 HttpServletRequest, 두 번째 인자는 UserContext라고 ‘가정’하고 있었다.

 

하지만 두 번째 메서드에서는 두 번째 인자가 HttpServletResponse였기 때문에 instanceof 조건이 성립하지 않았고, AOP 블록이 실행되지 않았다.

 

결과적으로 UserContext는 세션 값이 없는 상태로 컨트롤러에 전달되었다.

 

 

 

3. 동작 원리

Spring AOP의 JoinPoint.getArgs()는 실제 메서드 호출 시의 인자를 그대로 배열 형태로 반환한다.

즉, 개발자가 선언한 파라미터 순서가 그대로 인덱스에 반영된다.

Object[] args = joinPoint.getArgs();
// 예: [HttpServletRequest, HttpServletResponse, UserContext]

 

 

따라서 순서에 의존한 코드는 파라미터 위치만 바뀌어도 완전히 다른 동작을 하게 된다.

 

 

 

4. 개선 방법

파라미터 순서가 아니라 타입을 기준으로 탐색하도록 코드를 수정하면 된다.

public void beforeControllerCall(JoinPoint joinPoint) {
    HttpServletRequest request = null;
    UserContext context = null;

    for (Object arg : joinPoint.getArgs()) {
        if (arg instanceof HttpServletRequest) request = (HttpServletRequest) arg;
        if (arg instanceof UserContext) context = (UserContext) arg;
    }

    if (request != null && context != null) {
        context.setUserSession(request.getSession().getAttribute("USER_SESSION"));
    }
}

 

 

이렇게 하면 인자 순서가 바뀌더라도 정상적으로 동작한다.

 

 

 

5. 유지보수 관점에서 얻은 교훈

  1. AOP는 ‘숨은 계약’을 만든다.
    - 메서드 시그니처의 파라미터 순서나 타입에 대해 암묵적인 기대가 있다면, 유지보수자는 이를 알기 전까지 쉽게 함정을 밟는다.

  2. 순서 의존 로직은 언제든 실패한다.
    - 특히 AOP의 getArgs()는 단순 배열이므로, "몇 번째 인자가 무엇인지"에 의존하면 리팩토링 시 바로 깨진다.

  3. MVC 표준 확장 포인트를 고려하자.
    - 컨트롤러 공통 전처리는 @ControllerAdviceHandlerMethodArgumentResolver로 구현하는 것이 안전하다.
    이 방식은 파라미터 순서나 선언 여부에 영향을 받지 않는다.

  4. 작은 변경도 테스트해야 한다.
    - 파라미터 순서를 바꾸는 건 IDE 리팩토링 수준의 단순 작업처럼 보이지만, AOP가 개입된 구조에서는 실행 흐름 자체를 바꿀 수 있다.
728x90
반응형