이 문서를 통해 아래 질문들에 답할 수 있게 됩니다.
- Controller 반환값이 View 이름인지 Response Body인지 어떻게 결정되는가?
- ViewResolver와 HttpMessageConverter는 각각 어떤 응답 경로를 담당하는가?
- HTML 렌더링 MVC와 JSON REST API는 어디서 갈라지는가?
개요
Spring MVC는 같은 DispatcherServlet 위에서 서버 렌더링 MVC와 REST API를 모두 지원한다. 차이는 Handler 반환값을 어떻게 해석하느냐에 있다.
ViewResolver는 view name을 실제 View로 바꾼다. HttpMessageConverter는 HTTP request body를 객체로 읽거나 객체를 response body로 쓴다. 둘 다 “응답을 만든다”는 점에서는 비슷해 보이지만, ViewResolver는 Model을 사용한 화면 렌더링 경로이고 MessageConverter는 HTTP body를 media type에 맞게 변환하는 경로다.
ViewResolver
전통적인 MVC Controller는 문자열을 반환할 수 있다.
@GetMapping("/members")
public String members(Model model) {
model.addAttribute("members", memberService.findAll());
return "member/list";
}이 반환값은 문자열 body가 아니라 논리적 view name이다. ViewResolver는 "member/list"를 실제 템플릿으로 해석하고 View가 model을 사용해 HTML을 렌더링한다.
HttpMessageConverter
REST API에서는 객체를 response body로 반환한다.
@RestController
public class MemberApiController {
@GetMapping("/api/members")
public List<MemberResponse> members() {
return memberService.findAllResponses();
}
}@RestController는 @Controller와 @ResponseBody를 합친 의미다. 반환 객체는 ViewResolver로 가지 않고 HttpMessageConverter를 통해 JSON, text, byte stream 같은 response body로 변환된다. JSON이 자주 보일 뿐, 실제 형식은 등록된 converter와 content negotiation 결과에 따라 달라진다.
Content Negotiation
요청의 Accept header와 handler의 produces 조건은 응답 형식 결정에 영향을 줄 수 있다. 서버가 만들 수 있는 형식과 클라이언트가 원하는 형식이 맞지 않으면 406 Not Acceptable 같은 문제가 발생할 수 있다. 반대로 Accept가 넓거나 비어 있으면 기본 converter와 서버 설정에 따라 응답 형식이 결정될 수 있다.
요청 body의 Content-Type이 handler의 consumes 조건이나 converter와 맞지 않으면 415 Unsupported Media Type 문제가 발생할 수 있다.
실전 팁
- 문자열 반환이 view name인지 body인지 보려면
@ResponseBody또는@RestController존재 여부를 확인한다. @ResponseBody가 붙었다고 항상 JSON이라고 단정하지 않는다.StringHttpMessageConverter, byte array converter처럼 타입과 media type에 따라 다른 converter가 선택될 수 있다.- JSON API에서는 Entity를 그대로 반환하지 말고 응답 DTO를 둔다.
- 406은 응답 형식 협상 문제, 415는 요청 body 형식 문제로 먼저 분류한다.
ResponseEntity는 body뿐 아니라 status와 header까지 명시하고 싶을 때 사용한다.- 서버 렌더링 Controller와 REST Controller를 패키지나 이름으로 분리하면 반환값 혼동이 줄어든다.
위험 신호!
@Controller에서 문자열을 반환하면서 JSON body라고 기대한다.@RestController에서 view name을 반환하려고 한다.@ResponseBody가 붙은String반환을 view name처럼 이해한다.- Entity를 그대로 JSON으로 내보내 lazy loading, 순환 참조, 과다 노출 문제가 생긴다.
- 406과 415를 모두 JSON 직렬화 오류로만 본다.
- API 응답 status를 예외 상황마다 일관되게 설계하지 않는다.
확인 질문
확인 질문
- ViewResolver는 무엇을 처리하는가?
- 논리적 view name을 실제 View로 해석해 HTML 같은 화면 응답을 만들게 한다.
- HttpMessageConverter는 무엇을 처리하는가?
- 요청 body를 객체로 읽거나 객체를 응답 body로 직렬화한다.
@RestController의 반환값은 기본적으로 어디로 가는가?
- ViewResolver가 아니라 HttpMessageConverter 기반 response body 처리로 간다.