Spring 에서 컨트롤러를 지정해주기 위한 어노테이션은 @Controller 와 @RestController 가 있습니다. 전통적인 Spring MVC 의 컨트롤러인 @Controller 와 Restful 웹서비스의 컨트롤러인 @RestController 의 주요한 차이점은 HTTP Response Body 가 생성되는 방식입니다. 이번에는 2가지 어노테이션의 차이와 사용법에 대해 알아보도록 하겠습니다.
Controller 로 View 반환하기
전통적인 Spring MVC 의 컨트롤러인 @Controller 는 주로 View 를 반환하기 위해 사용합니다. 아래와 같은 과정을 통해 Spring MVC Container 는 Client 의 요청으로부터 View 를 반환합니다.
Controller 가 반환한 뷰의 이름으로부터 View 를 렌더링하기 위해서는 ViewResolver 가 사용되며, ViewResolver 설정에 맞게 View 를 찾아 렌더링합니다.
Controller 로 Data 반환하기
하지만 Spring MVC 의 컨트롤러를 사용하면서 Data 를 반환해야 하는 경우도 있습니다. 컨트롤러에서는 데이터를 반환하기 위해 @ResponseBody 어노테이션을 활용해주어야 합니다. 이를 통해 Controller 도 Json 형태로 데이터를 반환할 수 있습니다.
컨트롤러를 통해 객체를 반환할 때에는 일반적으로 ResponseEntity 로 감싸서 반환을 합니다. 그리고 객체를 반환하기 위해서는 viewResolver 대신에 HttpMessageConverter 가 동작합니다. HttpMessageConverter 에는 여러 Converter 가 등록되어 있고, 반환해야 하는 데이터에 따라 사용되는 Converter 가 달라집니다. 단순 문자열인 경우에는 StringHttpMessageConverter 가 사용되고, 객체인 경우에는 MappingJackson2HttpMessageConverter 가 사용되며, 데이터 종류에 따라 서로 다른 MessageConverter 가 작동하게 됩니다. Spring 은 클라이언트의 HTTP Accept 헤더와 서버의 컨트롤러 반환 타입 정보 둘을 조합해 적합한 HttpMessageConverter 를 선택하여 이를 처리합니다.
@Controller 예제 코드
@Controller
@RequiredArgsConstructor
pulic class UserController {
private final UserService userService;
@getMapping(value = "/users")
pulic @ResponseBody ResponseEntity<User> findUser(@RequestParam("userName") String userName) {
return ResponseEntity.ok(userService.findUser(user));
}
@GetMapping(value = "/users/detailView")
public String detailView(Model model, @RequestParam("userName") String userName) {
User user = userService.findUser(userName);
model.addAttribute("user", user);
return "/users/detailView";
}
}
위 예제의 findUser 는 User 객체를 ResponseEntity 로 감싸서 반환하고 있고, User 를 json 으로 반환하기 위해 @Response Body 라는 어노테이션을 붙여주고 있습니다. detailView 함수에서는 View 를 전달해주고 있기 때문에 String 을 반환값으로 설정해주었습니다. (물론 이렇게 데이터를 반환하는 RestController 와 View 를 반환하는 Controller 를 분리하여 작성하는 것이 좋습니다.)
RestController
@RestController 는 @Controller 에 @ResponseBody 가 추가된 것입니다. 당연하게도 RestController 의 주용도는 Json 형태로 객체 데이터를 반환하는 것입니다. 최근에 데이터를 응답으로 제공하는 REST API 를 개발할 때 주로 사용하며 객체를 ResponseEntity 로 감싸서 반환합니다. 이러한 이유로 동작 과정 역시 @Controller 에 @ResponseBody 를 붙인 것과 완벽히 동일합니다.
@RestController 예제 코드
@RestController
@RequestMapping("/user")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
@GetMapping(value = "/users")
public User findUser(@RequestParam("userName") String userName){
return userService.findUser(user);
}
@GetMapping(value = "/users")
public ResponseEntity<User> findUserWithResponseEntity(@RequestParam("userName") String userName){
return ResponseEntity.ok(userService.findUser(user));
}
}
findUser 는 User 객체를 그대로 반환하고 있습니다. 이러한 경우의 문제는 클라이언트가 예상하는 HttpStatus 를 설정해줄 수 없다는 것입니다. 예를 들어 어떤 객체의 생성 요청이라면 201 CREATED 를 기대할 것이지만 객체를 그대로 반환하면 HttpStatus 를 설정해줄 수 없습니다. 그래서 객체를 상황에 맞는 ResponseEntity 로 감싸서 반환해주어야 합니다.