카테고리 없음

HandlerMethodArgumentResolver와 NativeWebRequest 이해하기

기억지기 개발자 2026. 8. 5. 13:07

Spring의 컨트롤러를 작성하다 보면 메서드의 매개변수에 @RequestParam, @PathVariable, @RequestBody, HttpServletRequest, HttpSession 등을 선언하기만 해도 Spring이 알아서 값을 전달해준다.

@GetMapping("/members/{id}")
public MemberResponse getMember(
        @PathVariable Long id,
        @RequestParam String name,
        HttpServletRequest request
) {
    // ...
}

개발자는 id, name, request 객체를 직접 생성하지 않았다.
그런데도 컨트롤러가 호출될 때는 각각의 값이 정상적으로 전달되는 이유는 Spring이 컨트롤러를 호출하기 전에 매개변수마다 적절한 값을 만들어 넣어주기 때문이다.
이 역할을 담당하는 핵심 구성 요소가 HandlerMethodArgumentResolver이다.

1. HandlerMethodArgumentResolver란 무엇인가

HandlerMethodArgumentResolver는 컨트롤러 메서드의 매개변수를 실제 인자값으로 변환하는 전략 인터페이스다.
쉽게 말하면 컨트롤러의 특정 매개변수를 보고 “이 매개변수는 내가 처리할 수 있다”라고 판단한 뒤, HTTP 요청이나 세션 등의 정보를 이용하여 실제 객체를 만들어주는 역할이다.

예를 들어 다음과 같은 컨트롤러가 있다고 가정하면

@GetMapping("/posts")
public List<PostResponse> getPosts(
        @RequestParam int page,
        @LoginUser LoginUser loginUser
) {
    // ...
}

page는 Spring이 기본으로 제공하는 RequestParamMethodArgumentResolver가 처리할 수 있다.
하지만 @LoginUser LoginUser loginUser는 개발자가 만든 사용자 정의 규칙이므로 Spring은 이 값을 어떻게 만들어야 하는지 알지 못한다. 이때 사용자 정의 HandlerMethodArgumentResolver를 만들어주면 세션, JWT, Spring Security 인증 정보 등에서 로그인 사용자 정보를 꺼내 LoginUser 객체로 변환하여 컨트롤러에 전달할 수 있다.

따라서 HandlerMethodArgumentResolver를 한 문장으로 정리하면 다음과 같다.

컨트롤러 메서드가 호출되기 전에 특정 매개변수에 들어갈 값을 대신 생성해주는 Spring MVC의 확장 기능이다.

2. 우리가 이미 사용하고 있던 ArgumentResolver

HandlerMethodArgumentResolver는 특별한 기능처럼 보이지만 실제로는 Spring MVC를 사용할 때 계속 도움을 받고 있던 기능이다. Spring은 다양한 기본 ArgumentResolver를 제공한다.

@RequestParam은 요청 파라미터를 읽어 매개변수에 넣어주고, @PathVariable은 URL 경로 변수를 읽어 넣어주며, @RequestHeader는 헤더를 읽어 넣어준다. @RequestBody는 HTTP 요청 본문을 HttpMessageConverter를 통해 객체로 변환하고, HttpServletRequest, HttpSession, Principal 등도 각각 적절한 Resolver가 처리한다.

@GetMapping("/test")
public String test(
        @RequestParam String keyword,
        HttpServletRequest request,
        HttpSession session
) {
    return keyword;
}

Spring 내부에 등록된 여러 HandlerMethodArgumentResolver 중에서 각 매개변수를 지원하는 Resolver를 찾아 값을 만들어주는 것이다.

3. HandlerMethodArgumentResolver의 두 메서드

HandlerMethodArgumentResolver를 구현하려면 핵심적으로 supportsParameter()와 resolveArgument() 두 메서드를 구현해야 한다.

supportsParameter()

supportsParameter()는 현재 컨트롤러 매개변수를 이 Resolver가 처리할 수 있는지 판단한다.

@Override
public boolean supportsParameter(MethodParameter parameter) {
    return parameter.hasParameterAnnotation(LoginUser.class)
            && parameter.getParameterType().equals(LoginUserInfo.class);
}

