스프링 어플리케이션을 개발하다보면 @ResponseBody나 ResponseEntity와 같이 응답과 관련한 스프링 프레임워크에 정의된 요소들을 사용하게 된다.
🔥 역할
@ResponseBody와 ResponseEntity의 공통적인 역할은 응답 객체를 JSON으로 직렬화한다는 것이다. 코드를 통해 알아보자.
📝 직렬화란?
객체를 직렬화한다는 것은 객체를 송수신이 가능한 형태로 변형하는 것이다. 네트워크 상에서는 Byte 단위로 데이터를 전달하는데, 서버와 클라이언트의 데이터 통신을 위해 객체를 직렬화하는 과정이 필요하다.
@ResponseBody
@GetMapping("/reservations")
public List<Reservation> getReservations() {
return adminRepository.getReservations()
.values()
.stream()
.toList();
}
@ResponseBody 어노테이션을 상단에 붙이고 간단하게 예약들을 조회하는 메서드를 작성하였다.
위에서 작성한 API에 의해 조회하였을 때 응답 JSON 형태이다.
@GetMapping("/reservations")
public ResponseEntity<List<Reservation>> getReservations() {
List<Reservation> reservations = adminRepository.getReservations()
.values()
.stream()
.toList();
return ResponseEntity.ok(reservations);
}
이번에는 @ResponseBody를 빼고 ResponseEntity 객체로 응답 객체를 감싸서 반환하였다.
이번에도 JSON 응답이 정상적으로 반환된다.
👀 차이점
이렇게 두 코드의 결과가 똑같은데, 어떤 것을 사용해야할까?
Spring 공식문서에서는 ResponseEntity를 아래와 같이 설명하고 있다.
ResponseEntity is like @ResponseBody but with status and headers.
ResponseEntity는 응답 객체 뿐만 아니라 헤더와 상태 코드를 함께 전달할 수 있다.
public class ResponseEntity<T> extends HttpEntity<T> {
private final HttpStatusCode status;
...
}
ResponseEntity는 HttpEntity를 상속받는다.
ResponseEntity는 상태 코드(status) 를 가진다.
public class HttpEntity<T> {
/**
* The empty {@code HttpEntity}, with no body or headers.
*/
public static final HttpEntity<?> EMPTY = new HttpEntity<>();
private final HttpHeaders headers;
@Nullable
private final T body;
...
}
그리고 HttpEntity는 응답 객체 (body)와 헤더 정보 (headers)를 필드로 가지고 있다.
이렇게 ResponseEntity는 header, body, status를 가질 수 있도록 구현되어있기 때문에 클라이언트에게 좀 더 세밀하게 결괏값을 반환할 수 있다.
@GetMapping("/reservations")
public ResponseEntity<List<Reservation>> getReservations() {
List<Reservation> reservations = adminRepository.getReservations()
.values()
.stream()
.toList();
MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
headers.add("Content-Type", "application/json");
return new ResponseEntity(reservations, headers, HttpStatusCode.valueOf(200));
}
반면 @ResponseBody는 페이지에 데이터만 전달할 수 있다.
그래서 HTTP 옵션을 지정해서 데이터를 전달해야하는 경우 ResponseEntity를, 그렇지 않다면 간단하게 @ResponseBody를 사용하는 것을 추천한다.
🛠 동작 원리
@ReseponseBody 어노테이션이 붙은 메서드는 어떻게 JSON 형태로 응답 객체를 직렬화해주는 걸까?
@ResponseBody에 대한 Spring 공식 문서를 살펴보면
You can use the @ResponseBody annotation on a method to have the return serialized to the response body through an HttpMessageConverter
라고 설명한다.
HttpMessageConverter는 하나의 함수형 인터페이스이고, HTTP 응답 객체를 직렬화하는 의도로 만들어졌다. 이 인터페이스를 구현한 MappingJackson2HttpMessgaeConverter에서 ObectMapper를 사용해 직렬화한다.
public MappingJackson2HttpMessageConverter() {
this(Jackson2ObjectMapperBuilder.json().build());
}
/**
* Construct a new {@link MappingJackson2HttpMessageConverter} with a custom {@link ObjectMapper}.
* You can use {@link Jackson2ObjectMapperBuilder} to build it easily.
* @see Jackson2ObjectMapperBuilder#json()
*/
public MappingJackson2HttpMessageConverter(ObjectMapper objectMapper) {
super(objectMapper, MediaType.APPLICATION_JSON, new MediaType("application", "*+json"));
}
내부적으로는 그렇고, 어떻게 @ResponseBody라는 어노테이션이 직렬화에 영향을 미칠 수 있는 걸까?
스프링 컨테이너에서 요청에 의해 매핑된 컨트롤러 메서드에 @ResponseBody가 붙어있으면 ReqeustMappingHandlerAdapter라는 친구가 HttpMessageConverter를 동작시킨다.
만약 @ResponseBody가 붙어있지 않다면 HttpMessageConverter가 아닌 ViewResolver를 실행하여 템플릿 엔진으로 처리하도록 한다.
그리고 ResponseEntity의 경우에도 동일하게 HttpMessageConverter를 통해 응답 형태를 구현한다.
참고
ResponseEntity :: Spring Framework
[Spring] @ResponseBody VS ResponseEntity<T> : 무엇을 사용할까? — 성장하는 성하 Blog (tistory.com)
'우아한테크코스 > 레벨2' 카테고리의 다른 글
[OOP] POJO (Plain Old Java Objects) 란? (feat. 스프링의 등장 배경) (0) | 2024.04.27 |
---|---|
[Spring] 직렬화 (Serialization)와 역직렬화 (Deserialization) 의 정의 및 스프링에서 사용하는 방법 (4) | 2024.04.27 |
[OOP] DTO(Data Transfer Object) 란 무엇인가? 어떻게 사용하는가? (0) | 2024.04.26 |
[회고] 우아한테크코스 6기 백엔드 레벨2 1주차 회고 (6) | 2024.04.21 |
[Spring] 문자열 경로를 반환하는 API를 호출하면 웹 페이지가 렌더링되는 이유는 뭘까? (feat. ViewResolver) (7) | 2024.04.18 |