ModelAndViewContainer、ModelMap、Model、ModelAndView详细介绍【享学Spring MVC】(上)

简介: ModelAndViewContainer、ModelMap、Model、ModelAndView详细介绍【享学Spring MVC】(上)

前言


写这篇文章非我本意,因为我觉得对如题的这个几个类的了解还是比较基础且简单的一块内容,直到有超过两个同学问过我一些问题的时候:通过聊天发现小伙伴都听说过这几个类,但对于他们的使用、功能定位是傻傻分不清楚的(因为名字上都有很多的相似之处)。

那么书写本文就是当作一篇科普类文章记录下来,已经非常熟悉小伙伴就没太大必要往下继续阅读本文内容了,因为这块不算难的(当然我只是建议而已~)。


ModelAndViewContainer


我把这个类放在首位,是因为相较而言它的逻辑性稍强一点,并且对于理解处理器ReturnValue返回值的处理上有很好的帮助。


ModelAndViewContainer:可以把它定义为ModelAndView上下文的容器,它承担着整个请求过程中的数据传递工作–>保存着Model和View。官方doc对它的解释是这句话:


Records model and view related decisions made by {@link HandlerMethodArgumentResolver HandlerMethodArgumentResolvers} and
{@link HandlerMethodReturnValueHandler HandlerMethodReturnValueHandlers} during the course of invocation of a controller method.

翻译成"人话"便是:记录HandlerMethodArgumentResolver和 HandlerMethodReturnValueHandler在处理Controller的handler方法时 使用的模型model和视图view相关信息.。


当然它除了保存Model和View外,还额外提供了一些其它功能。下面我们先来熟悉熟悉它的API、源码:


// @since 3.1
public class ModelAndViewContainer {
  // =================它所持有的这些属性还是蛮重要的=================
  // redirect时,是否忽略defaultModel 默认值是false:不忽略
  private boolean ignoreDefaultModelOnRedirect = false;
  // 此视图可能是个View,也可能只是个逻辑视图String
  @Nullable
  private Object view;
  // defaultModel默认的Model
  // 注意:ModelMap 只是个Map而已,但是实现类BindingAwareModelMap它却实现了org.springframework.ui.Model接口
  private final ModelMap defaultModel = new BindingAwareModelMap();
  // 重定向时使用的模型(提供set方法设置进来)
  @Nullable
  private ModelMap redirectModel;
  // 控制器是否返回重定向指令
  // 如:使用了前缀"redirect:xxx.jsp"这种,这个值就是true。然后最终是个RedirectView
  private boolean redirectModelScenario = false;
  // Http状态码
  @Nullable
  private HttpStatus status;
  private final Set<String> noBinding = new HashSet<>(4);
  private final Set<String> bindingDisabled = new HashSet<>(4);
  // 很容易想到,它和@SessionAttributes标记的元素有关
  private final SessionStatus sessionStatus = new SimpleSessionStatus();
  // 这个属性老重要了:标记handler是否**已经完成**请求处理
  // 在链式操作中,这个标记很重要
  private boolean requestHandled = false;
  ...
  public void setViewName(@Nullable String viewName) {
    this.view = viewName;
  }
  public void setView(@Nullable Object view) {
    this.view = view;
  }
  // 是否是视图的引用
  public boolean isViewReference() {
    return (this.view instanceof String);
  }
  // 是否使用默认的Model
  private boolean useDefaultModel() {
    return (!this.redirectModelScenario || (this.redirectModel == null && !this.ignoreDefaultModelOnRedirect));
  }
  // 注意子方法和下面getDefaultModel()方法的区别
  public ModelMap getModel() {
    if (useDefaultModel()) { // 使用默认视图
      return this.defaultModel;
    } else {
      if (this.redirectModel == null) { // 若重定向视图为null,就new一个空的返回
        this.redirectModel = new ModelMap();
      }
      return this.redirectModel;
    }
  }
  // @since 4.1.4
  public ModelMap getDefaultModel() {
    return this.defaultModel;
  }
  // @since 4.3 可以设置响应码,最终和ModelAndView一起被View渲染时候使用
  public void setStatus(@Nullable HttpStatus status) {
    this.status = status;
  }
  // 以编程方式注册一个**不应**发生数据绑定的属性,对于随后声明的@ModelAttribute也是不能绑定的
  // 虽然方法是set 但内部是add哦  ~~~~
  public void setBindingDisabled(String attributeName) {
    this.bindingDisabled.add(attributeName);
  }
  public boolean isBindingDisabled(String name) {
    return (this.bindingDisabled.contains(name) || this.noBinding.contains(name));
  }
  // 注册是否应为相应的模型属性进行数据绑定
  public void setBinding(String attributeName, boolean enabled) {
    if (!enabled) {
      this.noBinding.add(attributeName);
    } else {
      this.noBinding.remove(attributeName);
    }
  }
  // 这个方法需要重点说一下:请求是否已在处理程序中完全处理
  // 举个例子:比如@ResponseBody标注的方法返回值,无需View继续去处理,所以就可以设置此值为true了
  // 说明:这个属性也就是可通过源生的ServletResponse、OutputStream来达到同样效果的
  public void setRequestHandled(boolean requestHandled) {
    this.requestHandled = requestHandled;
  }
  public boolean isRequestHandled() {
    return this.requestHandled;
  }
  // =========下面是Model的相关方法了==========
  // addAttribute/addAllAttributes/mergeAttributes/removeAttributes/containsAttribute
}