컨트롤러의 매개변수에 @LoginUser가 붙어 있고, 매개변수 타입이 LoginUserInfo일 때만 true를 반환하도록 만든 코드이다.

Spring은 컨트롤러의 각 매개변수를 확인하면서 등록된 Resolver 중 해당 매개변수를 지원하는 Resolver를 찾는다. supportsParameter()가 true를 반환하면 이어서 해당 Resolver의 resolveArgument()를 호출한다. 여러 Resolver를 관리하는 HandlerMethodArgumentResolverComposite는 등록된 Resolver를 순회하면서 해당 매개변수를 지원하는 Resolver를 찾고, 이전에 찾은 결과를 캐시하여 이후 조회를 최적화한다.

resolveArgument()

resolveArgument()는 컨트롤러 매개변수에 실제로 전달할 값을 생성하여 반환한다.

@Override
public Object resolveArgument(
        MethodParameter parameter,
        ModelAndViewContainer mavContainer,
        NativeWebRequest webRequest,
        WebDataBinderFactory binderFactory
) {
    // 요청 또는 세션에서 값을 꺼내서 필요한 객체로 변환한다.
    // 컨트롤러 매개변수에 들어갈 값을 반환한다.
}

이 메서드가 반환한 객체가 컨트롤러 매개변수로 전달된다.
반환값은 반드시 supportsParameter()에서 검사한 매개변수 타입과 호환되어야 한다.

resolveArgument()의 각 매개변수는 다음 역할을 한다.

매개변수 역할
MethodParameter 현재 처리하고 있는 컨트롤러 매개변수의 타입과 애너테이션 정보를 제공한다.
ModelAndViewContainer 현재 요청의 Model과 View 관련 정보에 접근할 수 있게 한다.
NativeWebRequest 현재 HTTP 요청과 관련된 파라미터, 헤더, 세션 속성 및 원본 요청 객체에 접근하게 한다.
WebDataBinderFactory 데이터 바인딩과 타입 변환에 필요한 WebDataBinder를 생성한다.

일반적인 로그인 사용자 주입 Resolver에서는 주로 MethodParameter와 NativeWebRequest를 사용한다.

4. NativeWebRequest란 무엇인가

resolveArgument()의 매개변수를 보면 HttpServletRequest가 아니라 NativeWebRequest가 전달된다.
여기서 NativeWebRequest을 처음 보았기 때문에 무슨 차이가 있는지 의문이 들었다.

NativeWebRequest는 Spring이 제공하는 웹 요청 추상화 인터페이스로 WebRequest를 상속하며, Spring의 공통 요청 접근 기능과 함께 실제 웹 환경에서 사용되는 원본 요청 객체와 응답 객체에 접근할 수 있도록 만들어졌다

RequestAttributes
        ↑
   WebRequest
        ↑
 NativeWebRequest

NativeWebRequest를 사용하면 다음과 같이 요청 파라미터나 헤더를 읽을 수 있다.

String token = webRequest.getHeader("Authorization");
String page = webRequest.getParameter("page");

하지만 NativeWebRequest의 중요한 기능은 이름 그대로 원본, 즉 네이티브 요청 객체를 꺼낼 수 있다는 것이다.

HttpServletRequest request =
        webRequest.getNativeRequest(HttpServletRequest.class);

현재 애플리케이션이 Spring MVC와 Servlet 환경에서 동작하고 있다면 네이티브 요청 객체는 대부분 HttpServletRequest이다.
다만 getNativeRequest()의 반환 타입은 null이 가능하다. 요청 객체가 요구한 타입과 일치하지 않는 환경일 수도 있기 때문이다.

HttpServletRequest request =
        webRequest.getNativeRequest(HttpServletRequest.class);

if (request == null) {
    throw new IllegalStateException("HttpServletRequest를 찾을 수 없다.");
}

Spring MVC에서는 NativeWebRequest의 구현체가 내부적으로 HttpServletRequest를 감싸고 있다.
따라서 NativeWebRequest는 완전히 별개의 HTTP 요청을 나타내는 것이 아니라, 기존 요청 객체를 Spring이 사용하기 편한 형태로 한 번 추상화한 것이라고 이해하면 된다.

5. HttpServletRequest란 무엇인가

