일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 31 |
- 데이터베이스파서
- rest api 검증
- HTTP프로토콜
- 프로세스 생성
- rest api
- fcmv1
- 옵티마이저
- 동기비동기블로킹논블로킹
- Wrapper class
- multiparfile데이터
- 불변객체
- fcm성능비교
- 공유기작동방식
- 스프링요청반응
- DispatcherServlet
- biblecash
- fcm데이터구조
- 옵티마저
- httpservlet기술
- 클라이언트요청반응
- 검증 실패 예외처리
- 디스패처서블릿
- 중첩클래스
- 동등성동일성
- java enum
- 왜불변객체인가
- equals
- multipart바인딩
- 래퍼클래스
- HttpServlet
- Today
- Total
개발은 아름다워
[ Spring ] HTTP API를 가능하게 해주는 메세지 컨버터 본문
뷰 템플릿으로 HTML을 생성해서 응답하는 것이 아니라, HTTP API처럼 JSON 데이터를 HTTP 메세지 바디에서 직접 읽거나 쓰는 경우 HTTP 메세지 컨버터를 사용하면 편리하다. 그렇다면 HTTP 메세지 컨버터는 어떻게 동작하는걸까?
HTTP 메세지 컨버터 동작
스프링 MVC는 다음의 경우 HTTP 메세지 컨버터를 적용한다.
- HTTP 요청 : @RequestBody, HttpEntity(RequestEntity)
- HTTP 응답 : @ResponseBody, HttpEntity(ResponseEntity)
@ResponseBody 사용 원리
1. viewResolver 대신 HttpMessageConverter가 동작한다.
2. 이 때 클라이언트의 HTTP Accept 헤더와 서버의 컨트롤러 반환 타입 정보 둘을 조합해서 HttpMessageConverter가 선택된다.
3. 여러 HttpMessageConverter가 기본으로 등록되어 있다. 예를 들어 기본 문자처리는 StringHttpMessageConverter가 동작하며 기본 객체처리를 위해서 MappingJackson2HttpMessageConverter가 동작한다. 즉, HTTP 메세지 바디에 담긴 데이터에 맞는 HttpMessageConverter가 동작한다.
HTTP 메세지 컨버터 인터페이스 코드를 봐보자
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 메세지 컨버터는 HTTP요청,응답 둘 다 사용된다.
- canRead(),canWrite() : 메세지 컨버터가 해당 클래스,미디어타입을 지원하는지 체크
- read(),write() : 메세지 컨버터를 통해서 메세지를 읽고 쓰는 기능
주요한 메세지 컨버터
스프링 부트는 다양한 메세지 컨버터를 제공하는데, 대상 클래스 타입과 미디어 타입 둘을 체크해서 사용여부를 결정한다. 만약 만족하지 않으면 다음 메세지 컨버터로 우선순위가 넘어간다. 다음은 몇가지 주요한 메세지 컨버터이다.
0 = ByteArrayHttpMessageConverter
1 = StringHttpMessageConverter
2 = MappingJackson2HttpMessageConverter
1) ByteArrayHttpMessageConverter : byte[] 데이터를 처리한다.
- 클래스 타입 : byte[] , 미디어타입 : / ,
- 요청 예) @RequestBody byte[] data
- 응답 예) @ResponseBody return byte[] 쓰기 미디어타입 application/octet-stream
2) StringHttpMessageConverter : String 문자로 데이터를 처리한다.
- 클래스 타입 : String , 미디어타입 : /
- 요청 예) @RequestBody String data
- 응답 예) @ResponseBody return "ok" 쓰기 미디어타입 text/plain
3) MappingJackson2HttpMessageConverter
- 클래스 타입 : 객체 또는 HashMap , 미디어타입 application/json 관련
- 요청 예) @RequestBody HelloData data
- 응답 예) @ResponseBody return helloData
HTTP 요청을 어떻게 컨트롤러에서 받을까?
이전에 작성한 코드를 보면
public class RequestBodyJsonController {
private ObjectMapper objectMapper = new ObjectMapper();
@PostMapping("/request-body-json-v1")
public void reqeustBodyJsonV1(HttpServletRequest request,HttpServletResponse response) throws IOException{
ServletInputStream inputStream = request.getInputStream();
String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
log.info("messageBody={}",messageBody);
HelloData data = objectMapper.readValue(messageBody, HelloData.class);
log.info("username={},age={}", data.getUsername(),data.getAge());
response.getWriter().write("ok");
}
@ResponseBody
@PostMapping("/request-body-json-v2")
public String requestBodyJsonV2(@RequestBody String messageBody) throws JsonMappingException, JsonProcessingException{
HelloData data = objectMapper.readValue(messageBody, HelloData.class);
log.info("username={},age={}",data.getUsername(),data.getAge());
return "ok";
}
@ResponseBody
@PostMapping("/request-body-json-v3")
public String requestBodyJsonV3(@RequestBody HelloData data){
log.info("username={},age={}",data.getUsername(),data.getAge());
return "ok";
}
@SuppressWarnings("null")
@ResponseBody
@PostMapping("/request-body-json-v4")
public String requestBodyJsonV4(HttpEntity<HelloData> httpEntity){
HelloData data = httpEntity.getBody();
log.info("username={},age={}",data.getUsername(),data.getAge());
return "ok";
}
@ResponseBody
@PostMapping("/request-body-json-v5")
public HelloData requestBodyJsonV5(@RequestBody HelloData data){
log.info("username={},age={}",data.getUsername(),data.getAge());
return data;
}
}
HTTP 요청 데이터를 읽는 과정을 보면
- HTTP 요청이 오고, 컨트롤러에서 @RequestBody 또는 HttpEntity 파라미터를 사용한다.
- 이후 메세지 컨버터가 메세지를 읽을 수 있는지 확인하기 위해 canRead()를 호출한다.
- canRead() 조건을 만족하면 read()를 호출해서 객체를 생성하고 반환한다.
HTTP 응답 데이터 생성과정
public class ResponseBodyController {
// 고대의 서블릿때처럼 HttpServletResponse 객체를 통해 HTTP 메세지 바디에 직접 OK 응답 메세지 전달
@GetMapping("/response-body-string-v1")
public void responseBodyV1(HttpServletResponse response) throws IOException{
response.getWriter().write("It's ok~!");
}
// ResponseEntity는 HttpEntity를 상속 받았다.
// HttpEntity는 HTTP 메세지의 헤더,바디 정보를 가지고 있다.
// ResponseEntity는 HTTP 응답 코드를 설정할 수 있다.
@ResponseBody
@GetMapping("/response-body-string-v2")
public ResponseEntity<String> responseBodyV2(){
return new ResponseEntity<>("ok",HttpStatus.OK);
}
// @ResponseBody를 사용하면 view를 사용하지 않고 HTTP 메세지 컨버터를 통해
// HTTP 메세지바디에 직접 입력할 수 있다.
@ResponseBody
@GetMapping("/response-body-string-v3")
public String responseBodyV3() {
return "ok";
}
// ResponseEntity 를 반환한다.
// HTTP 메세지 컨버터를 통해서 JSON 형식으로 변환되서어 반환된다.
@GetMapping("/response-body-json-v1")
public ResponseEntity<HelloData> responseBodyJsonV1(){
HelloData helloData = new HelloData();
helloData.setUsername("userA");
helloData.setAge(22);
return new ResponseEntity<>(helloData,HttpStatus.OK);
}
// ResponseEntity는 HTTP 응답코드를 설정할 수 있는데
// ResponseBody를 사용하면 응답 코드를 설정하기 까다롭다
// 따라서 @ResponseStatus(HttpStatus.OK)로 응답코드를 설정한 것이다.
@ResponseStatus(HttpStatus.OK)
@ResponseBody
@GetMapping("/response-body-json-v2")
public HelloData responseBodyJsonV2(){
HelloData helloData = new HelloData();
helloData.setUsername("UserB");
helloData.setAge(30);
return helloData;
}
}
- 컨트롤러에서 @ResponseBody, HttpEntity로 값이 반환된다.
- 메세지 컨버터가 메세지를 쓸 수 있는지 확인하기 위해 canWrite()를 호출한다.
- canWrite() 조건을 만족하면 write()를 호출해서 HTTP 응답 메세지 바디에 데이터를 생성한다.
그렇다면 메세지 컨버터는 어느 시점에 동작하는 것일까?
어노테이션 기반 컨트롤러를 처리하는 RequestMappingHandlerAdapter는 바로 ArgumentResolver를 호출해서 컨트롤러가 필요로 하는 다양한 파라미터의 값(객체)를 생성한다. 파라미터의 값이 모두 준비되면 컨트롤러를 호출하면서 값을 넘겨준다. 이 때ArgumentResolver는 메세지 컨버터를 사용하게 되는 것이다. 응답 값은 ReturnValueHandler가 동작해서 응답 값을 변환하고 처리한다.
HTTP 메세지 컨버터를 사용하는 @RequestBody도 컨트롤러가 필요로 하는 파라미터의 값에 사용된다. @ResponseBody의 경우도 컨트롤러의 반환 값을 이용한다.
-요청의 경우
@RequestBody를 처리하는 ArgumentResolver가 있고 HttpEntity를 처리하는 ArgumentResolver가 있다. 이 ArgumentResolver들이 HTTP 메세지 컨버터를 사용해서 필요한 객체를 생성하는 것이다.
-응답의 경우
@ResponseBody와 HttpEntity를 처리하는 ReturnValueHandler가 있다. 그리고 여기에서 HTTP 메세지 컨버터를 호출해서 응답 결과를 만든다.
정리
어떻게 HTTP 요청 데이터가 바인딩 되는 것이고 응답 데이터가 어떻게 생성되는지 궁금했는데, 이번에 메세지 컨버터를 공부하면서 그 궁금증을 해결 할 수 있었다. 또한 스프링 프레임워크를 다양한 기능들을 공부할 수 록 객체가 협력해서 시스템 요구사항을 처리하는 매커니즘을 조금씩 더 이해할 수 있게 되었다.
'스프링' 카테고리의 다른 글
[ Spring ] 설정과 사용의 분리의 효과는 어마어마해! - 커넥션 풀과 DataSource (1) | 2024.10.14 |
---|---|
[ Spring ] 데이터베이스 접근 기술 JDBC - 근본은 변하지 않는다 (0) | 2024.10.14 |
[ Spring ] 스프링에서 응답 데이터 만드는 방식 (0) | 2024.10.14 |
[ Spring ] 고대의 서블릿을 찾아서(8) - 어댑터패턴과 트레이드 오프 (0) | 2024.10.14 |
[ Spring ] 고대의 서블릿을 찾아서(7) - ModelView를 없애고 더욱 단순하게 (0) | 2024.10.14 |