直观的阅读过源码后,至少我能够得到如下结论,分享给大家:


  • 它维护了模型model:包括defaultModle和redirectModel
  • defaultModel是默认使用的Model,redirectModel是用于传递redirect时的Model
  • 在Controller处理器入参写了Model或ModelMap类型时候,实际传入的是defaultModel。
  • - defaultModel它实际是BindingAwareModel,是个Map。而且继承了ModelMap又实现了Model接口,所以在处理器中使用Model或ModelMap时,其实都是使用同一个对象~~~
  • - 可参考MapMethodProcessor,它最终调用的都是mavContainer.getModel()方法
  • 若处理器入参类型是RedirectAttributes类型,最终传入的是redirectModel。
  • - 至于为何实际传入的是defaultModel??参考:RedirectAttributesMethodArgumentResolver,使用的是new RedirectAttributesModelMap(dataBinder)。
  • 维护视图view(兼容支持逻辑视图名称)
  • 维护是否redirect信息,及根据这个判断HandlerAdapter使用的是defaultModel或redirectModel
  • 维护@SessionAttributes注解信息状态
  • 维护handler是否处理标记(重要)


下面我主要花笔墨重点介绍一下它的requestHandled这个属性的作用:


requestHandled属性


1、首先看看isRequestHandled()方法的使用:

RequestMappingHandlerAdapter对mavContainer.isRequestHandled()方法的使用,或许你就能悟出点啥了:


这个方法的执行实际是:HandlerMethod完全调用执行完成后,就执行这个方法去拿ModelAndView了(传入了request和ModelAndViewContainer)

RequestMappingHandlerAdapter:
  @Nullable
  private ModelAndView getModelAndView(ModelAndViewContainer mavContainer ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
    // 将列为@SessionAttributes的模型属性提升到会话
    modelFactory.updateModel(webRequest, mavContainer);
    if (mavContainer.isRequestHandled()) {
      return null;
    }
    ModelMap model = mavContainer.getModel();
    ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
    // 真正的View 可见ModelMap/视图名称、状态HttpStatus最终都交给了Veiw去渲染
    if (!mavContainer.isViewReference()) {
      mav.setView((View) mavContainer.getView());
    }
    // 这个步骤:是Spring MVC对重定向的支持~~~~
    // 重定向之间传值,使用的RedirectAttributes这种Model~~~~
    if (model instanceof RedirectAttributes) {
      Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
      HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
      if (request != null) {
        RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
      }
    }
  }


可以看到如果ModelAndViewContainer已经被处理过,此处直接返回null,也就是不会再继续处理Model和View了~


