티스토리 뷰
@Controller
의 반환값이 문자열로 뷰 이름을 반환하는 것이 아닌 다른 타입의 반환값을 가지게 된다면 어떻게 될까?
클라이언트 요청 및 응답 과정
클라이언트의 요청은 크게 아래와 같은 흐름으로 진행된다.
- 요청이 DispatcherServlet으로 들어온다.
- 요청 정보를 바탕으로 HandlerMapping을 통해 핸들러를 찾는다.
- 핸들러 정보를 바탕으로 HandlerAdapter를 통해 핸들러 어댑터를 찾는다.
- 핸들러 어댑터가 핸들러(컨트롤러)로 요청을 위임한다.
- 핸들러 메서드 실행 후 Model과 View를 반환한다.
- 뷰 이름을 ViewResolver에 전달하고 뷰 리졸버는 해당하는 뷰를 반환한다.
- 디스패처 서블릿은 뷰에게 모델을 전달하고 모델 데이터를 뷰에 렌더링 한다.
- 최종적인 뷰를 클라이언트 응답으로 반환한다.
@Controller
실행 흐름
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
//...
try {
doDispatch(request, response);
}
//...
}
디스패처 서블릿의 doService
에 들어온 요청은 doDispatch
로 전달된다.
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
//...
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
//...
}
doDispatch
에서는 요청 결과 처리를 위해 processDispatchResult
를 호출한다.
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
//...
// Did the handler return a view to render?
if (mv != null && !mv.wasCleared()) {
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
//...
}
processDispatchResult
는 render
를 통해 뷰로 반환할 수 있는지 확인하게 된다.
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
//...
View view;
String viewName = mv.getViewName();
if (viewName != null) {
// We need to resolve the view name.
view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
if (view == null) {
throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
"' in servlet with name '" + getServletName() + "'");
}
}
//...
}
컨트롤러에서 처리된 ModelAndView
에서 뷰 이름을 얻고 resolveViewName
을 호출한다.
protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
Locale locale, HttpServletRequest request) throws Exception {
if (this.viewResolvers != null) {
for (ViewResolver viewResolver : this.viewResolvers) {
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
return view;
}
}
}
return null;
}
등록된 뷰 리졸버 중 뷰 이름을 통해 처리할 수 있는 뷰 리졸버를 찾게 된다. 만약 처리할 수 있는 뷰 리졸버가 존재하지 않는 경우 null
을 반환하게 된다. 최종 반환값이 null
인 경우 해당 뷰를 처리할 수 없기에 예외가 발생하게 된다.
ViewResolver
@Controller 어노테이션을 사용하면 반환값을 처리하는 과정에서 ViewResolver
가 사용된다.
뷰 리졸버란 스프링 프레임워크에서 뷰 객체를 생성하는 방법을 결정하는 인터페이스이다. 뷰 리졸버는 컨트롤러가 반환하는 뷰 이름을 뷰 객체로 변환하는데, 변환된 뷰 객체는 디스패처 서블릿이 클라이언트에게 반환하는 응답을 생성하기 위해 사용된다.
여러 개의 뷰 리졸버를 등록하여 사용할 수 있다. 여러 개의 뷰 리졸버는 등록된 순서대로 검색된다. 이를 통해 뷰 이름을 해석하는 방법을 동적으로 변경할 수 있다.
처음에는 위와 같이 뷰 리졸버가 등록되어 있다.
[ ContentNegotiatingViewResolver ]
뷰를 찾기 위해 요청 URL의 확장자와 AcceptHeader를 사용하는 뷰 리졸버이다. 요청 콘텐츠 형식의 기반을 두어 선택한 하나 이상의 다른 뷰 리졸버에게 위임하는 역할을 한다.
[ BeanNameViewResolver ]
뷰 이름과 동일한 이름을 갖는 빈을 뷰 객체로 사용하는 뷰 리졸버이다. 주로 커스텀 뷰 클래스를 뷰로 사용해야 할 때 사용된다. 이 방법은 뷰 이름과 빈 이름이 일치해야 한다는 제약이 존재한다. 따라서 뷰 이름과 일치하는 이름을 가진 빈이 하나만 존재하도록 보장해야 한다.
[ ViewResolverComposite ]
스프링 프레임워크에서 뷰 리졸버를 묶어서 처리하는 클래스이다. 단순히 뷰 리졸버를 하나만 사용할 때는 사용할 필요가 없지만, 여러 개의 뷰 리졸버를 등록할 때 사용된다.
[ InternalResourceViewResolver ]
스프링의 뷰 리졸버 중 하나로 주로 JSP를 사용할 때 쓰이는 뷰 리졸버이다. 뷰를 생성할 필요 없이 논리적인 이름만을 반환해 주면 된다. prefix와 suffix 프로퍼티를 이용해 앞뒤에 붙는 내용을 생략할 수 있다.
뷰 이름을 반환하지 않아도 가능한가
결론부터 말하자면 가능하다. 정확히 말하면 반환값이 뷰 이름이 아니더라도 존재하는 뷰 이름을 ModelAndView
에 담으면 가능하다
thymeleaf
의존성을 추가한 것으로 가정하고 설명을 진행한다. 타임리프 의존성을 추가하면 ThymeleafViewResolver
가 목록에 추가된다.
@GetMapping("/view/layout")
public String layout() {
return "layout";
}
따라서 위의 경로로 요청하게 되는 경우 타임리프 뷰 리졸버에 의해 /resources/templates/layout.html
경로에 파일이 존재하면 해당 뷰를 반환하게 된다. 하지만 경로에 파일이 존재하지 않으면 앞서 설명한 대로 예외가 발생하게 된다.
@GetMapping("/view/layout")
public ViewDto layout() {
return new ViewDto(1L, "layout");
}
반환값을 Long
, String
타입의 필드를 가지는 객체로 바꾼다면 아래의 사진과 같이 뷰 이름은 view/layout
가 되고 객체는 모델 정보로 매핑된다.
그렇다면 /resources/templates/view/layout.html
경로에 파일이 존재하면 해당 뷰를 반환할 수 있게 되는 것이다. 또한 해당 뷰가 모델에 사용되는 객체의 정보를 사용할 수도 있다.
@GetMapping("/view/layout")
public void layout() {
}
마찬가지로 반환값이 존재하지 않더라도 /resources/template/view/layout.html
경로에 파일이 존재하면 뷰를 반환할 수 있다.
'Spring' 카테고리의 다른 글
[Spring] ObjectMapper에서 LocalDateTime이 변환되지 않는 문제 (8) | 2023.05.21 |
---|---|
[Spring] BeanFactory, ApplicationContext (0) | 2023.05.14 |
[Spring] 스프링을 어노테이션 기반으로 만든 이유 (1) | 2023.05.02 |
[Spring] RestTemplate 알아보기 (2) | 2023.04.22 |