2025. 5. 2. 19:14ㆍSpring/MVC
[목차]
1. HttpMessageConverter란?
2. HttpMessageConverter 동작 시점
3. HttpEntityMethodProcessor와 RequestResponseBodyMethodProcessor
4. RequestMappingHandlerAdapter 동작 흐름 정리
1. HttpMessageConverter란?
- RequestMappingHandlerAdapter는 컨트롤러 메서드를 호출할 때 파라미터 처리와 리턴값 처리를 위해
HandlerMethodArgumentResolver 리스트와 HandlerMethodReturnValueHandler 리스트를 순회하며
파라미터 타입, 리턴 타입, 어노테이션 정보를 기반으로 적절한 구현체를 선택한다. - 이때, HttpMessageConverter는 ArgumentResolver와 ReturnValueHandler의 구현체 중
요청/응답 메시지 바디와 ↔ 자바 객체 변환을 필요로 하는 구현체의 내부에 위치해 사용된다. - 대표적으로 HttpEntityMethodProcessor, RequestResponseBodyMethodProcessor 내부에서 사용됨
2. HttpMessageConverter 동작 시점
1) 컨트롤러 메서드 파라미터 처리시: HandlerMethodArgumentResolver 구현체 선택
- RequestMappingHandlerAdapter가 HandlerMethodArgumentResolver 순회 - supportsParameter()
- 파라미터 타입이 HttpEntity인 경우 → HttpEntityMethodProcessor가 선택됨
- 파라미터에 @RequestBody가 붙은 경우 → RequestResponseBodyMethodProcessor가 선택됨
- 선택된 두 구현체 내부의 HttpMessageConverter.read()가 호출되며
HTTP 요청 메시지 바디를 자바 객체로 변환한다.
2) 컨트롤러 메서드 리턴값 처리시: HandlerMethodReturnValueHandler 구현체 선택
- RequestMappingHandlerAdapter가 HandlerMethodReturnValueHandler를 순회 - supportsReturnType()
- 리턴 타입이 HttpEntity인 경우 → HttpEntityMethodProcessor가 선택됨
- 메서드에 @ResponseBody가 붙은 경우 → RequestResponseBodyMethodProcessor가 선택됨
- 선택된 두 구현체 내부의 HttpMessageConverter.write()가 호출되며
자바 객체를 HTTP 응답 메시지 바디로 변환한다.
3. HttpEntityMethodProcessor와 RequestResponseBodyMethodProcessor
public class HttpEntityMethodProcessor implements
HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {
private final List<HttpMessageConverter<?>> messageConverters;
public HttpEntityMethodProcessor(List<HttpMessageConverter<?>> converters) {
this.messageConverters = converters;
}
...
}
public class RequestResponseBodyMethodProcessor implements
HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {
//동일한 구조
}
1) HandlerMethodArgumentResolver와 HandlerMethodReturnValueHandler 인터페이스 구현
- 두 구현체는 HandlerMethodArgumentResolver 리스트 argumentResolvers와
HandlerMethodReturnValueHandler 리스트 returnValueHandler 둘 다에 들어가 있다. - 하나의 클래스가 두 인터페이스를 모두 구현하고 리스트 두 개 모두에 들어가 있는 구조이다.
2) HttpMessageConverter 리스트 messageConverters
- 스프링은 여러 종류의 HttpMessageConverter 구현체를 제공한다.
HttpMessageConverter 구현체 | 지원 Content-Type | 설명 |
MappingJackson2HttpMessageConverter | application/json | JSON ↔ 객체 변환 |
StringHttpMessageConverter | text/plain 등 | String 변환 |
FormHttpMessageConverter | application/x-www-form-urlencoded | Form 데이터를 MultiValueMap으로 변환 |
ByteArrayHttpMessageConverter | application/octet-stream | byte[]로 다룸 (파일 등) |
- messageConverters 리스트가 각각의 객체에 주입된 방식은 아래와 같다
스프링 부트 실행 시, RequestMappingHandlerAdapter가 초기화될 때
HttpMessageConverter 리스트도 자동 구성된다.
(Jackson 같은 건 classpath에 있으면 자동 등록됨)
→ 그리고 RequestMappingHandlerAdapter가 ArgumentResolver/ReturnValueHandler 리스트를 구성할 때
RequestResponseBodyMethodProcessor, HttpEntityMethodProcessor를 직접 생성하고
이때 앞서 구성된 MessageConverter 리스트를 전달한다.
→ 즉, RequestMappingHandlerAdapter는
하나의 MessageConverter 리스트를 만들어 여러 Processor에 공유해서 사용하는 구조이다.
3) HttpMessageConverter 찾기
- HttpEntityMethodProcessor는 컨트롤러 메소드의 파라미터나 리턴 타입이 HttpEntity인 경우,
RequestResponseBodyMethodProcessor는 @RequestBody, @ResponseBody가 붙은 경우에
선택되는 리졸버 및 핸들러이다. - 이 두 객체는 요청/응답 메시지 바디와 ↔ 자바 객체 변환을 필요로하므로
자신이 가지고 있는 messageConverters를 순회하면서,
더 구체적인 조건을 만족하는 HttpMessageConverter를 사용한다.
//요청 처리시 조건 확인
converter.canRead(Member.class, MediaType.APPLICATION_JSON);
//응답 처리시 조건 확인
converter.canWrite(Member.class, MediaType.APPLICATION_JSON);
- 요청 바디를 자바 객체로 변환할 때의 canRead() 검사 조건
- 요청 헤더의 Content-Type: 요청 메세지 바디에 들어있는 데이터 유형 명시 (ex. application/json)
- 컨트롤러 메서드의 파라미터 타입: 자바 클래스 (ex. Member.class)
- 자바 객체를 응답 바디로 변환할 때의 canWrite() 검사 조건
- 요청 헤더의 Accept: 클라이언트가 원하는 응답 데이터 타입 (없으면 기본값 */* 또는 application/json)
- 컨트롤러 메서드의 리턴 객체 타입: 자바 클래스 (ex. Member.class)
4. RequestMappingHandlerAdapter 동작 흐름 정리
[HTTP 요청 메시지]
POST /members
Content-Type: application/json
Accept: application/json
{
"username": "hyunji",
"age": 24
}
[컨트롤러 메서드]
@PostMapping("/members")
@ResponseBody
public Member save(@RequestBody Member member) {
return member;
}
1) WAS에서 서블릿으로 HTTP 요청 전달
2) 핸들러 찾기
서블릿이 핸들러를 찾기 위해서 요청 데이터를 가지고 handlerMappings 순회
RequestMappingHandlerMapping에서
@RequestMapping 어노테이션이 붙어있고, URL이 "/members"이고, HTTP method가 POST 방식인
핸들러 메소드 객체(HandlerMethod)를 찾아서 반환
참고: @PostMapping("/members") = @RequestMapping(value="/members", method="POST")
3) 핸들러 어댑터 찾기
서블릿이 핸들러를 가지고 handlerAdapters 순회
→ handlerAdapters의 support(handler) 메서드 호출 해서 true인 어댑터 반환
반환받은 핸들러 객체가 HandlerMethod니까
핸들러 어댑터로는 RequestMappingHandlerAdapter가 반환됨
4) HandlerMethodArgumentResolver 찾기
RequestMappingHandlerAdapter는 핸들러 메서드 실행하기 위해
요청 파라미터를 처리해주는 HandlerMethodArgumentResolver를 찾아야 한다.
메서드의 파라미터와 어노테이션 정보를 기반으로 argumentResolvers를 순회하며 적절한 구현체를 선택한다.
HandlerMethodArgumentResolver 객체의 supportsParameter(parameter) 메서드 호출해서
true인 ArgumentResolver 실행함
→ 파라미터에 @RequestBody가 붙음
→ RequestResponseBodyMethodProcessor가 선택되어 실행됨
5) HttpMessageConverter 찾기
요청 헤더의 Content-Type과 컨트롤러 메서드의 파라미터 타입을 기반으로
RequestResponseBodyMethodProcessor 내의 messageConverters를 순회하며
적절한 HttpMessageConverter 구현체 찾기. canRead() 가 true인 것
→ 요청 헤더의 Content-Type: application/json, 핸들러 메서드의 파라미터 타입: Member.class
→ MappingJackson2HttpMessageConverter가 선택되어 요청 메시지 JSON을 바로 자바 객체로 변환함
6) HandlerMethodReturnValueHandler 찾기
RequestMappingHandlerAdapter는 핸들러 메서드가 실행 완료 후 반환된 리턴 값을 처리하기 위해
HandlerMethodReturnValueHandler를 찾아야 한다.
메서드의 리턴 타입과 어노테이션 정보를 기반으로 returnValueHandlers를 순회하며 적절한 구현체를 선택한다.
HandlerMethodReturnValueHandler 객체의 supportsReturnType(returnType) 메서드 호출해서
true인 returnValueHandlers 실행함
→ 핸들러 메서드에 @ResponseBody가 붙어있음
→ RequestResponseBodyMethodProcessor가 선택되어 실행됨
7) HttpMessageConverter 찾기
요청 헤더의 Accept와 컨트롤러 메서드의 리턴 타입을 기반으로
RequestResponseBodyMethodProcessor 내의 messageConverters를 순회하며
적절한 HttpMessageConverter 구현체 찾기. canWrite() 가 true인 것
→ 요청 헤더의 Accept: application/json, 핸들러 메서드의 리턴 타입: Member.class
→ MappingJackson2HttpMessageConverter가 선택되어 자바 객체를 JSON으로 직렬화하여 응답 메시지 바디로 변환시킴
8) 이후 뷰 렌더링 생략, 바로 WAS에 응답 메시지 전달
RequestResponseBodyMethodProcessor는
JSON으로 변환된 데이터를 HttpServletResponse의 메시지 바디에 직접 작성하고 ModelAndView를 생성하지 않는다.
따라서 RequestMappingHandlerAdapter도 서블릿에 null을 반환하므로
이후 DispatcherServlet은 뷰 렌더링 과정을 생략하고 WAS에 응답 메시지를 바로 전달한다.