相关文章
|
3月前
|
前端开发 Java 微服务
《深入理解Spring》:Spring、Spring MVC与Spring Boot的深度解析
Spring Framework是Java生态的基石,提供IoC、AOP等核心功能;Spring MVC基于其构建,实现Web层MVC架构;Spring Boot则通过自动配置和内嵌服务器,极大简化了开发与部署。三者层层演进,Spring Boot并非替代,而是对前者的高效封装与增强,适用于微服务与快速开发,而深入理解Spring Framework有助于更好驾驭整体技术栈。
|
10月前
|
前端开发 Java 测试技术
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@RequestParam
本文介绍了 `@RequestParam` 注解的使用方法及其与 `@PathVariable` 的区别。`@RequestParam` 用于从请求中获取参数值(如 GET 请求的 URL 参数或 POST 请求的表单数据),而 `@PathVariable` 用于从 URL 模板中提取参数。文章通过示例代码详细说明了 `@RequestParam` 的常用属性,如 `required` 和 `defaultValue`,并展示了如何用实体类封装大量表单参数以简化处理流程。最后,结合 Postman 测试工具验证了接口的功能。
579 0
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@RequestParam
|
10月前
|
JSON 前端开发 Java
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@RequestBody
`@RequestBody` 是 Spring 框架中的注解,用于将 HTTP 请求体中的 JSON 数据自动映射为 Java 对象。例如,前端通过 POST 请求发送包含 `username` 和 `password` 的 JSON 数据,后端可通过带有 `@RequestBody` 注解的方法参数接收并处理。此注解适用于传递复杂对象的场景,简化了数据解析过程。与表单提交不同,它主要用于接收 JSON 格式的实体数据。
998 0
|
10月前
|
前端开发 Java 微服务
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@PathVariable
`@PathVariable` 是 Spring Boot 中用于从 URL 中提取参数的注解,支持 RESTful 风格接口开发。例如,通过 `@GetMapping(&quot;/user/{id}&quot;)` 可以将 URL 中的 `{id}` 参数自动映射到方法参数中。若参数名不一致,可通过 `@PathVariable(&quot;自定义名&quot;)` 指定绑定关系。此外,还支持多参数占位符,如 `/user/{id}/{name}`,分别映射到方法中的多个参数。运行项目后,访问指定 URL 即可验证参数是否正确接收。
599 0
|
10月前
|
JSON 前端开发 Java
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@RequestMapping
@RequestMapping 是 Spring MVC 中用于请求地址映射的注解,可作用于类或方法上。类级别定义控制器父路径,方法级别进一步指定处理逻辑。常用属性包括 value(请求地址)、method(请求类型,如 GET/POST 等,默认 GET)和 produces(返回内容类型)。例如:`@RequestMapping(value = &quot;/test&quot;, produces = &quot;application/json; charset=UTF-8&quot;)`。此外,针对不同请求方式还有简化注解,如 @GetMapping、@PostMapping 等。
525 0
|
10月前
|
JSON 前端开发 Java
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@RestController
本文主要介绍 Spring Boot 中 MVC 开发常用的几个注解及其使用方式,包括 `@RestController`、`@RequestMapping`、`@PathVariable`、`@RequestParam` 和 `@RequestBody`。其中重点讲解了 `@RestController` 注解的构成与特点:它是 `@Controller` 和 `@ResponseBody` 的结合体,适用于返回 JSON 数据的场景。文章还指出,在需要模板渲染(如 Thymeleaf)而非前后端分离的情况下,应使用 `@Controller` 而非 `@RestController`
423 0
|
6月前
|
前端开发 Java API
Spring Cloud Gateway Server Web MVC报错“Unsupported transfer encoding: chunked”解决
本文解析了Spring Cloud Gateway中出现“Unsupported transfer encoding: chunked”错误的原因,指出该问题源于Feign依赖的HTTP客户端与服务端的`chunked`传输编码不兼容,并提供了具体的解决方案。通过规范Feign客户端接口的返回类型,可有效避免该异常,提升系统兼容性与稳定性。
447 0
|
6月前
|
SQL Java 数据库连接
Spring、SpringMVC 与 MyBatis 核心知识点解析
我梳理的这些内容,涵盖了 Spring、SpringMVC 和 MyBatis 的核心知识点。 在 Spring 中,我了解到 IOC 是控制反转,把对象控制权交容器;DI 是依赖注入,有三种实现方式。Bean 有五种作用域,单例 bean 的线程安全问题及自动装配方式也清晰了。事务基于数据库和 AOP,有失效场景和七种传播行为。AOP 是面向切面编程,动态代理有 JDK 和 CGLIB 两种。 SpringMVC 的 11 步执行流程我烂熟于心,还有那些常用注解的用法。 MyBatis 里,#{} 和 ${} 的区别很关键,获取主键、处理字段与属性名不匹配的方法也掌握了。多表查询、动态
194 0
|
6月前
|
JSON 前端开发 Java
第05课:Spring Boot中的MVC支持
第05课:Spring Boot中的MVC支持
300 0
|
12月前
|
SQL Java 数据库连接
对Spring、SpringMVC、MyBatis框架的介绍与解释
Spring 框架提供了全面的基础设施支持,Spring MVC 专注于 Web 层的开发,而 MyBatis 则是一个高效的持久层框架。这三个框架结合使用,可以显著提升 Java 企业级应用的开发效率和质量。通过理解它们的核心特性和使用方法,开发者可以更好地构建和维护复杂的应用程序。
698 29