HttpServletRequest는 Spring이 아니라 Jakarta Servlet 표준에서 제공하는 인터페이스이다.
Servlet 컨테이너가 클라이언트의 HTTP 요청을 받으면 요청 정보를 담은 HttpServletRequest 객체를 생성한다..

HttpServletRequest를 사용하면 HTTP 요청의 세부 정보에 직접 접근할 수 있다.

String method = request.getMethod();
String uri = request.getRequestURI();
String token = request.getHeader("Authorization");
String keyword = request.getParameter("keyword");
HttpSession session = request.getSession();
Cookie[] cookies = request.getCookies();

HttpServletRequest는 HTTP와 Servlet 환경에 직접 연결된 저수준 객체이다.
HTTP 메서드, 요청 URI, 헤더, 쿠키, 세션, 사용자 인증 정보, 원격 주소, 요청 본문 스트림 등 Servlet 환경에서 제공하는 거의 모든 요청 정보에 접근할 수 있다.

Spring MVC 컨트롤러에 HttpServletRequest를 매개변수로 선언해도 자동으로 전달되는 이유 역시 ArgumentResolver 때문이다. Spring의 ServletRequestMethodArgumentResolver가 ServletRequest, WebRequest, HttpSession, Principal, InputStream, Reader, Locale 등의 타입을 지원하며 현재 요청에 맞는 값을 반환한다.

@GetMapping("/request")
public String request(HttpServletRequest request) {
    return request.getRequestURI();
}

이 코드에서도 컨트롤러가 HttpServletRequest를 직접 생성한 것이 아니라 Spring이 해당 타입을 지원하는 기본 Resolver를 찾아 현재 요청 객체를 전달한 것이다.

6. HttpServletRequest와 NativeWebRequest의 차이

구분HttpServletRequestNativeWebRequest

구분 HttpServletRequest NativeWebRequest
제공 주체 Jakarta Servlet Spring Framework
목적 Servlet 환경의 HTTP 요청을 직접 표현한다. 웹 요청을 Spring 방식으로 추상화한다.
추상화 수준 Servlet API에 직접 의존하는 저수준 객체이다. Servlet 등 구체적인 웹 기술을 한 단계 감싼 추상화 객체이다.
주요 기능 URI, HTTP 메서드, 헤더, 쿠키, 세션, 요청 본문 등에 직접 접근한다. 파라미터, 헤더, 요청 속성에 공통 방식으로 접근하고 원본 요청과 응답 객체를 꺼낼 수 있다.
Servlet 의존성 강하게 의존한다. 인터페이스 자체는 Servlet에 직접 고정되지 않는다.
일반적인 사용 위치 필터, 인터셉터, 컨트롤러, Servlet 기반 코드 Spring MVC 내부 구성 요소와 사용자 정의 ArgumentResolver
원본 요청 접근 자기 자신이 원본 요청 인터페이스이다. getNativeRequest(HttpServletRequest.class)로 원본 요청을 꺼낸다.

핵심은 둘 중 하나가 다른 하나를 완전히 대체하는 관계가 아니라는 점이다.
HttpServletRequest는 Servlet 컨테이너가 만든 실제 HTTP 요청 객체이고, NativeWebRequest는 그 요청을 Spring이 공통된 방식으로 다루기 위해 감싼 추상화이다.

7. NativeWebRequest가 있는데 왜 다시 HttpServletRequest를 꺼내는가

단순히 헤더나 요청 파라미터를 읽는 경우에는 NativeWebRequest만으로 충분하다.

String authorization = webRequest.getHeader("Authorization");
String page = webRequest.getParameter("page");

하지만 HttpSession, 쿠키, Servlet 전용 속성 또는 특정 Servlet API가 필요한 경우에는 원본 HttpServletRequest를 꺼내 사용하는 편이 자연스럽다.

 

8. 언제 HandlerMethodArgumentResolver를 사용해야 하는가

다음과 같은 값이 여러 컨트롤러에서 반복적으로 필요하다면 ArgumentResolver 사용을 고려할 수 있다.

  • 현재 로그인한 사용자 정보
  • 현재 요청의 테넌트 정보
  • 특정 헤더를 바탕으로 만든 클라이언트 정보
  • API 버전 정보
  • 요청 컨텍스트 객체
  • 공통 권한이나 사용자 환경 정보

