Spring

HttpMessageConverter 간단 정리

꾸준함. 2021. 6. 11. 17:52

1. HttpMessageConverter 용도

  • HTTP API처럼 JSON 데이터를 HTTP 메시지 바디 내 직접 읽거나 쓰는 경우 사용
  • @ResponseBody 어노테이션을 사용할 때 HTTP Body 내 문자 내용을 직접 반환하므로 HttpMessageConverter가 동작
  • String 문자 처리에는 StringHttpMessageConverter, 객체 처리에는 MappingJackson2HttpMessageConverter 사용
  • 이외에도 다양한 HttpMessageConverter가 존재

 

* 응답의 경우 클라이언트의 HTTP Accept 헤더서버 컨트롤러의 반환 타입을 조합해 MessageConverter가 선택됨 (하단에 추가 설명)

 

2. 스프링 MVC의 HttpMessageConverter 적용 사례

  • HTTP 요청: @RequestBody, HttpEntity(RequestEntity)
  • HTTP 응답: @ResponseBody, HttpEntity(ResponseEntity)

 

3. HttpMessageConverter 인터페이스 뜯어보기


/**
* Strategy interface for converting from and to HTTP requests and responses.
*
* @author Arjen Poutsma
* @author Juergen Hoeller
* @author Rossen Stoyanchev
* @since 3.0
* @param <T> the converted object type
*/
public interface HttpMessageConverter<T> {
/**
* Indicates whether the given class can be read by this converter.
* @param clazz the class to test for readability
* @param mediaType the media type to read (can be {@code null} if not specified);
* typically the value of a {@code Content-Type} header.
* @return {@code true} if readable; {@code false} otherwise
*/
boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);
/**
* Indicates whether the given class can be written by this converter.
* @param clazz the class to test for writability
* @param mediaType the media type to write (can be {@code null} if not specified);
* typically the value of an {@code Accept} header.
* @return {@code true} if writable; {@code false} otherwise
*/
boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);
/**
* Return the list of media types supported by this converter. The list may
* not apply to every possible target element type and calls to this method
* should typically be guarded via {@link #canWrite(Class, MediaType)
* canWrite(clazz, null}. The list may also exclude MIME types supported
* only for a specific class. Alternatively, use
* {@link #getSupportedMediaTypes(Class)} for a more precise list.
* @return the list of supported media types
*/
List<MediaType> getSupportedMediaTypes();
/**
* Return the list of media types supported by this converter for the given
* class. The list may differ from {@link #getSupportedMediaTypes()} if the
* converter does not support the given Class or if it supports it only for
* a subset of media types.
* @param clazz the type of class to check
* @return the list of media types supported for the given class
* @since 5.3.4
*/
default List<MediaType> getSupportedMediaTypes(Class<?> clazz) {
return (canRead(clazz, null) || canWrite(clazz, null) ?
getSupportedMediaTypes() : Collections.emptyList());
}
/**
* Read an object of the given type from the given input message, and returns it.
* @param clazz the type of object to return. This type must have previously been passed to the
* {@link #canRead canRead} method of this interface, which must have returned {@code true}.
* @param inputMessage the HTTP input message to read from
* @return the converted object
* @throws IOException in case of I/O errors
* @throws HttpMessageNotReadableException in case of conversion errors
*/
T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException;
/**
* Write an given object to the given output message.
* @param t the object to write to the output message. The type of this object must have previously been
* passed to the {@link #canWrite canWrite} method of this interface, which must have returned {@code true}.
* @param contentType the content type to use when writing. May be {@code null} to indicate that the
* default content type of the converter must be used. If not {@code null}, this media type must have
* previously been passed to the {@link #canWrite canWrite} method of this interface, which must have
* returned {@code true}.
* @param outputMessage the message to write to
* @throws IOException in case of I/O errors
* @throws HttpMessageNotWritableException in case of conversion errors
*/
void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException;
}
view raw .java hosted with ❤ by GitHub

 

* canRead(), canWrite() 메서드: 메시지 컨버터가 해당 클래스 혹은 미디어타입을 지원하는지 체크하는 용도

* read(), write() 메서드: 메시지 컨버터를 통해서 메시지를 읽고 쓰는 기능 지원

 

3.1 HTTP 요청 데이터 읽는 과정

  • HTTP 요청이 들어오고, 컨트롤러에서 @RequestBody 혹은 HttpEntity 파라미터 사용하는 상황
  • MessageConverter가 메시지를 읽을 수 있는 확인하기 위해 canRead() 메서드 호출 (대상 클래스 타입을 지원하는지, HTTP 요청의 Content-Type 미디어 타입을 지원하는지 체크)
  • canRead() 조건을 만족할 경우 read() 메서드를 호출해서 객체 생성 후 반환

 

