首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

Spring MVC 返回值處理機制深度解析:從 Handler 到 ModelAndView 的完整轉換鏈路

     編輯:Java 綜合教學

前言

Spring MVC 控制器方法支持多種返回類型(ModelAndView、String、View、Map、void 等),但最終框架都會將其統一解析為 ModelAndView 對象供視圖解析器使用。本文基於源碼深度剖析這一轉換全過程,並詳細說明 Model 數據的存放、傳遞與作用域機制。

一、核心結論:所有返回值最終轉為 ModelAndView

在 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);
}

三、Model 數據存放機制

1. 通過 ModelAndView 存放

@RequestMapping("/test")
public ModelAndView test() {
    ModelAndView mv = new ModelAndView();
    mv.setViewName("success");
    mv.addObject("key", "value"); // 或 mv.addObject("key", userObj);
    return mv;
}

2. 通過方法入參 Model/Map 存放(推薦)

@RequestMapping("/test2")
public String test2(Map map) { // 或 Model model
    map.put("key", "value");
    return "success"; // 返回視圖名
}

3. 底層原理:BindingAwareModelMap

框架創建 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)。

四、Model 數據最終存放位置:Request 域

實測驗證:

@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 與 @ModelAttribute 补充

註解作用存放位置生命周期
@SessionAttributes("user")將模型屬性提升至 SessionHttpSession跨請求共享,需 SessionStatus.setComplete() 清除
@ModelAttribute("form")方法入參綁定/方法級模型預填充Request(默認)單次請求

六、最佳實踐總結

  1. 優先使用 Map/Model 入參 + String 視圖名返回:代碼簡潔、解耦視圖技術
  2. 避免直接返回 ModelAndView:除非需要程序化控制視圖對象
  3. 區分 @ResponseBody 與視圖渲染:REST API 用前者,頁面控制器用後者
  4. 隱式模型屬性命名:返回對象時默認鍵為類名首字母小寫,建議顯式 map.put("user", user)
  5. 請求屬性與模型隔離:request.setAttribute() 不會自動進入模型,勿混淆

總結

Spring MVC 通過 getModelAndView() 統一了多態返回值處理,Model 數據本質在 BindingAwareModelMap 中流轉,最終落地到 Request 域。理解這一機制,能靈活應對頁面渲染、REST API、異常處理等多樣場景,避免「數據取不到」「視圖解析錯誤」等常見坑點。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved