前言
写这篇文章非我本意,因为我觉得对如题的这个几个类的了解还是比较基础且简单的一块内容,直到有超过两个同学问过我一些问题的时候:通过聊天发现小伙伴都听说过这几个类,但对于他们的使用、功能定位是傻傻分不清楚的(因为名字上都有很多的相似之处)。
那么书写本文就是当作一篇科普类文章记录下来,已经非常熟悉小伙伴就没太大必要往下继续阅读本文内容了,因为这块不算难的(当然我只是建议而已~)。
ModelAndViewContainer
我把这个类放在首位,是因为相较而言它的逻辑性稍强一点,并且对于理解处理器ReturnValue返回值的处理上有很好的帮助。
ModelAndViewContainer:可以把它定义为ModelAndView上下文的容器,它承担着整个请求过程中的数据传递工作–>保存着Model和View。官方doc对它的解释是这句话:
翻译成"人话"便是:记录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了~