3.2 HTTP 응답 데이터 생성하는 과정

  • 컨트롤러에서 @ResponseBody 혹은 HttpEntity로 값이 반환되는 상황
  • MessageConverter가 메시지를 쓸 수 있는지 확인하기 위해 canWrite() 메서드 호출 (대상 클래스 타입을 지원하는지, HTTP 요청의 Accept 미디어 타입을 지원하는지 체크)
  • canWrite() 조건을 만족할 경우 write() 메서드를 호출해서 HTTP 응답 메시지 바디 내 데이터 생성

 

4. 주로 사용하는 메시지 컨버터 세 가지 (우선순위 순)

  • ByteArrayHttpMessageConverter
  • StringHttpMessageConverter
  • MappingJackson2HttpMessageConverter

 

* 앞서 언급한 대로 스프링에서 메시지 컨버터를 선정할 때 대상 클래스 타입과 미디어 타입을 체크 후 사용 여부를 결정하고, 만족하지 않을 경우 다음 우선순위에 있는 메시지 컨버터로 넘어가 체크 진행

 

4.1 ByteArrayHttpMessageConverter

  • byte [] 데이터 처리
  • 클래스 타입: byte []
  • 미디어 타입: */*
  • HTTP 요청 예시: @RequestBody byte [] example
  • HTTP 응답 예시: @ResponeBody return byte[] (쓰기 MediaType: application/octet-stream)

 

4.2 StringHttpMessageConverter

  • String 문자열로 데이터 처리
  • 클래스 타입: String
  • 미디어 타입: */*
  • HTTP 요청 예시: @RequestBody String example
  • HTTP 응답 예시: @ResponseBody return example (쓰기 MediaType: text/plain)

 

 

content-type: text/plain
@RequestMapping
String stringHttpMessageConverterExample(@RequestBody String example) {
return example;
}
view raw .java hosted with ❤ by GitHub

 

 

4.3 MappingJackson2HttpMessageConverter

  • application/json 처리
  • 클래스 타입: 객체 또는 HashMap
  • 미디어 타입: application/json
  • HTTP 요청 예시: @RequestBody Example example
  • HTTP 응답 예시: @ResponseBody return example (쓰기 MediaType: application/json)


content-type: application/json
@ResponseStatus(HttpStatus.OK)
@ResponseBody
@GetMapping("/response-body-json")
public Example responseBodyJson(@RequestBody Example example) {
return example;
}
view raw .java hosted with ❤ by GitHub

 

4.4 오류가 나는 경우


content-type: text/html
@RequestMapping
void ErrorExample(@RequestBody Example example) {
// 중략
}
view raw .java hosted with ❤ by GitHub

 

* 대상 클래스 타입과 미디어 타입이 매칭이 안되기 때문에 오류가 발생

 

HttpMessageConverter가 쓰이는 시점?

 

우선, 기존 게시물에서 작성한 Spring MVC 구조를 살펴봅시다. (https://jaimemin.tistory.com/1820)

 

Spring MVC 구조

 

* 위 그림에서는 HttpMessageConverter가 쓰이는 시점이 보이지 않지만, HttpMessageConverter는 아래 사진처럼 @RequestMapping을 처리하는 HandlerAdapter인 RequestMappingHandlerAdapter에서 쓰입니다.

 

HttpMessageConverter가 쓰이는 시점

 

  • 어노테이션 기반 컨트롤러는 HttpServletRequest, Model, @RequestParam, @ModelAttribute, @RequestBody, HttpEntity와 같이 다양한 파라미터를 사용할 수 있는데 이렇게 유연하게 처리해줄 수 있는 이유는 ArgumentResovler 덕분
  • 즉, 컨트롤러를 처리하는 RequestMappingHandlerAdapter가 ArgumentResolver를 호출하면서 필요로 하는 다양한 파라미터의 값을 생성한 후 컨트롤러를 호출하며 파라미터를 넘겨줌 (사진 내 1번과 2번 과정)
  • ReturnValueHandler는 응답 값을 변환하고 처리하는데, 컨트롤러에서 String으로 뷰의 논리적 이름만 반환하더라도 동작하는 이유가 ReturnValueHandler 덕분 (사진 내 3번 과정)
  • 이 과정에서 HttpMessageConverter는 요청과 응답을 처리하는 과정에서 모두 필요한데 아래와 같은 상황에서 사용이 됨
    • 요청: @RequestBody 혹은 HttpEntity를 처리하는 ArgumentResovler가 HttpMessageConverter를 사용해서 필요하는 객체를 생성
    • 응답: @ResponseBody 혹은 HttpEntity를 처리하는 ReturnValueHandler에서 HttpMessageConverter를 사용해서 응답 결과 생성

 

출처

인프런 스프링 MVC 1편 (김영한 강사님)

반응형