// 1. 處理器執行,返回 ModelAndView ModelAndView mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); // 2. 結果處理:視圖解析 + 渲染 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) {
Locale locale = localeResolver.resolveLocale(request);
response.setLocale(locale);
View view;
if (mv.isReference()) {
// === 視圖解析階段 ===
view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);
if (view == null) throw new ServletException("Could not resolve view: " + mv.getViewName());
} else {
// 直接包含 View 對象
view = mv.getView();
}
// === 視圖渲染階段 ===
view.render(mv.getModelInternal(), request, response);
}
protected View resolveViewName(String viewName, Map<String, Object> model, Locale locale, HttpServletRequest request) {
for (ViewResolver resolver : viewResolvers) {
View view = resolver.resolveViewName(viewName, locale);
if (view != null) return view; // 首個成功解析即返回
}
return null;
}
order = Integer.MAX_VALUE (2147483647)ContentNegotiatingViewResolver 默認 Ordered.HIGHEST_PRECEDENCE (-2147483648)| 解析器 | 用途 | 關鍵屬性 |
|---|---|---|
| InternalResourceViewResolver | JSP/內部資源轉發 | prefix, suffix |
| BeanNameViewResolver | Bean 名稱即視圖名 | 無 |
| FreeMarkerViewResolver | FreeMarker 模板 | prefix, suffix, contentType |
| ThymeleafViewResolver | Thymeleaf 模板 | templateMode, characterEncoding |
| ContentNegotiatingViewResolver | 根據 Accept Header/參數協商 | mediaTypes, defaultViews |
| JsonView / MappingJackson2JsonView | JSON 直接輸出 | 無 |
// UrlBasedViewResolver.createView
if (viewName.startsWith("redirect:")) {
return new RedirectView(viewName.substring("redirect:".length()));
}
if (viewName.startsWith("forward:")) {
return new InternalResourceView(viewName.substring("forward:".length()));
}
// 默認:prefix + viewName + suffix → JstlView/InternalResourceView
String url = getPrefix() + viewName + getSuffix();
public View resolveViewName(String viewName, Locale locale) {
if (!context.containsBean(viewName)) return null; // 支持鏈式
return context.getBean(viewName, View.class); // 從容器獲取 View Bean
}
public interface View {
String getContentType();
void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response);
}
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) {
Map<String, Object> merged = createMergedOutputModel(model, request, response);
prepareResponse(request, response);
renderMergedOutputModel(merged, request, response); // 子類實現
}
protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) {
// 1. 模型屬性暴露為請求屬性
exposeModelAsRequestAttributes(model, requestToExpose);
exposeHelpers(requestToExpose);
// 2. 獲取 RequestDispatcher
String dispatcherPath = prepareForRendering(requestToExpose, response);
RequestDispatcher rd = getRequestDispatcher(requestToExpose, dispatcherPath);
// 3. 轉發或包含
if (useInclude(requestToExpose, response)) {
rd.include(requestToExpose, response); // include
} else {
rd.forward(requestToExpose, response); // forward(默認)
}
}
ModelAndView(視圖名 + 模型)DispatcherServlet 遍歷 ViewResolver 鏈,首個成功解析返回 ViewView.render(model, request, response) 執行渲染InternalResourceView 將模型放入請求屬性,RequestDispatcher.forward() 到 JSPInternalResourceViewResolver 時設置 order 確保優先級@ResponseBody / ResponseEntity,繞過視圖解析SimpleMappingExceptionResolver 或 @ControllerAdviceResourceBundleViewResolver 或 ContentNegotiatingViewResolver視圖解析與渲染是 SpringMVC 將模型數據轉化為用戶可感知響應的關鍵環節,掌握其源碼流程有助於排查 404、視圖不生效、亂碼等疑難問題。