Spring MVC 控制器方法支持多種返回類型(ModelAndView、String、View、Map、void 等),但最終框架都會將其統一解析為 ModelAndView 對象供視圖解析器使用。本文基於源碼深度剖析這一轉換全過程,並詳細說明 Model 數據的存放、傳遞與作用域機制。
在 AnnotationMethodHandlerAdapter.invokeHandlerMethod() 中:
Object result = methodInvoker.invokeHandlerMethod(...);
ModelAndView mav = methodInvoker.getModelAndView(..., result, ...);
getModelAndView() 是返回值解析的核心入口,處理邏輯如下圖:
public ModelAndView getModelAndView(Method handlerMethod, Class> handlerType,
Object returnValue, ExtendedModelMap implicitModel, ServletWebRequest webRequest) {
// 1. @ResponseStatus 處理
if (responseStatusAnn != null) { ... }
// 2. 自定義 ModelAndViewResolver
if (customModelAndViewResolvers != null) {
for (ModelAndViewResolver resolver : customModelAndViewResolvers) {
ModelAndView mav = resolver.resolveModelAndView(...);
if (mav != UNRESOLVED) return mav;
}
}
// 3. HttpEntity / @ResponseBody → 直接寫響應,返回 null
if (returnValue instanceof HttpEntity) { handleHttpEntityResponse(...); return null; }
if (handlerMethod.hasAnnotation(ResponseBody.class)) { handleResponseBody(...); return null; }
// 4. ModelAndView 直接使用
if (returnValue instanceof ModelAndView) {
ModelAndView mav = (ModelAndView) returnValue;
mav.getModelMap().mergeAttributes(implicitModel);
return mav;
}
// 5. Model / Map → 包裝為 ModelAndView
if (returnValue instanceof Model) {
return new ModelAndView().addAllObjects(implicitModel).addAllObjects(((Model)returnValue).asMap());
}
if (returnValue instanceof Map) {
return new ModelAndView().addAllObjects(implicitModel).addAllObjects((Map)returnValue);
}
// 6. View 對象 → 包裝
if (returnValue instanceof View) {
return new ModelAndView((View)returnValue).addAllObjects(implicitModel);
}
// 7. @ModelAttribute → 作為模型屬性
if (handlerMethod.hasAnnotation(ModelAttribute.class)) {
addReturnValueAsModelAttribute(...);
return new ModelAndView().addAllObjects(implicitModel);
}
// 8. String → 視為視圖名
if (returnValue instanceof String) {
return new ModelAndView((String)returnValue).addAllObjects(implicitModel);
}
// 9. void / null
if (returnValue == null) {
if (responseArgumentUsed || webRequest.isNotModified()) return null;
return new ModelAndView().addAllObjects(implicitModel); // 約定視圖名
}
// 10. 非簡單類型 → 作為單一模型屬性(鍵為類名首字母小寫)
if (!BeanUtils.isSimpleProperty(returnValue.getClass())) {
addReturnValueAsModelAttribute(...);
return new ModelAndView().addAllObjects(implicitModel);
}
// 11. 其他簡單類型 → 非法
throw new IllegalArgumentException("Invalid handler method return value: " + returnValue);
}
@RequestMapping("/test")
public ModelAndView test() {
ModelAndView mv = new ModelAndView();
mv.setViewName("success");
mv.addObject("key", "value"); // 或 mv.addObject("key", userObj);
return mv;
}
@RequestMapping("/test2")
public String test2(Map map) { // 或 Model model
map.put("key", "value");
return "success"; // 返回視圖名
}
框架創建 ExtendedModelMap implicitModel = new BindingAwareModelMap(),所有入參 Map 或 Model 類型參數均綁定到此實例:
if (Model.class.isAssignableFrom(paramType) || Map.class.isAssignableFrom(paramType)) {
args[i] = implicitModel; // 直接賦值同一實例
}
BindingAwareModelMap 繼承鏈:
BindingAwareModelMap → ExtendedModelMap → ModelMap → LinkedHashMap
本質是有序 Map,保證屬性順序,支持點號路徑訪問(user.name)。
實測驗證:
@RequestMapping("/test")
public ModelAndView test() {
ModelAndView mv = new ModelAndView("success");
mv.addObject("testKey", "testValue");
return mv;
}
JSP 頁面:${testKey} 可直接取值 → 數據存放在 HttpServletRequest 域中。
源碼流向:DispatcherServlet.render() → RequestToViewNameTranslator → View.render() → Request.setAttribute()。
| 註解 | 作用 | 存放位置 | 生命周期 |
|---|---|---|---|
| @SessionAttributes("user") | 將模型屬性提升至 Session | HttpSession | 跨請求共享,需 SessionStatus.setComplete() 清除 |
| @ModelAttribute("form") | 方法入參綁定/方法級模型預填充 | Request(默認) | 單次請求 |
map.put("user", user)request.setAttribute() 不會自動進入模型,勿混淆Spring MVC 通過 getModelAndView() 統一了多態返回值處理,Model 數據本質在 BindingAwareModelMap 中流轉,最終落地到 Request 域。理解這一機制,能靈活應對頁面渲染、REST API、異常處理等多樣場景,避免「數據取不到」「視圖解析錯誤」等常見坑點。