개요
웹 애플리케이션을 개발하다 보면 컨트롤러에서 반복적으로 수행하는 작업들이 있습니다.
- 헤더에서 토큰을 꺼내서 파싱
- 쿠키나 세션에서 사용자 정보 가져오기
- 특정 헤더 값을 객체로 변환하기
특히 스프링 시큐리티를 사용할 때, 인증된 사용자 정보가 SecurityContext의 Principal에 담기는데, 이를 꺼내는 작업도 매번 반복됩니다.

이런 코드가 모든 컨트롤러 메서드마다 반복된다면 코드 중복이 심해지고 유지보수가 어려워집니다.
이때 사용할 수 있는 것이 바로 HandlerMethodArgumentResolver입니다. 오늘은 이 HandlerMethodArgumentResolver에 대해서 공부한 내용을 정리하고자 합니다.
HandlerMethodArgumentResolver란❓
HandlerMethodArgumentResolver는 스프링 MVC에서 컨트롤러 메서드의 파라미터를 유연하게 처리할 수 있게 해주는 인터페이스입니다.

위 Spring 공식 Docs를 보면 다음과 같이 설명하고 있습니다.
주어진 요청의 컨텍스트 내에서 메서드 매개변수를 인자 값으로 변환하기 위한 전략 인터페이스
즉, HTTP 요청이 들어왔을 때 컨트롤러 메서드의 파라미터에 어떤 값을 넣어줄지 결정하는 역할을 합니다.
동작 흐름
1. 클라이언트 HTTP 요청
⬇️
2. DispatcherServlet이 요청 수신
⬇️
3. 적절한 컨트롤러 메서드 찾기
⬇️
4. ArgumentResolver가 파라미터 분석 및 값 주입
⬇️
5. 컨트롤러 메서드 실행
인터페이스 구조
HandlerMethodArgumentResolver는 두 개의 핵심 메서드로 구성되어 있습니다.

- supportsParameter()
- 해당 Resolver가 특정 메서드 파라미터를 처리할 수 있는지 여부를 결정합니다.
- 파라미터의 어노테이션, 타입, 이름 등을 검사하여 처리 가능 여부를 판단합니다.
- true를 반환하면 이 Resolver가 해당 파라미터를 처리하게 되며, false를 반환하면 다른 Resolver가 처리를 시도합니다.
- 스프링은 등록된 모든 Resolver를 순회하며 supportsParameter()가 true를 반환하는 첫 번째 Resolver를 사용합니다.
- resolveArgument()
- supportsParameter()`가 true를 반환한 경우, 실제로 파라미터에 주입할 값을 생성하고 반환합니다.
- HTTP 요청 정보(NativeWebRequest)를 활용하여 헤더, 쿠키, 세션 등에서 필요한 데이터를 추출할 수 있습니다.
- 생성된 객체는 컨트롤러 메서드의 해당 파라미터에 자동으로 주입됩니다.
- 예외 발생 시 적절한 예외 처리를 통해 클라이언트에게 의미 있는 오류 응답을 제공해야 합니다
구현 단계
1단계 : 커스텀 어노테이션 정의
먼저 컨트롤러 파라미터에 붙일 어노테이션을 만듭니다.

- @Target(ElementType.PARAMETER)
- 메서드 파라미터에만 사용 가능하도록 제한
- @Retention(RetentionPolicy.RUNTIME)
- 런타임 시점에도 어노테이션 정보를 유지하여 리플렉션으로 읽을 수 있게 함
2단계 : HandlerMethodArgumentResolver 구현
실제로 파라미터 값을 주입하는 Resolver를 구현합니다.

- supportsParameter() 메서드
- @LoginUser 어노테이션이 붙어있는지 확인
- 파라미터 타입이 "String"인지 확인
- 두 조건을 모두 만족하면 이 Resolver가 처리
- resolveArgument() 메서드
- SecurityContext에서 현재 인증 정보를 가져옴
- JWT 필터에서 이미 검증을 마치고 "SecurityContext"`에 저장한 인증 정보
- "Authentication" 객체에서 사용자 이메일(또는 식별자)을 추출
- 해당 값을 컨트롤러 메서드의 파라미터로 주입
3단계 : WebMvcConfigurer에 Resolver 등록
만든 Resolver를 스프링에 등록해야 실제로 동작합니다.