반대로 특정 컨트롤러 한 곳에서만 사용하는 단순한 값이라면 굳이 Resolver를 만들 필요는 없다.
@RequestHeader, @RequestParam, @CookieValue, HttpServletRequest처럼 Spring이 제공하는 기본 기능으로 충분한지 먼저 확인하는 것이 좋다.

ArgumentResolver는 단순히 코드를 줄이기 위한 기능이 아니다. 컨트롤러가 HTTP 요청의 세부 구현을 직접 알지 않아도 필요한 객체를 받을 수 있도록 책임을 분리하는 데 의미가 있다.

9. 사용 시 주의할 점

  1. supportsParameter()의 범위를 너무 넓게 설정하면 안 된다.
    타입 하나만 검사하기보다는 사용자 정의 애너테이션과 타입을 함께 검사하는 것이 안전하다.
  2. Resolver 안에 지나치게 많은 비즈니스 로직을 넣으면 안 된다.
    Resolver의 핵심 책임은 요청 정보를 컨트롤러 매개변수로 변환하는 것이다. 복잡한 회원 조회, 권한 정책 결정, 상태 변경 등의 로직은 서비스 계층에 두는 것이 좋다.
  3.  Resolver에서 데이터베이스를 조회할 수는 있지만 모든 요청마다 불필요한 조회가 발생하지 않는지 확인해야 한다.
    컨트롤러에 사용자 ID만 필요하다면 Resolver에서 회원 엔티티 전체를 조회하기보다 ID 또는 간단한 인증 DTO만 전달하는 방법이 더 효율적일 수 있다.
  4. JPA 엔티티를 컨트롤러 매개변수로 직접 전달하는 방식은 신중해야 한다.
    영속성 컨텍스트 범위, 지연 로딩, 계층 간 결합 문제가 발생할 수 있으므로 LoginUserInfo, AuthenticatedUser와 같은 별도의 인증 DTO를 전달하는 편이 안전하다.

10. HttpServletRequest와 NativeWebRequest 중 무엇을 사용해야 하는가

컨트롤러에서 요청 URI, 세션, 쿠키 등 Servlet 전용 기능이 직접 필요한 경우에는 HttpServletRequest를 사용할 수 있다.

@GetMapping("/request-info")
public String requestInfo(HttpServletRequest request) {
    return request.getRequestURI();
}

사용자 정의 HandlerMethodArgumentResolver를 구현할 때는 메서드 매개변수로 전달되는 NativeWebRequest를 먼저 사용한다. 헤더나 요청 파라미터처럼 NativeWebRequest가 직접 제공하는 기능이라면 그대로 사용하고, 세션이나 Servlet 전용 API가 필요할 때 HttpServletRequest를 꺼내 사용하면 된다.

String token = webRequest.getHeader("Authorization");

HttpServletRequest request =
        webRequest.getNativeRequest(HttpServletRequest.class);

따라서 선택 기준은 다음과 같이 정리할 수 있다.

Spring이 제공하는 공통 요청 정보만 필요하다.
→ NativeWebRequest를 사용한다.

Servlet 전용 기능이 필요하다.
→ NativeWebRequest에서 HttpServletRequest를 꺼낸다.

일반 컨트롤러에서 저수준 요청 정보가 직접 필요하다.
→ HttpServletRequest를 매개변수로 받을 수 있다.

11. 최종 정리

HttpServletRequest
= Servlet 환경의 실제 HTTP 요청 객체

NativeWebRequest
= 실제 요청 객체를 Spring 방식으로 감싼 요청 추상화

HandlerMethodArgumentResolver
= 요청 정보를 이용하여 컨트롤러 매개변수 값을 만드는 기능

결국 전체 흐름은 NativeWebRequest 또는 그 안의 HttpServletRequest에서 필요한 요청 정보를 꺼내고, HandlerMethodArgumentResolver가 이를 특정 객체로 변환하여 컨트롤러 매개변수에 전달하는 구조이다. 이 흐름을 이해하면 로그인 사용자 자동 주입, 공통 요청 정보 생성, 인증 컨텍스트 전달과 같은 기능을 단순한 편의 기능이 아니라 Spring MVC의 매개변수 해석 구조 위에서 설계할 수 있다.