본문 바로가기
Backend/Spring | SpringBoot

[SpringBoot] HTTP 메시지 컨버터 (+ArugumentResolver, ReturnValueHandler)

by 2245 2023. 9. 1.

목차

     

     

    HTTP API처럼 뷰가 아닌 JSON 데이터를 HTTP 메시지 바디에서 직접 읽거나 쓰는 경우, HTTP 메시지 컨버터를 사용합니다.

     

    HttpMessageConverter

    @ResponseBody 사용 원리 

    • HTTP의 Body에 문자 내용을 직접 반환합니다.
    • viewResolver 대신 HttpMessageConverter가 동작합니다.
    • 기본 문자 처리 : StringHttpMessageConverter
    • 기본 객체 처리 : MappingJackson2HttpMessageConverter
    • 이외에도, byte 처리 등 기타 여러 HttpMessageConverter가 등록되어 있습니다. 
    • 응답의 경우, 클라이언트의 Http Accept 헤더와 서버의 컨트롤러 반환 타입 둘을 조합해 HttpMessageConverter가 선택됩니다. 

     

    Spring MVC의 HTTP 메시지 컨버터

    스프링 MVC는 다음의 경우에 HTTP 메시지 컨버터를 적용합니다.

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

     

    HttpMessageConverter 인터페이스

    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;
    }

     

    HttpMessageConverter는 HTTP 요청, HTTP 응답 둘 다 사용됩니다.

    • canRead(), canWrite() : 해당 메시지 컨버터가 해당 클래스와 미디어타입을 지원하는지 체크
    • read(), write() : 메시지 컨터버를 통해 읽고 쓰는 기능

     

    스프링 부트 기본 메시지 컨버터

    0, 1, 2 : 우선순위 (일부 생략)

    0 = ByteArrayHttpMessageConverter
    1 = StringHttpMessageConverter
    2 = MappingJackson2HttpMessageConverter

     

    스프링부트는 다양한 메시지 컨버터를 제공하는데, 대상 클래스 타입과 미디어 타입 둘을 체크하여 사용 여부를 결정합니다.

    만약, 만족하지 않는다면 다음 메시지 컨버터로 우선순위가 넘어갑니다.

    • ByteArrayHttpMessageConverter : byte[] 데이터를 처리합니다.
      • 클래스 타입: byte[]
      • 미디어 타입 : */*
      • 요청 예) @RequestBody byte[] data
      • 응답 예) @ResponseBbody return byte[] , 쓰기 미디어 타입 (응답 헤더의 Content-type) : application/octect-stream
    • StringHttpMessageConverter : String 문자로 데이터를 처리합니다.
      • 클래스 타입 : String
      • 미디어 타입 : */*
      • 요청 예) @RequestBody String data
      • 응답 예) @ResponseBody return "ok", 쓰기 미디어 타입 : text/plain
    • MappingJackson2HttpMessageConverter : application/json 타입의 데이터를 처리합니다.
      • 클래스 타입 ; 객체 또는 HashMap
      • 미디어 타입 ; application/json 관련
      • 요청 예) @RequestBody HelloData data
      • 응답 예) @ResponseBody return helloData, 쓰기 미디어 타입 : application/json 관련

     

    예제: Http 메시지 컨버터 선택

    1. StringHttpMessageConverter

    content-type: application/json

    @RequestMapping
    void hello(@RequestBody String data) {
        ...
    }

     

    → StringHttpMessageConverter가 선택됩니다.

    (StringHttpMessageConverter는 미디어타입이 */* 이므로 어떠한 미디어타입도 상관이 없습니다.)

    2. MappingJackson2HttpMessageConverter

    content-type: application/json

    @RequestMapping
    void hello(@RequestBody HelloData data) {
        ...
    }   

     

    → MappingJackson2HttpMessageConverter가 선택됩니다.

    3. X

    content-type: text/html

    @RequestMapping
    void hello(@RequestBody HelloData data) {
        ....
    }

     

    → 메시지 컨버터가 동작하지 않습니다.

     

    RequestBody의 타입이 객체이기 때문에 우선순위부터 차례대로 살펴보면 ByteArrayHttpMessageConverter, StringHttpMessageConverter 모두 탈락됩니다.

    마지막 MappingJackson2HttpMessageConverter의 클래스 타입은 맞으나, 미디어 타입이 application/json이 아니므로 어떠한 메시지 컨버터도 동작하지 않습니다.

     

     

    HttpMessageConverter 동작 과정

    HTTP 요청 데이터

    1. HTTP 요청이 오고, 컨트롤러에서 @RequestBody 또는 HttpEntity 파라미터를 사용합니다.
    2. 해당 메세지 컨버터가 메시지를 읽을 수 있는지 확인하기 위해 canRead()를 호출합니다.
      • 대상 클래스 타입을 지원하는가?
        예) @RequestBody의 대상 클래스 : byte[], string, HelloData
      • HTTP 요청의 Content-type 미디어 타입을 지원하는가?
        예) text/plain, application/json, */*
    3. canRead() 조건에 만족하면 read()를 호출해 객체를 생성하고 반환합니다.

     

    HTTP 응답 데이터

    1. 컨트롤러에서 @ResponseBody 또는 HttpEntity를 통해 값이 변환됩니다.
    2. 해당 메시지 컨버터가 메시지를 쓸 수 있는지 확인하기 위해 canWriter()를 호출합니다.
      • 대상 클래스 타입을 지원하는가?
        예) byte[], String, HelloData
      • HTTP 요청의 Accept 미디어 타입을 지원하는가? (더 정확히는 @RequestMapping의 produces)
        예) text/plain, application/json, */*
    3. canWrite() 조건을 만족하면 write()를 호출하여 HTTP 응답 메시지 바디에 데이터를 입력합니다. 

     

    요청 핸들러 어댑터 

    그렇다면 HTTP 메시지 컨버터는 스프링 MVC 구조 중 어디에서 사용되는 것일까요?

    SpringMVC 구조

    답은 애노테이션 기반의 컨트롤러 즉, @RequestMapping 을 처리하는 핸들러 어댑터인 RequestMappingHandlerAdapter에 있습니다.

     

    RequestMappingHandlerAdater 동작 방식

    ArugumentResolver

    애노테이션 기반의 컨트롤러는 매우 다양한 파라미터를 사용할 수 있습니다.

    HttpServletRequest, Model 은 물론이고, @RequestParam, @ModelAttribute 같은 애노테이션, @RequestBody, HttpEntity 같은 HTTP 메시지를 처리하는 부분까지 매우 큰 유연함을 보여줍니다.

    이렇게 여러 파라미터를 유연하게 처리할 수 있는 이유는 ArgumentResovler 덕분입니다.

     

    애노테이션 기반 컨트롤러를 처리하는 RequestMappingHandlerAdapter는 이 ArgumentResolver를 호출해 컨트롤러(핸들러)가 필요로 하는 다양한 파라미터 값(객체)를 생성합니다.

     

    스프링은 30개가 넘는 ArgumentResolver를 기본으로 제공합니다.

    참고 가능한 파라미터 목록은 다음 공식 메뉴얼에서 확인할 수 있습니다.
    https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-controller/ann-methods/arguments.html

     

    ArgumentResolver의 정확한 이름은 HandlerMethodArgumentResolve인데, 줄여서 ArgumentResolver라고 부릅니다.

    public interface HandlerMethodArgumentResolver {
    
        boolean supportsParameter(MethodParameter parameter);
    
        @Nullable
        Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;
    }
    1. supportsParameter를 호출해 해당 파라미터를 지원하는지 체크합니다.
    2. 지원한다면, resolveArgument()를 호출하여 실제 객체를 생성합니다. 
    3. 이렇게 생성된 객체가 컨트롤러 호출 시 파라미터로 넘어갑니다.
    참고
    여러분이 원한다면 직접 이 인터페이스를 확장해 원하는 ArgumentResolver를 만들 수도 있습니다.

     

    ReturnValueHandler

    HandlerMethodReturnValueHandler를 줄여서 ReturnValueHandler라 부릅니다.

    ArgumentResolver와 비슷합니다. 단, 요청 값이 아닌 응답 값을 변환하고 처리합니다.

     

    컨트롤러에서 String으로 뷰 이름을 반환해도 동작하는 이유는 바로 이 ReturnValueHandler 덕분입니다.

    스프링은 10여개가 넘는 ReturnValueHandler를 지원합니다.

    예) ModelAndView, @ResponseBody, HttpEntity, String

    참고 가능한 응답 값 목록은 다음 공식 메뉴얼에서 확인할 수 있습니다.
    https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-controller/ann-methods/return-types.html

     

    HTTP 메시지 컨버터 위치

    • 위에서 설명한 @RequestBody와 HttpEntity를 처리하는 ArumentResolver들이 HTTP 메시지 컨버터를 사용해 객체를 생성합니다.
    • 또한, @ResponseBody와 HttpEntity를 처리하는 ReturnValueHandler가 HTTP 메시지 컨버터를 호출하여 응답 결과를 만듭니다.
    • 스프링 MVC는 @RequestBody 또는 @ResponseBody가 있다면 RequestResponseBodyMethodProcessor(ArgumentResolver)를, HttpEntity가 있다면 HttpEntityMethodProcessor(ArugumentResolver)를 사용합니다.

     

    확장

    스프링은 다음을 모두 인터페이스로 제공합니다. 따라서 필요하면 언제든지 기능을 확장할 수 있습니다. 

    • HandlerMethodArugmentResolver
    • HandlerMethodReturnValueHandler
    • HttpMessageConverter
    참고
    필요한 대부분의 기능을 제공하기 때문에 실제 기능을 확장할 일이 많지는 않습니다. 
    기능 확장은 WebMvcConfigurer 를 상속 받아 스프링 빈으로 등록하면 됩니다. 
    실제 자주 사용하지는 않으니 실제 기능 확장이 필요할 때 WebMvcConfigurer를 검색해봅시다.

     

    WebMvcConfigurer

    @Bean
    public WebMvcConfigurer webMvcConfigurer() {
        return new WebMvcConfigurer() {
    
            @Override
            public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
               ...
            }
    
            @Override
            public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
               ...
            }
        };
    }

     

     


    출처

    https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1/dashboard

     

    스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 - 인프런 | 강의

    웹 애플리케이션을 개발할 때 필요한 모든 웹 기술을 기초부터 이해하고, 완성할 수 있습니다. 스프링 MVC의 핵심 원리와 구조를 이해하고, 더 깊이있는 백엔드 개발자로 성장할 수 있습니다., 원

    www.inflearn.com