
안녕하세요, 오늘은 HTTP Message Converter 에 대해 정리해 보았습니다.
💡 View Template을 사용하여 응답하는것이 아니라 Rest API(HTTP API)로 JSON, TEXT, XML 등의 데이터를 HTTP Message Body에 직접 입력하는 경우 HTTP Message Converter를 사용한다.HTTP Message Converter
@Controller + View Template을 사용하여@RestController + Rest API를 통해 화면을 동적으로 그린다.html(${folders}) → js(ajax)MSA
사진출처 - SKT EnterPrise

해당 아키텍처를 가지게되면 구성된 Application마다 어떠한 언어를 사용하는지에 상관없이
서로 통신을 할수 있는데 이것이 가능한 이유는 JSON형태로 데이터 통신을하기 때문이다.
그림예시

Client → Server 의 경우에도 JSON을 사용한다.
Server → Server 의 경우에도 JSON을 사용한다.
각각의 Application들은 JSON 데이터를 해당언어의 Object로 변환시켜주는
Message Converter가 필요하다!
동작 순서

@ResponseBody 사용

HTTP Message Body에 String을 직접 입력 후 반환
String : StringHttpMessageConverter
JSON : MappingJackson2HttpMessageConverter
이외 기본적으로 만들어놓은 다양한 MessageConverter가 존재한다.
Response의 경우 HTTP Request Header의 Accept(허용) 헤더와
Controller 반환 Type 정보를 조합하여 Message Converter가 만들어진다.
Spring에서 HTTP Message Converter가 적용되는 두가지 예시
@RequestBody, HttpEntity<>, RequestEntity<> 가 사용된 경우(요청)@ResponseBody, HttpEntity<>, ResponseEntity<> 가 사용된 경우(응답)HttpMessageConverter 내부구조

HttpMessageConverter는 HTTP Request, HTTP Response 모두 사용된다.
canRead(), canWrite() 메서드로 클래스, 미디어 타입 지원여부를 체크한다.
boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);
boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);
read(), write() 메서드로 Http Message 를 읽고 쓴다.
T read(
Class<? extends T> clazz,
HttpInputMessage inputMessage
) throws IOException, HttpMessageNotReadableException;
void write(
T t,
@Nullable MediaType contentType,
HttpOutputMessage outputMessage
) throws IOException, HttpMessageNotWritableException;
ByteArrayHttpMessageConverter// 요청 헤더 -> content-type: */*
@PostMapping("/byte[]") // produces = "application/octext-stream"
public byte[] example(@RequestBody byte[] data) {
// logic
return data; // response
}
StringHttpMessageConverter// content-type: text/plain
@PostMapping("/string") // produces = "text/plain"
public String example(@RequestBody String data) {
// logic
return data;
}
MappingJackson2HttpMessageConverter// content-type: application/json
@PostMapping("/json") // produces = "application/json"
public Data example(@RequestBody Data data) {
// logic
return data;
}
@RequestBody 혹은 HttpEntity<>로 파라미터 바인딩한다.canRead() 메서드로 읽기가능 여부를 조회한다.Content-Type Media Type 지원여부 확인(consumes)boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);
read() 메서드를 호출하여 Object를 생성한다.
T read(
Class<? extends T> clazz,
HttpInputMessage inputMessage
) throws IOException, HttpMessageNotReadableException;
@ResponseBody 혹은 HttpEntity로 응답이 반환된다.canWrite() 메서드로 사용가능 여부를 조회한다.Accept Media Type 지원여부 확인(produces)boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);
write() 메서드를 호출하여 HTTP Response Message Body에 데이터를 입력한다.
void write(
T t,
@Nullable MediaType contentType,
HttpOutputMessage outputMessage
) throws IOException, HttpMessageNotWritableException;
// content-type: application/json
// {"key": "value"}
@PostMapping("/json-to-text") // produces = "text/plain"
public String example(@RequestBody String data) { // "{\"key\": \"value\"}"
// logic
return data;
}
ByteArrayHttpMessageConverter 의 canRead() 메서드 호출해보고 false 지나간다.StringHttpMessageConverter 의 canRead() 메서드 호출한다.
StringHttpMessageConverter가 동작한다.MappingJackson2HttpMessageConverter 는 동작하지 않는다.// content-type: application/json
// {"key": "value"}
@PostMapping("/json-to-json") // produces = "application/json"
public String example(@RequestBody Data data) { // // {"key": "value"}
// logic
return data;
}
ByteArrayHttpMessageConverter 의 canRead() 메서드 호출해보고 false 지나간다.StringHttpMessageConverter 의 canRead() 메서드 호출해보고 false 지나간다.MappingJackson2HttpMessageConverter 의 canRead() 메서드를 호출한다.
@Data
public class Data {
private String key;
}
// content-type: text/html
// // "{\"key\": \"value\"}"
@PostMapping("/text-to-json") // produces = "application/json"
public String example(@RequestBody Data data) {
// logic
return data;
}
ByteArrayHttpMessageConverter 의 canRead() 메서드 호출해보고 false 지나간다.StringHttpMessageConverter 의 canRead() 메서드 호출해보고 false 지나간다.MappingJackson2HttpMessageConverter 의 canRead() 메서드를 호출해보고 false
@RequestMapping 을 처리하는 HandlerAdapter의 구현체인RequestMappingHandlerAdapter가 어떤 일을 해준다!
동작방식

