Spring MVC 내부 구조 중
ArgumentResolver,
ReturnValueHandler,
HttpMessageConverter,
Converter등을 학습했다.
처음 접하는 개념들이 많아 이해가 쉽지 않았지만, 흐름을 잡는 데 집중했다.
- HttpMessageConverter
- ArgumentResolver (기본 흐름만)
- ConversionService (개념만)
- Converter
- TypeConverter
- Formatter 날짜, 숫자 등 포맷 변환 필요할 때 사용
- WebMvcConfigurer.addFormatters() Converter/Formatter 등록법 학습
HttpMessageConverter - HTTP Body(예: JSON)를 자바 객체로 변환하거나, 자바 객체를 HTTP Body로 변환하는 컴포넌트
ArgumentResolver - HTTP 요청 데이터를 컨트롤러 메서드의 파라미터에 맞는 자바 타입으로 변환해 주는 컴포넌트
(예: @RequestParam, @PathVariable, @ModelAttribute 처리 담당)
ConversionService - 타입 간 변환을 위한 중앙 서비스로, 스프링 내부에서 다양한 변환
(예: String → Integer)을 일관되게 지원
당신은 서빙하는 알바생이다.
손님은 미국인, 일본인, 중국인 등등 다양한 국적의 사람이 온다고 가정하자.
당신은 이 모든 사람의 언어를 알아들을 수 있다.
하지만, 셰프는 한국인이라 이 모든 언어들을 알아듣지 못한다.
셰프는 오직 '한국어로 적힌 정확한 주문서(자바 객체)'만 이해할 수 있다.
----- 일반적인 상황 (변환기 없음) ------
주문 : 여기 워터멜론 하나 주세요~
당신 : 여기 워터멜론 하나 주문 들어왔습니다.
주방 : ?;; 멜론? 그런건 메뉴판에 없어! (주방이 이해 못함 : 변환 실패)
부가 설명:
스프링은 기본적으로 [내가 만든 등급]을 모른다. 나는 BLUE_STAR 등급을 만들었지만,
'String 에서 VIP_CODE 로 변환하는 법'을 모르는데
그걸 변환 시키게 해주는 것이 Converter 역할이며,
그걸 관리하는게 'ConverterService' 이다.
주문 : (워터멜론 달라는 쪽지를 HTTP Body에 담아 보냄 - 예: JSON)
당신 : (쪽지를 읽으며 변환) → "아, 수박 하나 주문이구나!" (@RequestBody - 자바 객체 변환)
주방 : 아~ 수박, 알겠어~ (주방이 이해함)
주문 : 저는 블루스타 등급이에요 (특수 코드 전달 - "등급코드=BLUE_STAR")
당신 : (ConversionService라는 '변환 메뉴얼' 참고해서)
"BLUE_STAR → VIP 고객 등급"으로 변환
VIP 고객 등급 주문 들어왔습니다~
주방 : 아 VIP 고객이 오셨구나?
public class VIPCode { > 이와 같은 VIP Code 가 존재한다고 가정하자.
private String grade;
public VIPCode(String grade) {
this.grade = grade;
}
public String getGrade() {
return grade;
}
}
이걸 어떻게 변환해야하나?
만약 요청이 /welcome?code=BLUE_STAR 라고 들어왔다.
@GetMapping("/welcome")
public String welcome(@RequestParam VIPCode code) { <- 이렇게 받을 수 있는가?
...
}
정답: 못받는다.
그래서 우리가 직접 Converter를 구현하고, 이를 스프링 빈으로 등록해서
스프링이 변환 방법을 알 수 있도록 만들어준다.
@Component
public class StringToVIPCodeConverter implements Converter<String, VIPCode> {
@Override
public VIPCode convert(String source) {
return new VIPCode(source); 이렇게 넣어주면 위의 코드가 실행이 된다.
}
} 이렇게 하면 변환이 성공적으로 진행 된다는 뜻 이다.
위 이미지 때문에 헷갈렸던 부분을 정리하고자 한다.
나는 처음에 저런 다이어그램을 보고 이렇게 생각했다.
요청이 들어오고 그 요청이 ArgumentResolver로 간다.
그 다음 ArgumentResolver 가 @RequestParm , @RequestBody 등등을 확인하고 각자 역할에 맞게 실행된다.
RequestBody 는 HttpMessageConverter 로 이동하며,
RequestParam, PathVariable 등등을 처리하는 것은 ConversionService 로 이동한다.
라고 이해하고 있다.
처음단계는 일단 요청이 들어왔고,
RequestMappingHandlerAdapter.class
각각 처리하는 클레스들 ( RequestResponseBody, RequestParam, PathVariable ) 등등... 그리고 상속받는 걸 들어가 본다면....
내부구조
공통적으로
HanlderMethodArgumentResolver을 구현받고 있다.
정리해보자
요청 → DispatcherServlet
↓
HandlerAdapter (RequestMappingHandlerAdapter)
↓
각 파라미터마다 호환되는지 체크( 아래와 같은 메서드 실행 )

※ 여기서 resolver 종류:
- @RequestParam → RequestParamMethodArgumentResolver → ConversionService 사용
- @PathVariable → PathVariableMethodArgumentResolver → ConversionService 사용
- @RequestBody → **RequestResponseBodyMethodProcessor** → HttpMessageConverter 사용
- @ModelAttribute → ModelAttributeMethodProcessor → ConversionService 사용
처음에는 ArgumentResolver가 모든 파라미터를 처리하고,
그 안에서 ConversionService와 HttpMessageConverter도 같이 쓰는 줄 알았다.
하지만 조사해보니,
@RequestBody는 RequestResponseBodyMethodProcessor가 HttpMessageConverter를 직접 사용하고,
@RequestParam과 @PathVariable은 일반 ArgumentResolver가 ConversionService를 사용한다는 걸 알게 됐다.
처음에는 @RequestParam으로 커스텀 객체는 못받는 줄 알았으나,
하지만, Converter 인터페이스를 구현해서 변환 방법을 등록해주면 커스텀 객체도 받을 수 있다는 사실을 알게 되었다.