HTTP 요청 메시지 - JSON
서블릿을 사용할 때는 ObjectMapper를 사용하여 Json을 객체로 변환하였다.
스프링을 사용할 때도 같은 방식이 가능하다.
@RequestBody 객체 파라미터
HttpEntity , @RequestBody 를 사용하면 HTTP 메시지 컨버터가 HTTP 메시지 바디의 내용을 우리가 원하는 문자나 객체 등으로 변환해준다.
HTTP 메시지 컨버터는 문자 뿐만 아니라 JSON도 객체로 변환할 수 있다.
@RequestBody는 생략 불가능
스프링은 @ModelAttribute , @RequestParam 과 같은 해당 애노테이션을 생략시 다음과 같은 규칙을 적용한다.
String , int , Integer 같은 단순 타입 = @RequestParam
나머지 = @ModelAttribute (argument resolver 로 지정해둔 타입 외)
즉, 직접 정의한 객체(API용)에 @RequestBody 를 생략하면 @ModelAttribute 가 적용된다.
HelloData data -> @ModelAttribute HelloData data
따라서 생략하면 HTTP 메시지 바디가 아니라 요청 파라미터를 처리하게 된다.
주의! HTTP 요청 시에 content-type이 application/json 인지 꼭! 확인해야 한다. 그래야 JSON을 처리할 수 있는 HTTP 메시지 컨버터가 실행된다.
HttpEntity<T>
httpEntity.getBody() 하면 선언한 T 타입으로 객체를 변환하여 받아올 수 있다.
@ResponseBody
객체를 반환하면 HTTP 메시지 컨버터가 Json으로 변환하여 메시지 바디에 직접 넣어준다.
물론 이 경우에도 HttpEntity 를 사용해도 된다.
정리
@RequestBody 요청: JSON 요청 -> HTTP 메시지 컨버터 -> 객체
@ResponseBody 응답: 객체 -> HTTP 메시지 컨버터 -> JSON 응답
HTTP 응답 - 정적 리소스, 뷰 템플릿
요청에서 일부 다룬 내용 있음
서버에서 응답 데이터를 만드는 방법
- 정적 리소스: 웹 브라우저에 정적인 HTML, css, js 등 파일을 그대로 제공
- 뷰 템플릿: 웹 브라우저에 동적 HTML을 제공할 때 사용
- HTTP 메시지: HTTP API를 제공할 때, HTTP 메시지 바디에 JSON 같은 형식으로 데이터를 직접 전달
정적 리소스
src/main/resources: 리소스를 보관, 클래스패스의 시작 경로
스프링 부트가 제공하는 정적 리소스 경로
src/main/resources 이하의 /static , /public , /resources , /META-INF/resources
ex. src/main/resources/static/basic/hello-form.html
=> http://localhost:8080/basic/hello-form.html 으로 접속
뷰 템플릿
뷰 템플릿을 거쳐서 HTML이 생성되고, 뷰가 응답을 만들어서 전달한다.
일반적으로 HTML을 동적으로 생성하는 용도로 사용하지만, 다른 것들도 가능하다.
스프링 부트가 제공하는 뷰 템플릿 경로
src/main/resources/templates
뷰 템플릿 호출
<String을 반환하는 경우> - View or HTTP 메시지
@ResponseBody 의 유무
- @ResponseBody 가 없으면 반환 문자열을 뷰의 논리 이름으로 뷰 리졸버가 실행되어서 뷰를 찾고, 렌더링 한다.
- @ResponseBody 가 있으면 뷰 리졸버를 실행하지 않고, HTTP 메시지 바디에 직접 문자가 입력된다.
<Void를 반환하는 경우>
@Controller 를 사용하고, HttpServletResponse , OutputStream(Writer) 같은 HTTP 메시지 바디를 처리하는 파라미터가 없으면 요청 URL을 참고해서 논리 뷰 이름으로 사용 요청
URL: /response/hello
실행: templates/response/hello.html
참고로 이 방식은 명시성이 너무 떨어지고 이렇게 딱 맞는 경우도 많이 없어서, 권장하지 않는다.
Thymeleaf 스프링 부트 설정
라이브러리 추가
in build.gradle
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
스프링 부트가 자동으로 ThymeleafViewResolver 와 필요한 스프링 빈들을 등록한다.
그리고 다음 설정도 사용한다. 이 설정은 기본 값 이기 때문에 변경이 필요할 때만 설정하면 된다.
스프링 부트 설정
in application.properties
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
참고: 스프링 부트의 타임리프 관련 추가 설정 (페이지 안에서 thymeleaf 검색)
HTTP 응답 - HTTP API, 메시지 바디에 직접 입력
HTTP API를 제공하는 경우에는 HTML이 아니라 데이터를 전달해야 하므로, HTTP 메시지 바디에 JSON 같은 형식으로 데이터를 실어 보낸다.
참고: HTML이나 뷰 템플릿을 사용해도 HTTP 응답 메시지 바디에 HTML 데이터가 담겨서 전달된다. 여기서 설명하는 내용은 정적 리소스나 뷰 템플릿을 거치지 않고, 직접 HTTP 응답 메시지를 전달하는 경우를 말한다.
1. HttpServletResponse 객체 파라미터
HTTP 메시지 바디에 직접 전달
response.getWriter().write("ok")
2. ResponseEntity 객체 반환
HttpEntity: HTTP 메시지의 헤더, 바디 정보 포함
HttpEntity를 상속받은 ResponseEntity 는 여기에 더해서 HTTP 응답 코드를 설정할 수 있다.
return new ResponseEntity<>(String/Object, HttpStatus.OK)
3. @ResponseBody + @ResponseStatus
view를 사용하지 않고 HTTP 메시지 컨버터를 통해서 반환한 문자나 객체를 HTTP 메시지 바디에 입력
ResponseEntity와 같은 방식
@ResponseStatus: HTTP 응답 코드 설정. 애노테이션이기 때문에 응답 코드를 동적으로 변경할 수는 없다. 프로그램 조건에 따라서 동적으로 변경하려면 ResponseEntity 를 사용하면 된다.
@RestController
Rest API(HTTP API)를 만들 때 사용하는 컨트롤러 (@Controller+@ResponseBody)
@Controller 대신에 @RestController 애노테이션을 사용하면, 해당 컨트롤러에 모두 @ResponseBody 가 적용되는 효과가 있다. 따라서 뷰 템플릿을 사용하는 것이 아니라, HTTP 메시지 바디에 직접 데이터를 입력한다.
참고: @ResponseBody 는 클래스 레벨에 두면 전체 메서드에 적용되는데, @RestController 에노테이션 안에 @ResponseBody 가 포함
HTTP 메시지 컨버터

