뷰 템플릿으로 HTML을 생성해서 응답하는 것이 아니라, HTTP API처럼 JSON 데이터를 HTTP 메시지 바디에서 직접 읽거나 쓰는 경우 HTTP 메시지 컨버터를 사용하면 편리하다.
@ResponseBody 사용 원리
1. 웹 브라우저가 요청을 받는다.
2. 컨트롤러를 호출하고 이때 @ResponseBody
가 있으면 HttpMessageConverter
가 동작한다.
3. 이때 @ResponseBody
에서 응답(return)
이 나온다.
4. 이때 응답으로 String
이 나온다면 String 컨버터
가 선택이 되고, Json
이 나온다면 Json 컨버터
가 선택이 된다.
@ResponseBody를 사용
viewResolver
대신에 HttpMessageConverter
가 동작 StringHttpMessageConverter
(String 반환시)MappingJackson2HttpMessageConverter
json
으로 변환되어서 응답 메세지로 나감.HttpMessageConverter
가 기본으로 등록되어 있음참고
응답의 경우 클라이언트의HTTP Accept 헤더
와 서버의컨트롤러 반환 타입
정보 둘을 조합해서HttpMessageConverter
가 선택된다.
- HTTP 요청 메세지의 Accept의 역할은 '나는 (텍스트, Json, html 등) 메세지를 해석할 수 있다. 나는 (텍스트, Json, html 등)으로 줘라' 라고 간단하게 생각하면 된다.
스프링 MVC는 다음의 경우에 HTTP 메시지 컨버터를 적용한다.
@RequestBody
, HttpEntity(RequestEntity)
@ResponseBody
, HttpEntity(ResponseEntity)
org.springframework.http.converter.HttpMessageConverter
package org.springframework.http.converter;
public interface HttpMessageConverter<T> {
boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);
boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);
List<MediaType> getSupportedMediaTypes();
T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException;
void write(T t, @Nullable MediaType contentType, HttpOutputMessage
outputMessage)
throws IOException, HttpMessageNotWritableException;
}
HTTP Message Converter는 HTTP 요청, HTTP 응답 둘 다 사용된다.
canRead()
, canWrite()
: 메시지 컨버터가 해당 클래스, 미디어타입을 지원하는지 체크한다.read()
, write()
: 메시지 컨버터를 통해서 메시지를 읽고 쓰는 기능을 한다.HTTP Message Converter
는 HTTP 응답에 있는 Message Body
를 읽어서 객체로 바꿔서 Controller
의 Parameter
로 넘겨주는 역할도 하고 또 하나는 Controller
에서 return
값을 가지고 HTTP 응답 메시지
를 넣는 역할도 한다.
위에서 소개한 S
tringHTTPMessageConverter
,MappingJackson2HttpMessageConverter
는HttpMessageConverter
interface를 상속한다.
0 = ByteArrayHttpMessageConverter
1 = StringHttpMessageConverter
2 = MappingJackson2HttpMessageConverter
Spring boot
는 다양한 메시지 컨버터
를 제공하는데, 대상 클래스 타입
과 미디업 타입
을 체크해서 사용 여부를 결정한다. 우선순위 최상단 컨버터의 조건을 체크하고 만족하지 않으면 다음 메시지 컨버터로 우선순위가 넘어간다.
몇 개의 주요한 메시지 컨버터를 알아보자.
ByteArrayHttpMessageConverter
byte[]
, 미디어타입: */*
,StringHttpMessageConverter
String
, 미디어타입: */*
MappingJackson2HttpMessageConverter
객체 또는 HashMap
, 미디어타입: application/json 관련
미디어타입:
*/*
<- 아무 미디어 타입이든 상관없다는 뜻이다.
다음 예시들을 보면서 HTTP Message Converter를 이해해보자.
아래의 경우 StringHttpMessageConverter 호출
content-type: application/json
@RequestMapping
void hello(@RequestBody String data) {}
Class 타입
은 String
이고 미디어 타입
은 json
인 것을 확인할 수 있다. 먼저 우선순위 최상단 메세지 컨버터인 ByteArrayHttpMessageConverter가 조건을 체크하지만 클래스 타입이 달라서 조건을 만족하지 않는다. StringHttpMessageConverter로 우선순위가 넘어가게 되고 클래스 타입과 미디어 타입이 조건을 만족하므로 StringHttpMessageConverter이 선택된다.
아래의 경우 MappingJackson2HttpMessageConverter 호출
content-type: application/json
@RequestMapping
void hello(@RequestBody HelloData data) {}
클래스 타입
은 객체
, 미디어 타입
은 Json
이다. ByteArrayHttpMessageConverter와 StringHttpMessageConverter는 조건을 만족하지 않아서 MappingJackson2HttpMessageConverter로 우선순위가 넘어오게 되고 조건 또한 만족하므로 MappingJackson2HttpMessageConverter가 호출된다.
아래의 경우 호출 가능한 HTTP Message Converter ❌
content-type: text/html
@RequestMapping
void hello(@RequestBody HelloData data) {}
클래스 타입
은 객체
, 미디어 타입
은 text/html
이다. 조건에 맞는 HTTP Message Converter
가 없으므로 에러가 발생한다.
그렇다면 Spring
은 상황에 따라 어떤 HTTP Messag Converter
를 사용해야하는지 어떻게 알 수 있을까?
HTTP 요청 데이터 읽기
@RequestBody
, HttpEntity
파라미터를 사용한다. canRead()
를 호출한다.@RequestBody
의 대상 클래스 (byte[]
, String
, HelloData
)Content-Type
미디어 타입을 지원하는가. text/plain
, application/json
, */*
canRead()
조건을 만족하면 read()
를 호출해서 객체 생성하고, 반환한다.HTTP 응답 데이터 생성
@ResponseBody
, HttpEntity
로 값이 반환된다.메시지 컨버터
가 메시지를 쓸 수 있는지 확인하기 위해 canWrite()
를 호출한다.byte[]
, String
, HelloData
)Accept
미디어 타입을 지원하는가.(더 정확히는 @RequestMapping
의 produces
) text/plain
, application/json
, */*
canWrite()
조건을 만족하면 write()
를 호출해서 HTTP 응답 메시지 바디에 데이터를 생성한다.지금까지 HTTP Message Converter
에 대해서 알아보았다. 그렇다면 HTTP Message Converter
는 Spring MVC
의 어디에서 사용되는 것일까?
위의 Spring MVC
의 구조를 봐도 HTTP Message Converter
는 보이지 않는다.
@RequestMapping
을 처리하는 핸들러 어댑터인 RequestMappingHandlerAdapter
(요청 매핑 핸들러 어댑터)에 있다.Spring MVC
동작 과정에서 핸들러 어댑터
<-> 컨트롤러
과정을 조금 더 세부화 한다면 아래의 그림처럼 된다.
1. Dispatcher servlet
이 RequestMappingHandlerAdapter
호출
2. RequestMappingHandlerAdapter
가 컨트롤러 호출
3. 근데 이때 컨트롤러를 호출할 때에는 request
, response
의 값, @RequestParam
의 파라미터, inputstream
등 어디선가 이들에게 데이터를 던져주고 있다. 이건 무엇일까? 바로 Argument Resolver
이다.
핸들러 어댑터가 컨트롤러를 호출하기 전에 Argument Resolver
를 호출하고 컨트롤러에서 핸들러 어댑터로 반환하기 전에 ReturnValueHandler
가 호출되는 것을 볼 수 있다.
핸들러 어댑터
가핸들러(컨트롤러)
를 보고 빨간색, 파란색 파라미터가 필요한 것을 확인하면Argument Resolver
에게 물어본다. 그러면Argument Resolver
가 파라미터 2개를 생성해준다. 그때핸들러 어댑터
가핸들러(컨트롤러)
를 호출하면서Argument Resolver
를 통해 생성된 파라미터를 넣어준다.
컨트롤러의 요청 파라미터로 어떤 타입들이 올 수 있는지 생각해보면 애노테이션 기반의 컨트롤러는 매우 다양한 파라미터를 사용할 수 있다.
HttpServletRequest
, Model
, @RequestParam
, @ModelAttribute
같은 애노테이션 그리고 @RequestBody
, HttpEntity
같은 HTTP 메시지를 처리하는 부분까지 매우 큰 유연함을 보여주었다.
이렇게 파라미터를 유연하게 처리할 수 있는 이유가 바로 ArgumentResolver
덕분이다.
애노테이션 기반 컨트롤러를 처리하는 RequestMappingHandlerAdapter
는 바로 이 ArgumentResolver
를 호출해서 컨트롤러(핸들러)
가 필요로 하는 다양한 파라미터의 값(객체)을 생성한다. 그리고 이렇게 파리미터의 값이 모두 준비되면 컨트롤러를 호출하면서 값을 넘겨준다.
스프링은 30개가 넘는
ArgumentResolver
를 기본으로 제공한다. 정확히는HandlerMethodArgumentResolver
인데 줄여서ArgumentResolver
라고 부른다.
참고
가능한 파라미터 목록은 다음 공식 메뉴얼에서 확인할 수 있다.
https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-arguments
HandlerMethodArgumentResolver
public interface HandlerMethodArgumentResolver {
boolean supportsParameter(MethodParameter parameter);
@Nullable
Object resolveArgument(MethodParameter parameter, @Nullable
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory
binderFactory) throws Exception;
}
동작 방식
1. HandlerMethodArgumentResolver
인데 supportsParameter()
를 호출해서 해당 파라미터를 지원하는지 체크한다.
2. 해당 파라미터를 지원하면 resolveArgument()
를 호출해서 실제 객체를 생성한다.
3. 이렇게 생성된 객체가 컨트롤러 호출시 넘어가는 간다.
원한다면 직접 이 인터페이스를 확장해서 원하는
ArgumentResolver
를 만들 수도 있다.
HandlerMethodReturnValueHandler
를 줄여서 ReturnValueHandler
라 부른다. ArgumentResolver
와 비슷한데, 이것은 응답 값을 변환하고 처리한다.
컨트롤러에서 String
으로 뷰 이름을 반환해도, 동작하는 이유가 바로 ReturnValueHandler
덕분이다.
스프링은 10여개가 넘는 ReturnValueHandler
를 지원한다.
예) ModelAndView, @ResponseBody, HttpEntity, String
참고
가능한 응답 값 목록은 다음 공식 메뉴얼에서 확인할 수 있다.
https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-return-types
HTTP 메시지 컨버터는 어디쯤 있을까?
HTTP Message Converter
를 사용하는 @RequestBody
도 컨트롤러가 필요로 하는 파라미터의 값에 사용된다. @ResponseBody
의 경우도 컨트롤러의 반환 값을 이용한다.
HTTP Message Converter
는Argument Resolver
가 사용하는 것이다. 또한returnValueHandler
에서 반환할 때도 사용한다.
요청의 경우
@RequestBody
를 처리하는 ArgumentResolver
가 있고, HttpEntity
를 처리하는 ArgumentResolver
가 있다. 이 ArgumentResolver
들이 HTTP Message Converter
를 사용해서 필요한 객체를 생성하는 것이다.응답의 경우
@ResponseBody
와 HttpEntity
를 처리하는 ReturnValueHandler
가 있다. 그리고 여기에서 HTTP Message Converter
를 호출해서 응답 결과를 만든다.스프링 MVC는
@RequestBody
, @ResponseBody
가 있으면 RequestResponseBodyMethodProcessor(ArgumentResolver)
를 사용한다.HttpEntity
가 있으면 HttpEntityMethodProcessor (ArgumentResolver)
를 사용한다.스프링은 다음을 모두 인터페이스로 제공한다. 따라서 필요하면 언제든지 기능을 확장할 수 있다.
스프링이 필요한 대부분의 기능을 제공하기 때문에 실제 기능을 확장할 일이 많지는 않다. 기능 확장은 WebMvcConfigurer를 상속 받아서 스프링 빈으로 등록하면 된다.
실제 자주 사용하지는 않으니 실제 기능 확장이 필요할 때 WebMvcConfigurer를 검색해보자.
WebMvcConfigurer 확장
@Bean
public WebMvcConfigurer webMvcConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addArgumentResolvers(
List<HandlerMethodArgumentResolver>resolvers) {
//...
}
@Override
public void extendMessageConverters(
List<HttpMessageConverter<?>>converters) {
//...
}
};
}