221007 TIL @ResponseBody, @RestController

어제 Dto클래스의 getter 메서드의 이름에 get을 뺐을때 발생했던 문제의 원인을 파악하기 위해 오늘 공부하고 정리한 내용이다.

 

우선 문제상황은 getter 메서드의 이름에 get을 뺐을때 그에 해당하는 값을 responseBody에 전달되지 않는 상황이다.

밑에 사진에서 원래는 id값도 전달이 되야하는데 Dto클래스의 getter인 getId 메서드의 이름을 get을 빼고 그냥 id로 바꾸고 실행한 결과값이다.

@ResponseBody

그러면 responseBody에 대해서 먼저 알아봐야 할 필요가 있었다.

서버에서 클라이언트로 응답을 보낼 때는 데이터를 responseBody에 담아서 보내야 한다.

 

클라이언트에게 응답을 보내는 컨트롤러에서 @ResponseBody라는 어노테이션을 사용하면 자바의 객체를 HTTP 응답 본문의 객체로 변환하여 클라이언트로 전송할 수 있다는 것을 알 수 있다.

 

 

하지만 현재 나의 컨트롤러에서는 @ResponseBody라는 어노테이션을 사용하고 있는 것을 볼 수가 없지만, 정상적으로 DTO객체를 HTTP Body에 데이터를 담아서 클라이언트에게 응답하고 있다.

 

@RestController

그 이유는 @RestController라는 어노테이션을 사용하기 때문이다.

 

@RestController는 @Controller에 @ResponseBody가 추가된 것이기 때문에 따로 @ResponseBody 어노테이션을 사용하지 않아도 된다.

@RestController의 주용도는 Json 형태로 객체 데이터를 반환하는 것이다.

 

@Controller가 주로 View를 반환하기 위해 사용하는 것에 비해 @RestController의 경우 데이터 그 자체를 반환하기 위해 사용한다

@Controller는 리턴 값을 생성할 view의 이름으로 사용하지만 @RestController의 리턴 값은 Response Body에 담아 전송을 한다. 그 이유는 RestController에 @ResponseBody가 있기 때문이다.

 

@Controller도 리턴 값을 응답 본문으로 기록할 때는 @ResponseBody 어노테이션을 사용하면 된다.

 

@ResponseBody는 리턴되는 데이터 타입에 따라 자바 객체를 HTTP 응답 본문의 객체로 MessageConverter에 의해서 변환한다

 

RestController의 작동 원리

https://mangkyu.tistory.com/49

client가 요청(프론트엔드에서 axios.get으로 요청, 예를들어 /orders라는 페이지를 요청한다고 가정)을 한다.

DispatcherServlet이 “/orders” 와 매칭이 되는 컨트롤러를 찾는다.

HandlerMapping을 통해서 받은 요청(/orders)과 매칭이 되는 컨트롤러에게 요청을 처리해달라고 한다.

컨트롤러는 ”/orders”로 GetMapping이 된 것을 찾아 요청을 처리한 후에 객체를 반환한다. (여기서 @RestController의 @ResponseBody를 이용하기 때문에 view로 반환되는 게 아니라 객체로 반환이 된다)

반환되는 객체는 MessageConverter에 의해 Json으로 Serialize 되어 클라이언트에게 반환된다.

  • Serialize(직렬화)는 자바 객체를 JSON 형태로 변환하는 것을 말한다.
    • 직렬화를 해주는 이유
      • 자바로 만든 데이터를 외부에 전송해서 사용하기 위해서는 객체 보다 단순한 형태로 변환이 돼야 하기 때문에 직렬화를 통해 바이트 형태로 변환한다.

MessageConverter

위와 같이 파라미터 부분에 @RequestBody 어노테이션을 입력한 경우 파라미터의 타입에(OrderDto) 맞는 MessageConverter를 선택한 뒤 HTTP 요청 본문을 전부 메시지로 변환하여 파라미터에 바인딩한다.

 

하지만 위와 같이 요청이 GET일 경우 HTTP 요청이 없기 때문에 @RequestBody를 사용하지 못한다.

위 GetMapping은 컨트롤러 윗부분에 위 사진과 같이 컨트롤러 상단에 @RestController를 입력해주었기 때문에 GetMapping 한 부분의 리턴 타입(여기서는 TransactionDto)에 맞는 MessageConverter를 선택한 뒤에 리턴 값을 통째로 변환해서 응답한다.

 

MessageConverter는 HTTP 요청 메시지 본문과 HTTP 응답 메세지 본문을 통째로 하나의 메시지로 보고 이를 처리한다.

Spring에서 이러한 작업을 하는 데 사용되는 어노테이션이 바로 위와 같이 @RequestBody와 @ResponseBody이다.

@RequestBody로 JSON 데이터가 넘어오면 이 JSON을 Java 객체로 변환은 MessageConverter인  MappingJackson2 HttpMessageConverter에서 해준다.

 

스프링에는 기본 MessageConverter가 몇 가지가 있는데 그중에 json형태로 바꿔주는 MappingJackson2HttpMessageConverter가 있다.

 

MappingJackson2HttpMessageConverter

Jackson의 ObjectMapper를 이용해서 자바 객체를 JSON으로 변경하거나 JSON을 자바 객체로 자동 변환해주는 메시지 컨버터다.

지원하는 미디어 타입은 application/json타입이다.

 

이때 ObjectMapper가 getter메서드를 사용한다. 그래서 getter가 존재하지 않으면 에러가 발생한다.

 

정리를 해보자

 

위 코드에서 리턴 값이 TransactionDto인데 컨트롤러 상단에 @RestController를 입력해주었기 때문에 리턴 타입(TransactionDto)에 맞는 MessageConverter를 선택해서 TransactionDto객체를 JSON형태로 변형한 뒤 응답한다.

여기서 MessageConverter는 기본 컨버터인 MappingJackson2HttpMessageConverter를 사용한다.

Jackson이 java 객체를 JSON으로 변환시킬 때 json필드와 java 필드(여기서는 변환하려는 TransactionDto 필드)를 일치시키는 방법은 java 객체 필드의 getter와 setter 메서드에 일치시킴으로써 JSON 객체의 필드를 java 객체의 필드와 매핑을 한다.

 

예를 들어서 getName이라는 getter메서드가 있으면 get을 지운 name필드와 매핑을 한다.

즉, getXxxx()라는 getter 메서드는 xxxx 필드와 연결이 된다.

 

그래서 나의 문제 상황이었던 getId라는 getter 메서드를 id라고 바꿨을 때 json의 id 필드와 매핑이 되지 않아 id값이 반환이 되지 않는 것으로 생각이 된다.

 

 

참고 : https://jenkov.com/tutorials/java-json/jackson-objectmapper.html#how-jackson-objectmapper-matches-json-fields-to-java-fields