모든 건 이 질문으로부터 시작되었다.
🐻브라운 : @ResponseBody를 안 붙이면 어떻게 되나요?
@Controller
public class ReservationController {
private final ReservationDao reservationDao;
public ReservationController(ReservationDao reservationDao) {
this.reservationDao = reservationDao;
}
@GetMapping("/reservations")
@ResponseBody
public ResponseEntity<List<ReservationResponse>> getReservations() {
List<ReservationResponse> reservationResponses = reservationDao.findAll().stream()
.map(ReservationResponse::fromReservation)
.toList();
return ResponseEntity.ok(reservationResponses);
}
}
문제없이 잘 동작했다.
🐻브라운 : 그러면 ResponseEntity를 지우면요?
@Controller
public class ReservationController {
private final ReservationDao reservationDao;
public ReservationController(ReservationDao reservationDao) {
this.reservationDao = reservationDao;
}
@GetMapping("/reservations")
@ResponseBody
public List<ReservationResponse> getReservations() {
return reservationDao.findAll().stream()
.map(ReservationResponse::fromReservation)
.toList();
}
}
항상 ResponseEntity를 사용했기 때문에 사용해보지 않는 건 생각해본 적이 없었다.
그런데 아무 문제없이 잘 돌아갔다.
여기서부터 ResponseEntity와 ResponseBody의 차이에 대한 고민이 시작됐다.
먼저 각각의 정의를 찾아봤다.
You can use the
@ResponseBodyannotation on a method to have the return serialized to the response body through an HttpMessageWriter. 출처 : @ResponseBody
HttpMessageWriter를 통해 직렬화된 리턴값을 Response Body에 넣어주기 위해 사용하는 어노테이션
여기서 직렬화(serialization)란 쉽게 말하면 자바 객체를 JSON 형태로 변환하는 것을 의미한다.
Extension of HttpEntity that adds a HttpStatus status code.
출처 : ResponseEntity
상태코드를 추가한 HttpEntity를 상속받은 클래스
Represents an HTTP request or response entity, consisting of headers and body.
출처 : HttpEntity
headers와 body로 이루어진 HTTP 요청 클래스
둘 다 자바 객체를 JSON으로 변환한 후 HTTP 응답을 보내기 위해 사용한다.
ResponseEntity is like @ResponseBody but with status and headers. 출처 : ResponseEntity
공식 문서에는 단순히 ResponseEntity가 ResponseBody에 상태코드와 헤더를 추가한다는 내용만 나와있었다. 어떠한 추가적인 장점도 나와있지 않았다. 과연 그 이유일 뿐일까라는 고민으로 동작방식의 차이를 알아봤다.
HTTP 응답에 대해 @ResponseBody, HttpEntity 모두 HttpMessageConverters를 사용한다.

HttpMessageConverters 인터페이스는 다음의 메서드들이 정의되어 있다.
canRead(), canWrite() : 메시지 컨버터가 해당 클래스, 미디어타입을 지원하는지 체크read(), write() : 메시지 컨버터를 통해 메시지를 읽고 쓰는 기능스프링부트 기본 메시지 컨버터
스프링부트는 다양한 메시지 컨버터를 제공하는데, 대상 클래스 타입과 미디어 타입을 체크해서 사용여부를 결정한다. 클래스 타입이 객체 또는 미디어타입이 application/json 이라면 MappingJackson2HttpMessageConverter 사용한다.
HandlerMethodReturnValueHandler(ReturnValueHandler) 인터페이스의 handleReturnValue()에 의해 컨트롤러의 반환 값을 변환한다.

HandlerMethodReturnValueHandler 의 구현체인 HttpEntityMethodProcessor 를 사용한다. handleReturnValue()가 재정의되어 있다.

메서드를 쭉 아래로 내려가다보면 writeWithMessageConverters()가 있다.

writeWithMessageConverters() 메서드를 살펴보면 위에서 살펴본 컨버터의 canWrite() / write()가 쓰이고 있음을 알 수 있다.

디버깅을 찍어보니 for문을 돌며 해당되는 메시지 컨버터를 찾고 있었고 MappingJackson2HttpMessageConverter 를 사용하고 있음을 알 수 있다.

RequestResponseBodyMethodProcessor 를 사용할 뿐 동작방식은 완전히 똑같았다 ‼️

@Response이냐, ResponseEntity이냐에따라 HandlerMethodReturnValueHandler 의 구현체가 다르게 주입되고 있을뿐 큰 차이는 없었다.
동작방식을 비교해보고나니 별거는 없었다... ? 큰 차이가 없는 걸로 보아 결국 HTTP 옵션 유무가 큰 차이로 보인다. 간단하게 장, 단점을 정리해보면 다음과 같다.
장점 👍
단점 👎
@ResponseStatus로 상태코드값을 지정해줄 수 있다. 하지만 ENUM으로 지정되어있기 때문에 커스텀한 지정은 불가능하다.장점 👍
단점 👎
@ResponseBody 보다는 작성할 코드가 많다.커스텀 상태코드를 사용하거나 헤더에 값을 추가할 일이 있을까 싶지만,, 어떤 변경사항이 생길지 모르기 때문에 변경에 유연한 ResponseEntity를 사용하는 것이 더 좋겠다는게 내 결론이다.
내가 본 모든 프로젝트에서는 ResponseEntity를 사용했다. 이걸 보고 @ResponseBody를 사용하지 않는 이유가 있나? 하는 의문이 들어 토미에게 질문했다. 그랬더니 토미는 프로젝트에서 ResponseEntity를 사용하지 않은 적도 있다고 했다. 굳이 사용할 이유가 없어서였다. 결국 팀by팀인 것 같다.
참고로 읽어보면 좋을만한 글을 첨부한다.
https://yeonyeon.tistory.com/257
동작방식 코드랑 그림으로 설명해주니 이해 쏙쏙이군요 👍