概述
ModelAndViewContainer
是Spring Web
提供的一个持有器工具,用于记录一个控制器方法调用过程中HandlerMethodArgumentResolver
/HandlerMethodReturnValueHandler
的有关决定。这些决定主要通过ModelAndViewContainer
的如下实例属性来表示 :
requestHandled
- 标记请求是否已经被处理完,如果请求已经被处理完,调用者无需再做后续处理工作:视图解析和渲染。
defaultModel
- 缺省数据模型(一般指非跳转模式下的数据模型)
redirectModel
- 针对跳转模式的数据模型
redirectModelScenario
- 当前模式是否是跳转模式
redirect
- 当前模式是否是跳转模式
ignoreDefaultModelOnRedirect
- 跳转模式下如果没有设置
redirectModel
是否使用defaultModel
- 缺省为
false
- 跳转模式下如果没有设置
view
- 字符串视图名称
- 或者视图对象自身
status
- 将要写入到响应的状态字
源代码
源代码版本 : spring-webmvc-5.1.5.RELEASE
package org.springframework.web.method.support;
// 省略 import 行
public class ModelAndViewContainer {
// 跳转模式下 , 如果没有设置 redirectModel (也就是 RedirectAttributes), 是否使用 defaultModel
// 缺省值为false,不使用
private boolean ignoreDefaultModelOnRedirect = false;
// 最重要渲染的视图,可能是字符串表示视图名称,或是视图对象自身
@Nullable
private Object view;
// 针对非跳转模式设计的数据模型
private final ModelMap defaultModel = new BindingAwareModelMap();
// 针对跳转模式设计的数据模型
@Nullable
private ModelMap redirectModel;
// 标记当前模式是否为跳转模式
private boolean redirectModelScenario = false;
// 将要写入到响应的 HTTP 状态字
@Nullable
private HttpStatus status;
// 记录通过注解 @ModelAttribute(binding=true/false) 设置为不要绑定的属性的名称
private final Set<String> noBinding = new HashSet<>(4);
// 用于记录程序化设置的禁止绑定的属性的集合
private final Set<String> bindingDisabled = new HashSet<>(4);
// 记录当前会话的状态,用于沟通协调会话处理
private final SessionStatus sessionStatus = new SimpleSessionStatus();
// 记录请求是否已经被完全处理,如果记录被完全处理,调用者就知道后续的处理不必要继续了
// 初始值为 false
private boolean requestHandled = false;
/**
* By default the content of the "default" model is used both during
* rendering and redirect scenarios. Alternatively controller methods
* can declare an argument of type RedirectAttributes and use
* it to provide attributes to prepare the redirect URL.
* Setting this flag to true guarantees the "default" model is
* never used in a redirect scenario even if a RedirectAttributes argument
* is not declared. Setting it to false means the "default" model
* may be used in a redirect if the controller method doesn't declare a
* RedirectAttributes argument.
* The default setting is false.
*/
public void setIgnoreDefaultModelOnRedirect(boolean ignoreDefaultModelOnRedirect) {
this.ignoreDefaultModelOnRedirect = ignoreDefaultModelOnRedirect;
}
/**
* Set a view name to be resolved by the DispatcherServlet via a ViewResolver.
* Will override any pre-existing view name or View.
*/
public void setViewName(@Nullable String viewName) {
this.view = viewName;
}
/**
* Return the view name to be resolved by the DispatcherServlet via a
* ViewResolver, or null if a View object is set.
*/
@Nullable
public String getViewName() {
return (this.view instanceof String ? (String) this.view : null);
}
/**
* Set a View object to be used by the DispatcherServlet.
* Will override any pre-existing view name or View.
*/
public void setView(@Nullable Object view) {
this.view = view;
}
/**
* Return the View object, or null if we using a view name
* to be resolved by the DispatcherServlet via a ViewResolver.
*/
@Nullable
public Object getView() {
return this.view;
}
/**
* Whether the view is a view reference specified via a name to be
* resolved by the DispatcherServlet via a ViewResolver.
*/
public boolean isViewReference() {
return (this.view instanceof String);
}
/**
* Return the model to use -- either the "default" or the "redirect" model.
* The default model is used if redirectModelScenario=false or
* there is no redirect model (i.e. RedirectAttributes was not declared as
* a method argument) and ignoreDefaultModelOnRedirect=false.
*/
public ModelMap getModel() {
if (useDefaultModel()) {
return this.defaultModel;
}
else {
if (this.redirectModel == null) {
this.redirectModel = new ModelMap();
}
return this.redirectModel;
}
}
/**
* Whether to use the default model or the redirect model.
*/
private boolean useDefaultModel() {
return (!this.redirectModelScenario
|| (this.redirectModel == null && !this.ignoreDefaultModelOnRedirect));
}
/**
* Return the "default" model created at instantiation.
* In general it is recommended to use #getModel() instead which
* returns either the "default" model (template rendering) or the "redirect"
* model (redirect URL preparation). Use of this method may be needed for
* advanced cases when access to the "default" model is needed regardless,
* e.g. to save model attributes specified via @SessionAttributes.
* @return the default model (never null)
* @since 4.1.4
*/
public ModelMap getDefaultModel() {
return this.defaultModel;
}
/**
* Provide a separate model instance to use in a redirect scenario.
* The provided additional model however is not used unless
* #setRedirectModelScenario gets set to true
* to signal an actual redirect scenario.
*/
public void setRedirectModel(ModelMap redirectModel) {
this.redirectModel = redirectModel;
}
/**
* Whether the controller has returned a redirect instruction, e.g. a
* "redirect:" prefixed view name, a RedirectView instance, etc.
*/
public void setRedirectModelScenario(boolean redirectModelScenario) {
this.redirectModelScenario = redirectModelScenario;
}
/**
* Provide an HTTP status that will be passed on to with the
* ModelAndView used for view rendering purposes.
* @since 4.3
*/
public void setStatus(@Nullable HttpStatus status) {
this.status = status;
}
/**
* Return the configured HTTP status, if any.
* @since 4.3
*/
@Nullable
public HttpStatus getStatus() {
return this.status;
}
/**
* Programmatically register an attribute for which data binding should not occur,
* not even for a subsequent @ModelAttribute declaration.
* @param attributeName the name of the attribute
* @since 4.3
*/
public void setBindingDisabled(String attributeName) {
this.bindingDisabled.add(attributeName);
}
/**
* Whether binding is disabled for the given model attribute.
* @since 4.3
*/
public boolean isBindingDisabled(String name) {
return (this.bindingDisabled.contains(name) || this.noBinding.contains(name));
}
/**
* Register whether data binding should occur for a corresponding model attribute,
* corresponding to an @ModelAttribute(binding=true/false) declaration.
* Note: While this flag will be taken into account by #isBindingDisabled,
* a hard #setBindingDisabled declaration will always override it.
* @param attributeName the name of the attribute
* @since 4.3.13
*/
public void setBinding(String attributeName, boolean enabled) {
if (!enabled) {
this.noBinding.add(attributeName);
}
else {
this.noBinding.remove(attributeName);
}
}
/**
* Return the SessionStatus instance to use that can be used to
* signal that session processing is complete.
*/
public SessionStatus getSessionStatus() {
return this.sessionStatus;
}
/**
* Whether the request has been handled fully within the handler, e.g.
* @ResponseBody method, and therefore view resolution is not
* necessary. This flag can also be set when controller methods declare an
* argument of type ServletResponse or OutputStream).
* The default value is false.
* 一个标志,标志请求是否已经在Handler内部被完全处理,比如使用了注解
* @ResponseBody 的控制器方法就会被Handler完全处理,从而该标志会被
* 设置成 true。另外如果控制器方法带有类型为 ServletResponse/OutputStream
* 的参数,那么这个标志也会被设置为 true,因为这种情况下,框架会认为
* 开发人员会自己处理响应结果的输出。
* 缺省情况下,该标志为 false,也就是需要框架后续的视图解析和渲染过程。
*/
public void setRequestHandled(boolean requestHandled) {
this.requestHandled = requestHandled;
}
/**
* Whether the request has been handled fully within the handler.
*/
public boolean isRequestHandled() {
return this.requestHandled;
}
/**
* Add the supplied attribute to the underlying model.
* A shortcut for getModel().addAttribute(String, Object).
*/
public ModelAndViewContainer addAttribute(String name, @Nullable Object value) {
getModel().addAttribute(name, value);
return this;
}
/**
* Add the supplied attribute to the underlying model.
* A shortcut for getModel().addAttribute(Object).
*/
public ModelAndViewContainer addAttribute(Object value) {
getModel().addAttribute(value);
return this;
}
/**
* Copy all attributes to the underlying model.
* A shortcut for getModel().addAllAttributes(Map).
*/
public ModelAndViewContainer addAllAttributes(@Nullable Map<String, ?> attributes) {
getModel().addAllAttributes(attributes);
return this;
}
/**
* Copy attributes in the supplied Map with existing objects of
* the same name taking precedence (i.e. not getting replaced).
* A shortcut for getModel().mergeAttributes(Map<String, ?>).
*/
public ModelAndViewContainer mergeAttributes(@Nullable Map<String, ?> attributes) {
getModel().mergeAttributes(attributes);
return this;
}
/**
* Remove the given attributes from the model.
*/
public ModelAndViewContainer removeAttributes(@Nullable Map<String, ?> attributes) {
if (attributes != null) {
for (String key : attributes.keySet()) {
getModel().remove(key);
}
}
return this;
}
/**
* Whether the underlying model contains the given attribute name.
* A shortcut for getModel().containsAttribute(String).
*/
public boolean containsAttribute(String name) {
return getModel().containsAttribute(name);
}
/**
* Return diagnostic information.
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder("ModelAndViewContainer: ");
if (!isRequestHandled()) {
if (isViewReference()) {
sb.append("reference to view with name '").append(this.view).append("'");
}
else {
sb.append("View is [").append(this.view).append(']');
}
if (useDefaultModel()) {
sb.append("; default model ");
}
else {
sb.append("; redirect model ");
}
sb.append(getModel());
}
else {
sb.append("Request handled directly");
}
return sb.toString();
}
}