12. DispatcherServlet → HandlerMethodArgumentResolver: resolveArgument()
Step 11에서 인터셉터 preHandle을 통과하면, DispatcherServlet은 컨트롤러를 바로 호출하지 않고 먼저 컨트롤러 파라미터를 준비합니다. 이때 핵심 역할을 하는 것이 HandlerMethodArgumentResolver 입니다.
시퀀스 다이어그램
sequenceDiagram
participant DS as DispatcherServlet
participant HA as HandlerAdapter(RequestMapping)
participant Invocable as InvocableHandlerMethod
participant Resolver as HandlerMethodArgumentResolver
participant RR as RequestResponseBodyMethodProcessor
DS->>HA: handle(request, response, handler)
HA->>Invocable: invokeAndHandle(...)
Invocable->>Resolver: supportsParameter(param)?
Resolver-->>Invocable: true (for @RequestBody)
Invocable->>RR: resolveArgument(...)
RR-->>Invocable: DTO argument 반환
Invocable->>Invocable: 컨트롤러 메서드 호출 준비 완료
핵심 코드 흐름
// RequestResponseBodyMethodProcessor.java
@Override
public @Nullable Object resolveArgument(MethodParameter parameter,
@Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
@Nullable WebDataBinderFactory binderFactory) throws Exception {
parameter = parameter.nestedIfOptional();
// 1) HttpMessageConverter를 통해 본문(JSON)을 객체로 읽는다.
Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
// 2) @Valid 같은 검증 어노테이션이 있으면 검증 수행
if (binderFactory != null) {
String name = Conventions.getVariableNameForParameter(parameter);
ResolvableType type = ResolvableType.forMethodParameter(parameter);
WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name, type);
if (arg != null) {
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
}
}
}
return adaptArgumentIfNecessary(arg, parameter);
}
여기서 Step 12는 전체 오케스트레이션 단계입니다. 세부는 다음으로 분리됩니다.
- Step 12-1: JSON → DTO 객체 생성
- Step 12-2: @Valid 검증
- Step 12-3: 검증 실패 예외 처리
이 단계의 의미
핵심은 “컨트롤러를 실행하기 전에 파라미터 품질을 보장”한다는 점입니다.
- 타입 변환 실패, JSON 파싱 실패를 먼저 걸러냄
- 빈 값/형식 오류를 검증에서 걸러냄
- 문제가 있으면 컨트롤러 진입 전에 예외 처리 경로로 전환
즉, 컨트롤러는 상대적으로 신뢰할 수 있는 입력을 받게 됩니다.
탐구 질문
- 컨트롤러가 직접 JSON 파싱/검증을 하지 않고 Resolver 계층에서 처리하면, 테스트와 책임 분리에 어떤 이점이 있을까요?
- @RequestBody가 아닌 @ModelAttribute 바인딩과 비교하면, 생성 시점과 검증 시점은 어떻게 다를까요?
- 만약 resolveArgument 단계에서 예외가 발생하면 인터셉터 postHandle은 호출될까요?