Controller, RestController 어떻게 다를까?
@Controller, @RestController가 어떻게 다른지 알아보자~~
@Controller
는 뷰를 반환할 때 사용된다. ModelAndViwe 객체나 Model 객체를 활용하여 데이터를
전달하고, 템플릿 엔진을 통해 HTML 뷰를 렌더링한다.
만약 JSON, XML과 같은 데이터를 반환하려면 @ResponseBody를 추가해야 한다.
@RestController
은 @Controller + @ResponseBody
으로 RESTful 웹 서비스에서 사용되는 컨트롤러로,
Json 형태로 객체 데이터를 반환한다.
컨트롤러 어노테이션
RestController annotation
@RestController에는 @ResponseBody가 들어있는 것을 확인할 수 있다.
ResponseBody는 메서드의 반환 값을 HTTP 응답본문에 포함시켜 클라이언트에 전달하는 역할을 한다.
Controller 과 RestController의 요청과 응답 과정을 살펴보자.
Request, Response process
-
먼저 요청이 들어오면 DispatcherServlet이 수신한다.
DispatcherServlet은 요청을 처리하기 위한 핵심을 담당하는 프론트 컨트롤러로 전체 요청 처리를 제어한다. -
Handler Mapping이 Request Message의 Header에 저장된 부가적인 정보를 이용해 작업을 위임할 적절한 Controller를 찾는다.
(RequestMappingHandlerMapping, SimpleUrlHandlerMapping 등의 매핑 전략이 동작한다.) -
DispatcherServlet는 HandlerAdapter에 요청을 위임한다.
직접 Controller로 요청을 위임하지 않는 이유는 Controller 구현 방식이 다양하기 때문에 이를 모두 수 용하기 위해서 HandlerAdapter라는 Adapter 인터페이스를 통해 어댑터 패턴을 적용한다. 따라서, 컨트롤의 구현 방식에 상관 없이 요청을 위임할 수 있다. -
HandlerAdapter가 Handler(= Controller)로 요청을 위임한다.
이 과정에서 ArgumentResolver와 HttpMessageConverter가 사용된다. 위임하는 과정에서 Controller의 Method 인자에 선언된 객체를 인스턴스화 한다. 이 과정은 ArgumentResolver가 HttpMessageConverter를 사용해서 Request Message의 데이터를 적절한 데이터 타입으로 변환하고 생성한다.
-
작업 완료 후 리플렉션을 통해 Controller의 메서드를 호출하면, 변환된 파라미터가 컨트롤러 메서드의 인자로 전달된다.
- 컨트롤러가 반환한 값은 다시 DispatcherServlet으로 반환된다.
- 뷰 이름을 반환한 경우 ViewResolver를 통해 렌터링할 뷰를 찾는다.
- 데이터를 반환하는 경우 HttpMessageConverter를 통해 JSON, XML등으로 직렬화한다.
- 최종적으로 DispatcherServlet이 Http 응답을 클라이언트에 반환한다.
ArgumentResolver 프로세스
ArgumentResolver는 컨트롤러 메서드의 각 파라미터를 처리한다. 요청 데이터에서 적합한 값이나 객체를 추출하고, 필요한 경우 변환 작업을 수행한다.
- @RequestBody가 붙은 경우, 요청 본문을 자바 객체로 변환한다.
- @RequestParam이 붙은 경우 쿼리 파라미터 값을 자바 타입으로 변환한다.
- @PathVariable는 URL 경로 변수의 값을 추출한다.
이렇게 변환된 값을 컨트롤러 메서드로 전달한다.
ArgumentResolver는 supportsParameter()를 호출해서 해당 파라미터를 지원하는지 확인 후, 처리가 가능할 경우 resolveArgument()를 호출하여 파라미터를 처리하고, 컨트롤러 메서드에 전달할 값을 반환한다.
이렇게 생성된 객체는 RequestMappingHandlerAdaptor로 반환되고, 핸들러(컨트롤러) 호출에 사용된다.
필요에 따라 직접 HandlerMethodArgumentResolver인터페이스를 구현하여, 원하는 ArgumentResolver를 만들 수 있다. @RequestBody, @RequestParam, @PathVariable 등을 처리한다.
flow
- 클라이언트로부터 요청이 들어오면, DispatcherServlet이 적절한 컨트롤러 메서드를 찾는다.
- HandlerAdapter가 해당 컨트롤러 메서드를 호출하기 전에 메서드 파라미터를 준비합니다.
- 각 파라미터를 처리하기 위해 Spring MVC는 HandlerMethodArgumentResolver 구현체를 순차적으로 실행하여 적합한 값을 생성한다.
- 파라미터를 가공할 때 HTTPMessageConverter를 이용해 Request Messate를 알맞은 데이터 타입으로 변환하고 생성한 후 반환한다.
HTTPMessageConverter
Spring MVC에서 요청 본문을 특정 데이터 타입으로 변환하거나, 응답 데이터를 HTTP 메시지 본문으로 변환하는데 사용되는 인터페이스이다
@RestController 또는 @RequestBody, @ResponseBody 애노테이션과 함께 동작한다.
- canRead(), canWrite() : 메시지 컨버터가 해당 클래스, 미디어 타입을 지원하는지 체크한다.
- read(), write() : 메시지 컨버터를 통해서 메시지를 읽고 쓰는 기능이다.
- @RequestBody, @ResponseBody가 붙은 경우 HTTPMessageConverter를 이용해 Request Message를 알맞은 데이터 타입으로 변환하고 생성한 뒤에 반환한다.
- 기본적으로 제공되는 converter가 다양하게 존재하고 JSON을 역직렬화할 수 있는 라이브러리를 추가하면 자동으로 Converter 가 추가 등록한다.
- MessageConverter 구현체 중 MappingJacksonHttpMessageConverter를 사용하여 리플렉션 (Reflection)을 통해 @RequestBody가 붙은 객체를 가져와 Jackson의 ObjectMapper를 써서 직렬화, 역직렬 화를 하고 적절한 객체 타입으로 캐스팅한다.
Response Process
@Controller의 응답
- 결과 값을 받은 컨트롤러는 결과 값을 Model 객체에 넣거나, View 정보를 담아 DispatcherServlet에 보낸다.
- DispatcherServlet가 뷰 이름을 ViewResolver에게 전달하면, ViewResolver는 실제 뷰 객체를 찾아 반환한다.
- DispatcherServlet은 반환된 View 객체에 렌더링을 요청한다.
- Model 데이터를 이용해 최종 결과를 렌더링하고, HTML, JSP등과 같은 결과를 만들어 클라이언트로 전달한다.
@RestController의 응답
-
ArgumentResolver가 컨트롤러 메서드의 각 파라미터를 처리한 후, 변환된 값을 컨트롤러 메서드로 보낸다.
-
컨트롤러 메서드가 실행되고, 요청을 처리한 후 응답데이터를 반환한다.
-
반환된 값은 ReturnValueHandler를 통해 클라이언트 응답 본문으로 변환된다.
- HandlerMethodReturnValueHandler는 컨트롤러 메서드의 반환값을 처리하는데, 반환값 타입에 적합한 ReturnValueHandler이 선택된다.
- @ResponseBody 또는 @RestController의 경우, 반환값이 JSON 또는 XML로 변환되어 HTTP 응답 본문에 작성되는데, 이 과정에서 HttpMessageConverter이 사용된다.
- HttpMessageConverter는 데이터를 직렬화/ 역직렬화하는 핵심 컴포넌트이다.
- 요청 처리 시 ArgumentResolver가 요청 본문을 Java 객체로 변환할 때 사용되며,
- 응답 처리 시 ReturnValueHandler가 반환된 Java 객체를 JSON/XML 등으로 변환할 때 사용한다.
- 반환값 타입에 적합한 HttpMessageConverter가 선택되고, canWrite()를 통해 적절한 변환기를 확인한 후, 반환값을 HTTP 응답 본문 형식 (JSON, XML)으로 변환한다.
-
변환된 데이터는 HTTP 응답 본문(ResponseBody)에 작성되고, 상태 코드, 응답 헤더, 본문이 모두 설정된다.
- 최종적으로 DispatcherServlet이 HTTP 응답을 클라이언트에게 전달한다.
그리고 응답은 항상 동일한 메세지 형식으로 반환해야 한다.
response message 형식을 공통화하기 위한 방법
- Envelope Pattern
- ApiResponse
객체를 제네릭 터입으로 정의한 후, Handler Method의 리턴 타입으로 사용하는 방법이다. - Controller의 메서드를 정의할 때마다 항상 리턴타입을 ApiResponse
로 감싸주어야 한다.
- ApiResponse
- ResponseBodyAdvice
- ApiResponse
로 객체를 정의하고 AOP를 통해 Handler가 리턴하는 값을 중간에서 가로채, 정의한 타입으로 감싸는 방법이다. - 메서드를 NewPurchaseOrder 타입의 객체를 리턴하면 AOP구현체가 가로채서 ApiResponse
객체로 감싸고 ApiResponse 객체를 리턴한다.
- ApiResponse
ResponseBodyAdvice
각 컨트롤러 메서드의 리턴 값을 공통적으로 가공하고 싶은 요소가 있는 경우 사용한다. 한 가지 제약 조건은 Controller의 메서드가 @ResponseBody || ResponseEntity를 리턴할 때 적용할 수 있다.