아래는 7주차 위클리페이퍼 두 번째 주제이다.
- Spring Boot에서 @RestController로 들어온 HTTP 요청이 처리되어 응답으로 변환되는 전체 과정을 설명하세요. 특히 HTTP 메시지 컨버터가 동작하는 시점과 역할을 포함해서 설명하세요.
스프링부트로 웹 API를 만들때 컨트롤러에 @RestController를 사용한다. 왜 우리는 @Controller 대신 @RestController를 사용할까? 그 이유는 웹 아키텍처의 패러다임의 변화이다.
과거 전통적인 웹 개발 환경에서는 서버가 화면까지 모두 반환하는 서버 사이드 렌더링 방식이 주를 이뤘다. JSP나 Thymeleaf와 같은 템플릿 엔진을 사용해 핸들러 메서드의 반환값을 Model에 담아 View와 결합했다.
하지만 현대로 넘어오면서 모바일 기기와 웹 애플리케이션의 복잡성으로 인해 SSR만으로는 UX를 개선하기가 어려워진다. 이를 해결하기 위해 클라이언트 사이드 렌더링(클라이언트가 페이지를 렌더링) 개념이 등장하고 이로 인해 프론트엔드와 백엔드의 분리가 시작되었다. 그 과정에서 백엔드, 즉 서버는 더 이상 화면을 반환하지 않고 HTTP 통신을 통해 데이터(주로 JSON) 를 주고받는 역할에 집중하게 되었다. 이러한 변화 속에서 등장한 것이 바로 @ResponseBody이다. 이 어노테이션은 명시적으로 View 대신 HTTP 응답 바디에 핸들러 메서드가 반환하는 값을 매핑한다는 의미이다.
우리가 얘기 할 주제인 @RestController가 @Controller에 @ResponseBody가 결합된 것이다.
그러면 이제 @RestController로 들어온 HTTP 요청이 응답으로 변환되는 과정을 한번 흐름도로 살펴보자

1. 클라이언트가 HTTP 요청을 서버에 전송하면 Tomcat 서블릿 컨테이너가 받는다.
2. 톰캣 내부적으로 DispatcherServlet이 이 요청을 처리할 수 있는 컨트롤러가 있는지 HandlerMapping을 통해서 찾는다
3. 실행 가능한 컨트롤러가 있다면 HandlerAdapter를 불러서 실행을 위임한다.
4. 컨트롤러 메서드의 파라미터에 따라서(@RequestBody, @PathVariable 등) ArgumentResolver를 부른다.
5. 이때 @RequestBody가 붙어있다면 RequestResponseBodyMethodProcessor를 Resolver로 부른다.
6. 이 리졸버는 HttpMessageConverter를 호출해서 JSON 데이터를 Java 객체로 역직렬화 한다.
7. 생성된 자바 객체를 가지고 핸들러 메서드를 호출한다.
7. 핸들러 메서드는 HandlerMethodReturnValueHandler을 통해서 리턴값을 누가 처리할 수 있는지 확인한다.
8. @ResponseBody가 있다면 RequestResponseBodyMethodProcessor를 ReturnValueHandler로 부른다.
9. 이 ReturnValueHandler는 HttpMessageConverter를 호출해서 Java 객체를 JSON 데이터로 직렬화하고 HttpServletResponse에 저장한다.
10. 그리고 mavContainer.setRequestHandled(true)를 호출하여 응답 처리가 끝났음을 명시한다.
11. HandlerAdapter는 null을 반환하고 DisaptcherServlet은 반환값이 null이고 requestHandled가 true임을 확인하여 ViewResolver를 스킵하고 JSON 데이터를 클라이언트에게 반환한다.
여기서 포인트는 RequestResponseBodyMethodProcessor가 AugumemtResolver와 ReturnValueHandler를 둘다 수행한다는 것이다.
이를 통해 알 수 있는 HttpMessageConverter의 역할은 JSON 데이터 -> Java객체 역직렬화, Java 객체 -> JSON직렬화 이다.
그렇다면 httpMessageConverter는 무엇이고 위의 상황에서 사용된 컨버터는 어떤 컨버터인지 알아보자.
HttpMessageConverter는 HTTP 요청 바디와 응답 바디를 자바 객체로 직렬화/역직렬화 하는 인터페이스이다.
주요 메서드는 다음과 같다.
- canRead(clazz, mediaType): 해당 자바 타입과 HTTP 요청의 Content-Type을 지원하는지 확인
- canWrite(clazz, mediaType): 반환할 자바 타입과 요청의 Accept 헤더를 지원하는지 확인
- read() / write(): 실제로 변환 로직을 수행
그리고 이를 구현하는 여러 컨버터가 있으며 루프를 돌면서 우선순위가 높은 것 중 can~메서드의 조건에 맞는 것을 선택한다.
| 우선순위 | 컨버터 클래스명 | 지원하는 데이터 형식 | 비고 |
| 0 | ByteArrayHttpMessageConverter | byte[] | 이미지, 바이너리 파일 처리 시 사용 |
| 1 | StringHttpMessageConverter | String | 단순 텍스트 평문을 처리할 때 사용 |
| 2 | MappingJackson2HttpMessageConverter | application/json | REST API 핵심 / Jackson 라이브러리를 사용해 JSON 처리 |
| 3 | Jaxb2RootElementHttpMessageConverter | application/xml | XML 데이터를 처리할 때 사용 |
이외에도 x-www-form, octet-stream 등 더 많은 컨버터가 있다.