[테코톡] @ResponseBody vs. ResponseEntity

jeeeny·2024년 4월 21일

모든 건 이 질문으로부터 시작되었다.

🐻브라운 : @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의 차이에 대한 고민이 시작됐다.
먼저 각각의 정의를 찾아봤다.

@ResponseBody

You can use the @ResponseBody annotation on a method to have the return serialized to the response body through an HttpMessageWriter. 출처 : @ResponseBody

HttpMessageWriter를 통해 직렬화된 리턴값을 Response Body에 넣어주기 위해 사용하는 어노테이션

여기서 직렬화(serialization)란 쉽게 말하면 자바 객체를 JSON 형태로 변환하는 것을 의미한다.

ResponseEntity

Extension of HttpEntity that adds a HttpStatus status code.
출처 : ResponseEntity

상태코드를 추가한 HttpEntity를 상속받은 클래스

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() : 메시지 컨버터를 통해 메시지를 읽고 쓰는 기능

스프링부트 기본 메시지 컨버터

  • ByteArrayHttpMessageConverter
  • StringHttpMessageConverter
  • MappingJackson2HttpMessageConverter

스프링부트는 다양한 메시지 컨버터를 제공하는데, 대상 클래스 타입과 미디어 타입을 체크해서 사용여부를 결정한다. 클래스 타입이 객체 또는 미디어타입이 application/json 이라면 MappingJackson2HttpMessageConverter 사용한다.

참고 : HTTP Message Conversion

컨버터는 어디서 사용되나

HandlerMethodReturnValueHandler(ReturnValueHandler) 인터페이스의 handleReturnValue()에 의해 컨트롤러의 반환 값을 변환한다.

ResponseEntity

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

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

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

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

ResponseBody

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

헷갈리니까 정리

@Response이냐, ResponseEntity이냐에따라 HandlerMethodReturnValueHandler 의 구현체가 다르게 주입되고 있을뿐 큰 차이는 없었다.

결론

동작방식을 비교해보고나니 별거는 없었다... ? 큰 차이가 없는 걸로 보아 결국 HTTP 옵션 유무가 큰 차이로 보인다. 간단하게 장, 단점을 정리해보면 다음과 같다.

@ResponseBody

장점 👍

  • 어노테이션만을 사용해 간단히 자바 객체를 직렬화할 수 있다.

단점 👎

  • HTTP 헤더, 상태 코드 옵션에 대한 유연성이 떨어진다.
    • 헤더를 지정해주는 방법은 없는 듯하다.
    • @ResponseStatus로 상태코드값을 지정해줄 수 있다. 하지만 ENUM으로 지정되어있기 때문에 커스텀한 지정은 불가능하다.

ResponseEntity

장점 👍

  • HTTP 옵션에 대해 유연하다. ResponseEntity 빌더를 통해서 여러 HTTP 옵션을 설정할 수 있다.

단점 👎

  • @ResponseBody 보다는 작성할 코드가 많다.

커스텀 상태코드를 사용하거나 헤더에 값을 추가할 일이 있을까 싶지만,, 어떤 변경사항이 생길지 모르기 때문에 변경에 유연한 ResponseEntity를 사용하는 것이 더 좋겠다는게 내 결론이다.

+ 토미와의 대화

내가 본 모든 프로젝트에서는 ResponseEntity를 사용했다. 이걸 보고 @ResponseBody를 사용하지 않는 이유가 있나? 하는 의문이 들어 토미에게 질문했다. 그랬더니 토미는 프로젝트에서 ResponseEntity를 사용하지 않은 적도 있다고 했다. 굳이 사용할 이유가 없어서였다. 결국 팀by팀인 것 같다.

참고로 읽어보면 좋을만한 글을 첨부한다.
https://yeonyeon.tistory.com/257

profile
나의 성장 기록

4개의 댓글

comment-user-thumbnail
2024년 4월 21일

동작방식 코드랑 그림으로 설명해주니 이해 쏙쏙이군요 👍

1개의 답글
comment-user-thumbnail
2024년 4월 21일

와 되게 깊이 파보았네요⛏
좋은 글 감사해용

1개의 답글