HttpServletRequest, Model, HttpEntity,@ModelAttribute, @RequestBody, @RequestParam 등RequestMappingHandlerAdapter는 Argument Resolver를 호출하여
ex) RequestHeaderArgumentResolver(@RequestHeader)

HandlerMethodArgumentResolver(Argument Resolver의 실제 이름)
인터페이스로 구성되어 있다. 즉, implements 하여 커스텀하게 파라미터를 만들 수 있다.(확장)

supportsParameter(MethodParameter parameter);
지원한다면 resolveArgument() 메서드를 통해 Object(객체)로 만들어준다.
만들어진 Object(객체)가 Controller 호출시 메서드의 파라미터로 전달된다.
supportsParameter()를 사용하는곳들

ModelAndView, @ResponseBody, HttpEntity<> 등이 있으면 응답에 필요한 값으로 변환해준다.
Controller에서 String으로 View Name 을 반환하여도 View가 동작하는 이유
Spring은 다양한 ReturnValueHandler를 기본적으로 제공한다.
ex) ModelAndViewMethodReturnValueHandler(ModelAndView 객체 반환)

ex) HttpEntityMethodProcessor(HttpEntity 객체 반환)

HandlerMethodReturnValueHandler (ReturnValueHandler의 실제 이름)인터페이스로 구성되어 있다. 즉, 확장이 가능하다.



위에서 보았던 RequestMappingHandlerAdapter 구조에서 HTTP Message Converter는
보이지 않는다. 어디쯤에 있는것인가?
실제 구조

RequestResponseBodyMethodProcessor 라는 Argument Resolver가 동작한다.
- `supportsParameter()` 존재. 설명은 아래
- `resolveArgument()` 존재. 설명은 아래
- `RequestResponseBodyMethodProcessor` Class Diagram

HandlerMethodArgumentResolver 상속. 설명은 아래HandlerMethodReturnValueResolver 상속.HttpEntityMethodProcessor 라는 Argument Resolver가 동작한다.
HttpEntityMethodProcessor의 class diagram

HandlerMethodArgumentResolver가 부모 클래스에 존재한다.
즉, HandlerMethodArgumentResolver 는 HttpEntity를 처리하는 Argument Resolver이다.
HttpEntityMethodProcessor.supportParameter()

HttpEntity 혹은 RequestEntity(HttpEntity를 상속받은 객체)의 경우인지 체크한다.
HttpEntityMethodProcessor.resolveArgument()

readWithMessageConverters(webRequest, parameter, paramType);@RequestBody 를 처리하는 ArgumentResolver가 존재한다.HttpEntity를 처리하는 ArgumentResolver가 존재한다.ArgumentResolver들이 HTTP Message Converter를 호출하여@ResponseBody 를 처리하는 ReturnValueHandler가 존재한다.HttpEntity를 처리하는 ReturnValueHandler가 존재한다.ReturnValueHandler 들이 HTTP Message Converter를 호출하여HandlerMethodArgumentResolver Argument ResolverHandlerMethodReturnValueHandler Return Value HandlerHttpMessageConverter//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.web.servlet.config.annotation;
import java.util.List;
import org.springframework.format.FormatterRegistry;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.lang.Nullable;
import org.springframework.validation.MessageCodesResolver;
import org.springframework.validation.Validator;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.servlet.HandlerExceptionResolver;
public interface WebMvcConfigurer {
default void configurePathMatch(PathMatchConfigurer configurer) {
}
default void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
}
default void configureAsyncSupport(AsyncSupportConfigurer configurer) {
}
default void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
}
default void addFormatters(FormatterRegistry registry) {
}
default void addInterceptors(InterceptorRegistry registry) {
}
default void addResourceHandlers(ResourceHandlerRegistry registry) {
}
default void addCorsMappings(CorsRegistry registry) {
}
default void addViewControllers(ViewControllerRegistry registry) {
}
default void configureViewResolvers(ViewResolverRegistry registry) {
}
default void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
}
default void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> handlers) {
}
default void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
}
default void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
}
default void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
}
default void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
}
@Nullable
default Validator getValidator() {
return null;
}
@Nullable
default MessageCodesResolver getMessageCodesResolver() {
return null;
}
}
필요한 메서드를 오버라이딩 하면된다.

@Component 를 포함하고 있다. (Spring Bean 등록이 된다.)

Spring Bean → 실제로 면접에서 나오기도한다. 스프링 빈의 생명주기, 빈 스코프
오늘의 코드카타
import java.util.Stack;
public class Solution {
public int[] solution(int[] arr) {
Stack<Integer> st = new Stack();
for (int i = 0; i < arr.length; i++) {
if (st.empty() || st.peek() != arr[i]) {
st.push(arr[i]);
}
}
int[] answer = new int[st.size()];
for (int i = st.size() - 1; i >= 0; i--) {
answer[i] = st.pop();
}
return answer;
}
}
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
Solution solution = new Solution();
int[] a1 = {1, 1, 3, 3, 0, 1, 1};
int[] a2 = {4, 4, 4, 3, 3};
System.out.println(Arrays.toString(solution.solution(a1)));
System.out.println(Arrays.toString(solution.solution(a2)));
}
}