Model / Map / ModelMap / ModelAndView 原理

一、是什么 —— 这些到底是什么?

1. 问题引入

在 Controller 方法中,我们经常这样写:

// 源码位置:springboot2-master/boot-05-web-01/.../controller/RequestController.java
@GetMapping("/params")
public String testParam(Map<String, Object> map,
                        Model model,
                        HttpServletRequest request) {
    map.put("hello", "world666");           // Map 存数据
    model.addAttribute("world", "hello666"); // Model 存数据
    request.setAttribute("message", "HelloWorld"); // Request 存数据
    return "forward:/success";               // 转发到另一个方法
}

@ResponseBody
@GetMapping("/success")
public Map success(@RequestAttribute("msg") String msg,
                   HttpServletRequest request) {
    // 这里能拿到前面放进去的数据!
    Object hello = request.getAttribute("hello");   // → "world666"
    Object world = request.getAttribute("world");   // → "hello666"
    Object message = request.getAttribute("message"); // → "HelloWorld"
    // 它们都在 HttpServletRequest 的属性里!
}

问题

  • MapModel 是不同类型的参数,为什么存进去的数据最终都在 Request 域中?
  • 它们之间是什么关系?存到 Map 里的数据,Model 里能看到吗?

2. 一句话答案

MapModelModelMap 类型的参数,在 Controller 方法内部指向的是同一个 BindingAwareModelMap 对象。在视图渲染阶段,Spring 把这个 Model 中的所有数据通过 request.setAttribute() 复制到 Request 域中。


二、为什么 —— 为什么这样设计?

1. 统一数据模型

Controller 方法可以声明不同类型的数据容器参数(Map / Model / ModelMap),但底层需要一个统一的、共享的数据载体。这样无论开发者用哪种方式存数据,最终都能被视图层拿到。

2. 兼容不同开发习惯

  • 有人习惯用原生 Map(“我就当它是一个 HashMap”)
  • 有人习惯用 Model(语义更明确:“这是一个数据模型”)
  • 有人习惯用 ModelMap(兼容旧代码)

Spring 不强制你选哪个——底层都是同一个对象


三、怎么做 —— 源码机制深度拆解

第一阶段:参数解析(统一到同一个对象)

1. Map 类型参数 → MapMethodProcessor
// MapMethodProcessor
public boolean supportsParameter(MethodParameter parameter) {
    return Map.class.isAssignableFrom(parameter.getParameterType());
}

public Object resolveArgument(...) {
    Assert.state(mavContainer != null, "ModelAndViewContainer is required");
    return mavContainer.getModel();  // ★ 返回 mavContainer 中的共享 Model
}
2. Model 类型参数 → ModelMethodProcessor
// ModelMethodProcessor
public boolean supportsParameter(MethodParameter parameter) {
    return Model.class.isAssignableFrom(parameter.getParameterType());
}

public Object resolveArgument(...) {
    Assert.state(mavContainer != null, "ModelAndViewContainer is required");
    return mavContainer.getModel();  // ★ 同样返回 mavContainer 中的共享 Model
}

关键发现:两个不同的解析器,核心都是 mavContainer.getModel()

2. 共享数据模型:BindingAwareModelMap

mavContainer.getModel() 返回的到底是什么?

// ModelAndViewContainer 内部定义
private final ModelMap defaultModel = new BindingAwareModelMap();

public ModelMap getModel() {
    // ...
    return this.defaultModel;
}

BindingAwareModelMap 的继承关系:

LinkedHashMap<String, Object>          ← 本质是一个 Map!
    ↑
ModelMap                                ← 实现了 Model 接口
    ↑
BindingAwareModelMap                    ← Spring MVC 的实际类型

核心结论

Controller 方法中的 MapModelModelMap 参数,都指向 ModelAndViewContainer 内部同一个 BindingAwareModelMap 实例。

这意味着:你通过 Map 存的数据,Model 能看见;通过 Model 存的数据,Map 也能看见。它们是同一块内存的不同"视角"。

第二阶段:Controller 执行与数据填充

getMethodArgumentValues() 返回参数数组后,doInvoke(args) 反射执行 Controller 方法。

