HTTP 메시지 컨버터
@ResponseBody가 있으면 HttpMessageConverter가 동작한다.
반환값에 따라 JsonConverter가 동작해야할지, StringConverter가 동작해야할지등
반환 데이터타입에 따른 컨버터가 동작한다.
@ResponseBody가 있으면 HTTP 응답메시지 Body에 문자 내용을 직접 반환한다.
반환되는 값이 논리뷰이름으로 사용되는것이 아니므로 viewResolver대신 HttpMessageConverter가 동작한다.
String을 반환하면 기본문자 처리담당인 StringHttpMessageConverter가 동작하고
기본 객체를 반환하면 기본객체 처리담당인 MappingJackson2HttpMessageConverter가 동작한다.
(객체를 JSON으로 바꿔주는 그 컨버터이다.)
(@ResponseBody가 붙어있으면 객체든 Stirng이든 뭐든간에 http메시지컨버터가 동작한다 -> http응답메시지 바디에 넣어야하기 때문)
(@HttpEntity 또한 http 메시지 바디를 읽거나 써야하기 때문에 http메시지컨버터가 동작한다.)
클라이언트가 받을수있는 타입에 대한 헤더인 Accept헤더와
서버의 컨트롤러 반환타입정보를 조합해서 HttpMessage컨버터가 선택된다
Http Message Converter가 동작하는 경우
@RequestBody를 쓰면 JSON형식을 객체로 변환해서 받을수도 있었다. 그러므로 컨버터가 동작할것이다.
@ResponseBody를 사용하는 경우 객체를 반환할수도있었다. 그때 컨버터가 동작해야 JSON으로 변환시켜 응답메시지바디를 구성할것이다.
HttpEntity관련한 객체들은 당연하다.
요청을 받는쪽에서는 저장할 타입,객체를 정하는데 해당 값이 들어오면 변환해서 저장해야하므로 컨버터가 동작하고
응답을 내는쪽에서는 응답할수있는 메시지형태로 바꿔야하므로 컨버터가 동작한다.
RequestEntity<객체>를 해주면 JSON을 객체로 변환해준다.
ResponseEntity<객체>를 통해 객체를 JSON으로 변환 후 응답메시지바디를 구성해 응답한다.
(@ResponseBody가 없는 @Controller의 컨트롤러에서도 HttpEntity<> , ResponseEntity<>는 메세지컨버터가 동작해서 해당 객체를 JSON으로 변환해서 응답내준다. 당연하게도 HttpEntity<> , ResponseEntity<>는 http응답메시지 바디에 직접 값을 넣어야하니까 http메시지컨버터가 동작한다. (@ResponseBody가 없어도) )
Http 메시지 컨버터 인터페이스
Http 메시지 컨버터는 인터페이스로 되어있고,
다양한 타입의 컨버터는 그 구현체이다.
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요청일때는 Content-Type을 의미한다.
클래스타입은 String,int,객체등 을 의미한다.
요청메시지이든 응답메시지이든 메시지바디에 값이 있으면 해당 값의 Content-Type을 지정해줘야한다.
HttpMessageConverter란
바이트로 들어오는 데이터를 원하는 클래스타입으로 변환시켜주고,
클래스타입의 반환을 받아 Http응답메시지바디에 넣기위해 변환을 시켜주는 역할이다.
클래스타입이란
들어온 데이터를 "넣을 타입" , "반환할 데이터 타입"을 의미한다.
@Request String data 로 설정했다면 클래스타입은 String인것이다. String으로 데이터를 변환받기를 선택한것이므로 클래스타입이 String인 StringHttpMessageConverter가 동작할것이다.
ByteArrayHttpMessageConverter는 클래스타입이 byte[]이고 미디어타입이 */*이다.
요청메시지의 데이터를 byte[]로 받으려고할때 (->@RequestBody byte[] data 처럼 들어오는 데이터를 byte[]로 받을때)
ByteArrayHttpMessageConverter가 동작하여 어떤 데이터이든,
어떤 Content-Type이든 상관없이(미디어타입이 */*이므로) byte[]로 변환시킨다.
응답데이터로 byte[]를 쓰려고할때
ByteArrayHttpMessageConverter가 byte[] 데이터를 Http응답메시지바디에 넣기위해 응답 Content-Type으로 변환해서 넣어준다. 그때 응답의 Content-Type은 application/octet-stream으로 설정된다.그래서 Http요청 메시지의 Accep헤더를 확인해서 응답으로 주려는 Content-Type이 지원하는지 체크를 먼저한다.
StringHttpMessageConverter는 클래스타입이 String이고 미디어타입은 아무거나이다.
어떤 데이터이든 , 어떤 Content-type의 요청이든 String으로 받겠다고 하면 (=@Request String data로 설정)
StringHttpMessageConverter가 요청데이터를 String으로 변환시킨다.
응답할때 String데이터로 Http응답메시지바디를 채워 응답하고 싶다면 (return "hi")
StringHttpMessageConverter가 String 데이터를 응답http메시지 바디에 들어가게끔 응답 Content-Type으로 변환해서 넣어준다. 그때 응답의 Content-Type은 text/plain이다.그래서 Http요청 메시지의 Accep헤더를 확인해서 응답으로 주려는 Content-Type이 지원하는지 체크를 먼저한다.
MappingJackson2HttpMessageConverter는 클래스타입이 객체 또는 HashMap이고
미디어타입은 application/json관련 타입이다.
application/json관련 Content-Type의 요청데이터가 들어왔을때 그 데이터를 받고싶은 클래스타입이
객체이거나 HashMap이라면 MappingJackson2HttpMessageConverter이 요청데이터를 해당 클래스타입으로 변환시켜준다.
응답할때 객체 또는 HashMap을 반환하려고하면 ( return helloData;)
MappingJackson2HttpMessageConverter가 해당 객체 또는 HashMap을 응답http메시지 바디에 들어가게끔 응답 Content-Type으로 변환시켜 넣어준다. 그때 응답의 Content-Type은 application/json관련으로 설정된다.
동작 예시)
요청 데이터는 아래와 같다.
Content-Type = application/json
@RequestMapping
void hello(@RequestBody String hello)
라고 되어있을때
1. ByteArrayHttpMessageConverter의 canRead()를 실행해본다.
-> @RequestBody String hello를 보면 클래스타입이 String이므로 Pass
2. StringHttpMessageConverter의 canRead()를 실행해본다.
StringHttpMessageConverter의 지원하는 클래스타입은 String이라 괜찮다. 그다음 체크해야할것은 미디어타입
이다. 들어온 데이터타입의 미디어타입은 application/json이다. StringHttpMessageConverter는 */*이므로 어떤 미디어타입도 상관없다. 즉 클래스타입도 미디어타입도 조건에 만족하므로 StringHttpMessageConverter가 동작한다.
동작 예시2)
요청 데이터는 아래와 같다.
Content-Type = application/json
@RequestMapping
void hello(@RequestBody HelloData hello)
라고 되어있을때
HelloData 즉 객체이므로 ByteArrayHttpMessageConverter, StringHttpMessageConverter 둘다 탈락
MappingJackson2HttpMessageConverter가 객체 클래스타입을 지원하므로 일단 클래스타입은 통과
그리고 들어온 요청데이터의 미디어타입을 보니 application/json이고 MappingJackson2HttpMessageConverter가 지원하는 미디어타입을 보니 application/json관련이니까 통과이다.
즉 클래스타입,미디어타입 둘다 만족하므로 MappingJackson2HttpMessageConverter가 동작한다.
동작 예시3)
요청 데이터는 아래와 같다.
Content-Type = text/html
@RequestMapping
void hello(@RequestBody HelloData hello)
라고 되어있을때
HelloData 즉 객체이므로 ByteArrayHttpMessageConverter, StringHttpMessageConverter 둘다 탈락
MappingJackson2HttpMessageConverter가 객체 클래스타입을 지원하므로 일단 클래스타입은 통과
그리고 들어온 요청데이터의 미디어타입을 보니 text/html인데 MappingJackson2HttpMessageConverter가 지원하는 미디어타입을 보니 application/json관련이니까 통과하지못한다. -> 탈락!
이런식으로 하나씩 컨버터를 순서대로 canRead()를 동작시키면서 받을수있는 컨버터인지 체크한다.
밑의 ArgumentResolver를 보면 좀 더 이해가 쉬울것이다.
요청 매핑 핸들러 어뎁터(RequestMappingHandlerAdapter) 구조
@RequestMapping handler를 처리하는 핸들러 어댑터인 RequestMappingHandlerAdapter
(@RequestMapping를 내부에 가지고있는 @GetMapping,@PostMapping....)
DispatcherServlet이 핸들러를 동작시키기 위해 핸들러 어댑터를 찾아오고 , 핸들러어댑터에서 핸들러 내부의 메소드를 실행시키는데 그때 컨트롤러(핸들러)내부 메소드의 전달인자는 ArgumentResolver에서 컨트롤러의 파라미터,어노테이션정보를 기반으로 전달데이터(전달인자)를 생성해준다.(@RequestBody일때 객체로 받으면 컨버터를 이용해서 변환시켜서 객체 만들어 넣어줘야하고, @ModelAttribute도 그렇고 @RequestParam이면 파라미터 찾아서 넣어줘야하고.. 와 같은 동작들을 Argument Resolver에서 한다)
어노테이션 기반 컨트롤러(@RequestMapping,@GetMapping등을 쓰는 컨트롤러)를 처리하는 RequestMappingHandlerAdapter는 바로 이 Argument Resolver를 호출해서 컨트롤러(핸들러)가 필요로 하는 다양한 파라미터의값(객체)를 생성한다. 그리고 이렇게 파라미터의 값이 모두 준비되면 핸들러 어댑터가 준비된 파라미터를 가지고 컨트롤러를 호출하면서 값을 넘겨준다.
ArgumentResolver
https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-arguments
가능한 파라미터목록은 공식문서에서 체크
ArgumentResolver의 풀네임은 HandlerMethodArgumentResolver인데 줄여서 ArgumentResolver로 부른다.
HandlerMethodArgumentResolver를 구현한 수많은 구현체가 존재한다.
ArgumentResolver 동작방식
위의 코드에서 보이듯이 supportsParameter()라는 메소드와 resolveArgument()라는 메소드가 있다.
모든 요청 파라미터들, @RequestHeader까지도 ArgumentResolver에서 생성해서 전달해주는것이다.
(RequestHeaderMethodArgumentResolver라는게 존재한다.)
ReturnValueHandler
컨트롤러의 반환값을 변환해준다. ( ModelAndView, 객체 , @ResponseBody 등등)
HandlerMethodReturnValueHandler 또한 엄청많은 구현체가 존재한다.
https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-return-types
다양한 응답값 목록 - 공식문서
Http 메시지 컨버터 위치
ArgumentResolver가 컨트롤러메소드의 파라미터를 생성해준다. ( 들어온 데이터를 변환시켜서 객체를 생성해주고 등등)
그러므로 ArgumentResolver가 Http메시지 컨버터를 사용하는것이다.
물론 반환할떄도 ReturnValueHandler가 응답데이터를 Http메시지에 입력하기위해 반환된값을 변환을 시켜야하는데 그떄 Http메시지 컨버터를 사용한다.
요청의 경우
@RequestBody를 처리하는 ArgumentResolver가 있고,
HttpEntity를 처리하는 ArgumentResolver가 있다.
각각의 ArgumentResolver들이 Http 메시지 컨버터를 사용해서 필요한 객체를 생성한다.
즉 메소드의 파리미터에 따른 ArgumentResolver가 실행되고 그 안에서 만들어야하는 객체에 따른 Http메시지 컨버터가 실행되어 객체를 생성한다.
(http메시지컨버터를 순서대로 canRead()해서 체크하는것을 바로 ArgumentResolver가 하는것이다)
(http메시지컨버터를 순서대로 canWrite()해서 체크하는것을 바로 ReturnValueHandler가 하는것이다)
보면 ArgumentResolver랑 ReturnValueHandler 둘다 상속받는다
-> 하나의 Processor라는것에서 처리하려고 이렇게 한것이다.
ArgumentResolver,ReturnValueHandler 를 상속한 구현체들은 해당 타입을 지원할수있는지 체크하는 메소드와
지원할 수 있다면 알맞은 http메시지컨버터를 가져와 객체를 만들어주는 메소드가 존재한다.
정리
요청이 들어오기전, 어플리케이션이 실행될때 :
RequestMappingHandlerMapping는 @Controller가 붙은 어노테이션컨트롤러안에
@RequestMapping이 붙은 메소드들을 HandlerMethod 객체로 변환시켜서 RequestMappingHandlerMapping에 저장한다.
요청:
http 요청이 들어오면, 매핑된 메소드의 정보를 가지고있는 HandlerMethod를 RequestMappingHandlerMapping가 찾아서 HandlerMethod를 반환해주고 DispatcherServlet은 해당 HandlerMethod안에서 컨트롤러(핸들러)의 정보를 받고,
해당 핸들러를 처리할수있는 RequestMappingHandlerAdapter를 가져온다.
그리고 RequestMappingHandlerAdapter가 메소드의 각각의 파라미터에 맞는 ArgumentResolver를 호출한다.
각각의 ArgumentResolver는 Http메시지컨버터를 순서대로 확인하면서 변환을 처리할 수 있는 Http메시지컨버터를 호출해서 요청된 타입으로 변환시켜 객체를 생성해준다.
ArgumentResolver로 부터 변환된 파라미터(전달인자)를 받은 RequestMappingHandlerAdapter는 해당 파라미터를 가지고 컨트롤러(핸들러)의 메소드를 실행한다.
응답:
반환값이 들어오면 반환값을 처리하는 해당 ReturnValueHandler가 동작한다.
반환된 값을 응답Http메시지바디에 넣을 수 있게끔 변환시켜줘야하므로 반환된 값을 변환시켜주는 Http메시지컨버터를 찾아 호출하여 변환시켜 처리한다 (처리 == Http응답메시지 바디에 넣어준다)
확장
Resolver라던지 Handler라던지 인터페이스화 되어있는것을 가지고 기능을 확장하고 싶다면
WebMvcConfigurer를 상속받아서 확장하면된다.
WebMvcConfigurer안에는 확장할 수 있는 수많은 메소드들이 존재한다.
WebMvcConfigurer 상속받아 기능 구현을 한후 스프링빈으로 등록해서 사용하면 된다.
(스프링빈으로 등록하면, 스프링이 이것에 대한 설정을 인식해서 처리해준다)
필요할때 검색해서 사용
여기까지가 Spring MVC의 전체구조를 본것이고
이제 활용하고, 확장하는일만 남았다.
'인프런 > 스프링 MVC 1편' 카테고리의 다른 글
17)상품 도메인 개발,부트스트랩,타임리프 적용 (0) | 2023.01.27 |
---|---|
16)스프링 MVC(웹페이지 만들기,웹 퍼블리셔,웹 프론트앤드) (0) | 2023.01.27 |
14)HTTP 응답 문자(v1~v3) JSON(v1~v2) ,@RestController (0) | 2023.01.26 |
13)HTTP 응답 (정적리소스,뷰 템플릿) , Thymeleaf 스프링부트 설정 (0) | 2023.01.26 |
12)HTTP요청 메시지 처리방법 [데이터타입 :JSON] (v1 ~ v4) (0) | 2023.01.26 |
댓글