@ResponseBody 사용 시
- HTTP의 BODY에 문자 내용을 직접 반환 (json도 문자)
- viewResolver 대신에 HttpMessageConverter 가 동작
- 기본 문자처리: StringHttpMessageConverter
- 기본 객체처리: MappingJackson2HttpMessageConverter
- byte 처리 등등 기타 여러 HttpMessageConverter가 기본으로 등록되어 있음
*스프링 MVC가 HTTP 메시지 컨버터를 적용하는 경우*
HTTP 요청: @RequestBody , HttpEntity(RequestEntity)
HTTP 응답: @ResponseBody , HttpEntity(ResponseEntity)
HTTP 메시지 컨버터 인터페이스
> org.springframework.http.converter.HttpMessageConverter
canRead() , canWrite() : 메시지 컨버터가 해당 클래스, 미디어타입을 지원하는지 체크
read() , write() : 메시지 컨버터를 통해서 메시지를 읽고 쓰는 기능
스프링 부트 기본 메시지 컨버터
0 = ByteArrayHttpMessageConverter (-> byte)
1 = StringHttpMessageConverter (->String)
2 = MappingJackson2HttpMessageConverter (String<->Json)
(일부 생략)
스프링 부트는 다양한 메시지 컨버터를 제공하는데, 대상 클래스 타입과 미디어 타입(요청/content-type) 둘을 체크해서 사용여부를 결정한다. 만약 만족하지 않으면 다음 메시지 컨버터로 우선순위가 넘어간다.
ByteArrayHttpMessageConverter : byte[] 데이터를 처리한다.
- 클래스 타입: byte[] , 미디어타입: */* (모든 타입)
- 요청 예) @RequestBody byte[] data
- 응답 예) @ResponseBody return byte[] , 쓰기 미디어타입 application/octet-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 요청 데이터 읽기
HTTP 요청이 오고, 컨트롤러에서 @RequestBody , HttpEntity 파라미터를 사용한다.
메시지 컨버터가 메시지를 읽을 수 있는지 확인하기 위해 canRead() 를 호출한다.
-대상 클래스 타입을 지원하는가. 예) @RequestBody 의 대상 클래스 ( byte[] , String , HelloData )
-HTTP 요청의 Content-Type 미디어 타입을 지원하는가. 예) text/plain , application/json , */*
canRead() 조건을 만족하면 read() 를 호출해서 객체 생성하고, 반환한다.
HTTP 응답 데이터 생성
컨트롤러에서 @ResponseBody , HttpEntity 로 값이 반환된다.
메시지 컨버터가 메시지를 쓸 수 있는지 확인하기 위해 canWrite() 를 호출한다.
-대상 클래스 타입을 지원하는가. 예) return의 대상 클래스 ( byte[] , String , HelloData )
-HTTP 요청의 Accept 미디어 타입을 지원하는가.(더 정확히는 @RequestMapping 의 produces )
예) text/plain , application/json , */*
canWrite() 조건을 만족하면 write() 를 호출해서 HTTP 응답 메시지 바디에 데이터를 생성한다.
요청 매핑 핸들러 어뎁터 구조
HTTP 메시지 컨버터는 스프링 MVC 어디쯤에서 사용되는 것일까?
모든 비밀은 애노테이션 기반의 컨트롤러을 호출하는, 그러니까 @RequestMapping 을 처리하는 핸들러 어댑터 RequestMappingHandlerAdapter (요청 매핑 헨들러 어뎁터)에 있다.
RequestMappingHandlerAdapter 동작 방식

ArgumentResolver
애노테이션 기반 컨트롤러가 실행되기 위해서는 모든 파라미터가 세팅되어야 한다.
애노테이션 기반 컨트롤러를 처리하는 RequestMappingHandlerAdapter 는 바로 이 ArgumentResolver 를 호출해서 컨트롤러(핸들러)가 필요로 하는 다양한 파라미터의 값(객체)을 생성한다.
그리고 이렇게 파리미터의 값이 모두 준비되면 컨트롤러를 호출하면서 값을 넘겨준다.
스프링은 30개가 넘는 ArgumentResolver 를 기본으로 제공한다.
참고: 가능한 파라미터 목록
https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-arguments
public interface HandlerMethodArgumentResolver { /* 줄여서 ArgumentResolver */
boolean supportsParameter(MethodParameter parameter);
@Nullable
Object resolveArgument(MethodParameter parameter,
@Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
@Nullable WebDataBinderFactory binderFactory) throws Exception;
}
ArgumentResolver 의 supportsParameter() 를 호출해서 해당 파라미터를 지원하는지 체크하고, 지원하면 resolveArgument() 를 호출해서 실제 객체를 생성한다.
그리고 이렇게 생성된 객체가 컨트롤러 호출시 넘어가는 것이다.
원한다면 이 인터페이스를 직접 확장해서 원하는 ArgumentResolver 를 만들 수 있다. -> 로그인 처리 예제
ReturnValueHandler *HandlerMethodReturnValueHandler*
응답 값을 변환하고 처리한다.
컨트롤러에서 String으로 뷰 이름을 반환해도, 동작하는 이유가 바로 ReturnValueHandler 덕분이다.
스프링은 10여개가 넘는 ReturnValueHandler 를 지원한다.
예) ModelAndView , @ResponseBody , HttpEntity , String
참고: 가능한 응답 값 목록
https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-return-type
HTTP 메시지 컨버터

요청의 경우
@RequestBody 와 HttpEntity 를 처리하는 ArgumentResolver 들이 HTTP 메시지 컨버터를 사용해서 필요한 객체를 생성
응답의 경우
@ResponseBody 와 HttpEntity 를 처리하는 ReturnValueHandler 들이 HTTP 메시지 컨버터를 호출해서 응답 결과를 생성
스프링 MVC는 @RequestBody @ResponseBody 가 있으면 RequestResponseBodyMethodProcessor (ArgumentResolver),
HttpEntity 가 있으면 HttpEntityMethodProcessor (ArgumentResolver) 를 사용한다.
확장
스프링은 다음을 모두 인터페이스로 제공한다. 따라서 필요하면 언제든지 기능을 확장할 수 있다.
-HandlerMethodArgumentResolver
-HandlerMethodReturnValueHandler
-HttpMessageConverter
스프링이 필요한 대부분의 기능을 제공하기 때문에 실제 기능을 확장할 일이 많지는 않다.
기능 확장은 WebMvcConfigurer 를 상속 받아서 스프링 빈으로 등록하면 된다. 실제 자주 사용하지는 않으니 실제 기능 확장이 필요할 때 WebMvcConfigurer 를 검색해보자.