WebMvcConfigurer 인터페이스의 addArgumentResolvers() 메서드를 오버라이드하여 우리가 만든 Resolver를 등록합니다.
이제 스프링이 컨트롤러 메서드를 호출할 때 우리가 만든 Resolver를 사용하게 됩니다.
4단계 : 컨트롤러에서 사용
Before : Resolver 사용 전

- 문제점
- 헤더 파싱 코드가 매번 반복됨
- 토큰 검증 로직이 컨트롤러에 노출됨
- 코드가 길어지고, 가독성 하락
- 인증 로직 변경 시 모든 컨트롤러 수정이 필
After : Resolver 사용

로그아웃 엔드포인트를 보면 @LoginUser 어노테이션 하나로 인증된 사용자의 이메일을 바로 받아올 수 있습니다.
동작 흐름
- 클라이언트가 JWT 토큰과 함께 /logout 요청
- Spring Security Filter에서 JWT 검증 및 "SecurityContext"에 인증 정보 저장
- 컨트롤러 호출 전 LoginUserArgumentResolver가 동작
- "SecurityContext"에서 이메일 추출하여 파라미터에 주입
- 컨트롤러 메서드 실행
추가 활용 방안 1
이메일 대신 User 객체 전체를 주입하고 싶다면 다음과 같이 수정할 수 있습니다.

- 컨트롤러에서의 사용

추가 활용 방안 2
로그인하지 않아도 되는 엔드포인트에서 사용하려면 null을 허용하도록 구현합니다.

- 컨트롤러에서의 사용

마무리
HandlerMethodArgumentResolver를 활용하면 다음과 같은 이점을 얻을 수 있습니다.
- 코드 중복 제거
- 반복되는 인증 코드를 한 곳에서 관리
- 관심사 분리
- 비즈니스 로직과 인증 로직을 명확히 분리
- 가독성 향상
- 컨트롤러가 비즈니스 로직에만 집중
- 유지보수 용이
- 인증 로직 변경 시 Resolver만 수정
- 명확한 의도
- @LoginUser 어노테이션으로 "이 API는 로그인 필요"를 명시
다만, 다음과 같은 단점도 고려해야 합니다.
- 학습 곡선
- 스프링 내부 동작 원리에 대한 이해 필요
- 디버깅 복잡도
- 실행 흐름 추적이 어려울 수 있음
- 테스트 설정
- 단위 테스트 작성 시 추가 설정 필요
- 성능 고려
- DB 조회가 필요한 경우 캐싱 전략 필수
스프링이 제공하는 확장 포인트를 잘 활용하면 더 깔끔하고 유지보수하기 좋은 코드를 작성할 수 있습니다.
특히 인증/인가 로직처럼 모든 엔드포인트에서 반복되는 코드가 있다면, `HandlerMethodArgumentResolver`를 적극 활용해보는 것도 좋은 방법이라고 생각합니다.
물론 프로젝트의 규모와 팀의 상황에 따라 적절히 판단하여 사용하는 것이 중요할 것 같습니다. 작은 프로젝트에서는 오히려 과도한 추상화가 될 수 있으니, 반복 코드가 실제로 문제가 될 때 도입하는 것이 좋을 것 같다는 생각이 듭니다.
이번 글을 통해 HandlerMethodArgumentResolver의 동작 원리와 활용 방법을 정리해볼 수 있었습니다.
참고 자료
[Spring Framework 공식 문서 - HandlerMethodArgumentResolver]
'Framework > Spring\Spring boot' 카테고리의 다른 글
| [Spring boot] 우리가 보낸 객체는 어떻게 서버까지 흘러갈까? (직렬화와 역직렬화) (0) | 2026.01.13 |
|---|---|
| [Spring boot] 동기식 HTTP 클라이언트 RestTemplate 알아보기 (0) | 2026.01.07 |
| [Springboot] Spring Boot에서 Service와 ServiceImpl 분리, 꼭 필요할까❓ (0) | 2025.02.28 |
| [SpringBoot] ObjectMapper와 직렬화/역직렬화 (0) | 2025.02.27 |
| [Spring boot] DTO와 Entity 변환 위치에 대한 고찰 - Controller vs Service Layer (0) | 2024.12.25 |