TIL

Spring MVC의 ArgumentResolver 등록하고 사용하기

chungmani 2026. 7. 27. 13:12

1. ArgumentResolver란?

  • Argument = 메서드의 매개변수
  • Resolver = 해결해주는 객체

ArgumentResolver는 Spring MVC가 컨트롤러 메서드를 호출할 때 필요한 매개변수(Argument)를 어떻게 만들어서 넣어줄지 결정하는 역할을 한다.

 

@GetMapping("/users/{userId}")
public UserResponse getUser(@PathVariable Long userId) {
    ...
}

Spring MVC는 요청 URL에 있는 userId 값을 가져와서 메서드의 Long userId 파라미터에 넣어준다.

 

@PostMapping("/users")
public void createUser(@RequestBody SignupRequest request) {
    ...
}

이 경우 Spring MVC는 HTTP 요청 Body의 JSON 데이터를 읽어서 SignupRequest 객체로 변환한 뒤 컨트롤러 메서드에 전달한다.

 

개발자가 원하는 커스텀한 파라미터 처리 로직을 추가하고 싶을 때 사용할 수 있는 것이 ArgumentResolver이다.


2. 왜 ArgumentResolver가 필요한가?

JWT 인증을 구현하다 보면 로그인한 사용자의 정보를 컨트롤러에서 사용해야 하는 경우가 많다.

예를 들어 사용자가 비밀번호를 변경한다고 생각해보자.

현재 로그인한 사용자의 ID가 필요하다.

일반적으로 생각하면 다음과 같은 정보가 필요할 수 있다.

HTTP 요청
    ↓
Authorization Header
    ↓
JWT 토큰
    ↓
JWT 검증
    ↓
userId
email
userRole
    ↓
컨트롤러에서 사용

 

그런데 모든 컨트롤러에서 JWT를 직접 파싱한다면 코드가 매우 복잡해질 수 있다.

String token = ...
Long userId = ...
String email = ...
UserRole role = ...

이런 인증 관련 로직이 여러 컨트롤러에 반복될 가능성이 있다.

그래서 인증 과정에서 얻은 사용자 정보를 하나의 객체로 만들어 컨트롤러의 파라미터로 바로 전달할 수 있도록 만들 수 있다.

그 역할을 ArgumentResolver가 담당하도록 구현할 수 있다.

 


3.  AuthUserArgumentResolver

이번 프로젝트에서는 다음과 같은 ArgumentResolver를 사용했다.

public class AuthUserArgumentResolver implements HandlerMethodArgumentResolver {

여기서 중요한 부분은 HandlerMethodArgumentResolver 인터페이스를 구현했다는 것이다.

Spring MVC는 이 인터페이스를 구현한 객체를 ArgumentResolver로 사용할 수 있다.

우리가 직접 구현해야 하는 핵심 메서드는 두 가지다.

@Override
public boolean supportsParameter(MethodParameter parameter) {
    ...
}

@Override
public Object resolveArgument(
        @Nullable MethodParameter parameter,
        @Nullable ModelAndViewContainer mavContainer,
        NativeWebRequest webRequest,
        @Nullable WebDataBinderFactory binderFactory
) {
    ...
}

각 메서드의 역할은 다르다.

 


4. supportsParameter()의 역할

@Override
public boolean supportsParameter(MethodParameter parameter) {

    boolean hasAuthAnnotation =
            parameter.getParameterAnnotation(Auth.class) != null;

    boolean isAuthUserType =
            parameter.getParameterType().equals(AuthUser.class);

    if (hasAuthAnnotation != isAuthUserType) {
        throw new AuthException(
                "@Auth와 AuthUser 타입은 함께 사용되어야 합니다."
        );
    }

    return hasAuthAnnotation;
}

 

 

Spring MVC가 컨트롤러의 파라미터를 하나씩 확인할 때 supportsParameter()를 통해 판단한다.

@PutMapping("/users")
public void changePassword(
        @Auth AuthUser authUser,
        @RequestBody UserChangePasswordRequest request
) {
    ...
}

 

@Auth AuthUser authUser
        ↓
AuthUserArgumentResolver에게 물어봄
        ↓
supportsParameter()
        ↓
"이 파라미터 내가 처리할 수 있어!"
        ↓
true

 

Spring MVC는 해당 ArgumentResolver의 resolveArgument()를 호출한다.

 

반면 다음과 같은 파라미터라면

@RequestBody UserChangePasswordRequest request

AuthUserArgumentResolver가 처리할 대상이 아니다.

 


5. @Auth와 AuthUser를 함께 사용하는 이유

코드에서는 다음 두 가지를 확인하고 있다.

boolean hasAuthAnnotation =
        parameter.getParameterAnnotation(Auth.class) != null;

boolean isAuthUserType =
        parameter.getParameterType().equals(AuthUser.class);

 

@Auth

어노테이션이 붙어 있는지 확인

AuthUser

타입인지 확인

 

 

반대로 이런 식으로 사용하면 잘못된 사용이다.

@Auth String userId

 

AuthUser authUser

 

이런 경우도 우리가 만든 인증용 ArgumentResolver의 의도와 맞지 않는다.

그래서 다음 로직을 사용한다.

if (hasAuthAnnotation != isAuthUserType) {
    throw new AuthException(
            "@Auth와 AuthUser 타입은 함께 사용되어야 합니다."
    );
}

 

따라서 @Auth가 붙어 있고 AuthUser 타입인 경우에만 이 Resolver가 해당 파라미터를 처리하게 된다.


6. resolveArgument()의 역할

 

실제로 파라미터에 넣어줄 객체를 만드는 작업을 resolveArgument()에서 한다.

@Override
public Object resolveArgument(
        @Nullable MethodParameter parameter,
        @Nullable ModelAndViewContainer mavContainer,
        NativeWebRequest webRequest,
        @Nullable WebDataBinderFactory binderFactory
) {
    HttpServletRequest request =
            (HttpServletRequest) webRequest.getNativeRequest();

    Long userId =
            (Long) request.getAttribute("userId");

    String email =
            (String) request.getAttribute("email");

    UserRole userRole =
            UserRole.of(
                    (String) request.getAttribute("userRole")
            );

    return new AuthUser(
            userId,
            email,
            userRole
    );
}

 

 

 

 


7. WebMvcConfigurer에 ArgumentResolver 등록하기

WebMvcConfig를 만들어서 Resolver를 등록할 수 있다.

@Configuration
@RequiredArgsConstructor
public class WebMvcConfig implements WebMvcConfigurer {

    private final AuthUserArgumentResolver authUserArgumentResolver;

    @Override
    public void addArgumentResolvers(
            List<HandlerMethodArgumentResolver> resolvers
    ) {
        resolvers.add(authUserArgumentResolver);
    }
}

 


8. Resolver를 Bean으로 등록하는 이유

위 코드에서는 다음과 같이 의존성 주입을 받고 있다.

private final AuthUserArgumentResolver authUserArgumentResolver;

그러려면 AuthUserArgumentResolver가 Spring Bean으로 등록되어 있어야 한다.

 

@Component
public class AuthUserArgumentResolver
        implements HandlerMethodArgumentResolver {
    ...
}

그러면 Spring이 해당 클래스를 Bean으로 관리한다.

그리고 WebMvcConfig에서 생성자 주입으로 받을 수 있다.

private final AuthUserArgumentResolver authUserArgumentResolver;

 

그 후

resolvers.add(authUserArgumentResolver);

를 통해 Spring MVC에 등록한다.