方法执行期间,所有对传入 Map / Model 的修改(put / addAttribute)都直接写入共享的 BindingAwareModelMap

第三阶段:封装 ModelAndView

Controller 执行完后,RequestMappingHandlerAdapter.invokeHandlerMethod() 调用 getModelAndView() 封装结果:

// RequestMappingHandlerAdapter.getModelAndView()
modelFactory.updateModel(webRequest, mavContainer);  // 处理 Session 属性 + 数据绑定结果
ModelMap model = mavContainer.getModel();             // 取出共享 Model
ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, ...);
return mav;

此时 ModelAndView 内部持有完整的 ModelMap

第四阶段:视图渲染 —— 数据进入 Request 域

回到 DispatcherServlet.processDispatchResult()render()

// 1. 解析视图名 → 找到具体的 View 对象
View view = this.resolveViewName(viewName, mv.getModelInternal(), locale, request);

// 2. 调用 View 渲染
view.render(mv.getModelInternal(), request, response);

view.render() 内部(AbstractView 或其子类),执行关键的数据转移操作

// AbstractView.exposeModelAsRequestAttributes()
model.forEach((name, value) -> {
    // ★ 核心操作:使用 Servlet API 将数据写入 Request 域
    request.setAttribute(name, value);
});

这就是"为什么 Model/Map 中的数据最终会在 Request 域中"的终极答案。


四、完整四阶段流转图

┌─────────────────────────────────────────────────┐
│  阶段一:参数解析(统一)                        │
│  MapMethodProcessor / ModelMethodProcessor       │
│       ↓ 调用                                    │
│  mavContainer.getModel()                        │
│       ↓ 返回                                    │
│  BindingAwareModelMap(同一个实例)               │
├─────────────────────────────────────────────────┤
│  阶段二:Controller 执行(填充)                  │
│  map.put(...) / model.addAttribute(...)          │
│       ↓ 写入                                    │
│  共享的 BindingAwareModelMap                     │
├─────────────────────────────────────────────────┤
│  阶段三:封装 ModelAndView(传递)                │
│  getModelAndView()                              │
│       ↓                                         │
│  new ModelAndView(viewName, modelMap)           │
├─────────────────────────────────────────────────┤
│  阶段四:视图渲染(暴露到 Request 域)             │
│  view.render(model, request, response)          │
│       ↓                                         │
│  exposeModelAsRequestAttributes()                │
│       ↓                                         │
│  request.setAttribute(name, value) ← 转移完成!   │
└─────────────────────────────────────────────────┘

五、项目源码佐证

RequestController 中,我们有一个跨转发的数据传递测试:

// 源码位置:springboot2-master/boot-05-web-01/.../controller/RequestController.java

// 生产者:往 Map 和 Model 里分别放数据
@GetMapping("/params")
public String testParam(Map<String, Object> map, Model model, HttpServletRequest request) {
    map.put("hello", "world666");     // 存入共享 Model
    model.addAttribute("world", "hello666"); // 同样存入共享 Model
    request.setAttribute("message", "HelloWorld"); // 直接放入 Request
    return "forward:/success";  // 转发
}

// 消费者:验证数据是否都在 Request 域中
@ResponseBody
@GetMapping("/success")
public Map success(@RequestAttribute("msg") String msg, HttpServletRequest request) {
    Object hello = request.getAttribute("hello");     // ← map.put 的,拿得到!
    Object world = request.getAttribute("world");     // ← model.addAttribute 的,拿得到!
    Object message = request.getAttribute("message"); // ← request.setAttribute 的,拿得到!
    // 三种方式存的数据全部都在 Request 域中
}

六、总结

核心点 说明
共享模型 MapModelModelMap 参数都指向同一个 BindingAwareModelMap 实例
解析器 MapMethodProcessor(Map)、ModelMethodProcessor(Model)
数据载体 ModelAndViewContainer.defaultModel(类型为 BindingAwareModelMap
数据共享 Controller 内部 Map 和 Model 数据互通(同一块内存)
Request 暴露 视图渲染阶段 exposeModelAsRequestAttributes() 执行 request.setAttribute()
继承链 BindingAwareModelMapModelMapLinkedHashMap(本质是 Map)
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