GVKun编程网logo

哪个更好,在spring3控制器上返回“ ModelAndView”或“ String”(springmvc controller返回值类型)

13

本篇文章给大家谈谈哪个更好,在spring3控制器上返回“ModelAndView”或“String”,以及springmvccontroller返回值类型的知识点,同时本文还将给你拓展ModelAn

本篇文章给大家谈谈哪个更好,在spring3控制器上返回“ ModelAndView”或“ String”,以及springmvc controller返回值类型的知识点,同时本文还将给你拓展ModelAndViewContainer、ModelMap、Model 详细介绍【享学 Spring MVC】、ModelAndViewContainer、ModelMap、Model详细介绍【享学Spring MVC】、Spring Controller ModelAndView 测试 MockMvc 空响应、Spring MVC ModelAndView等相关知识,希望对各位有所帮助,不要忘了收藏本站喔。

本文目录一览:

哪个更好,在spring3控制器上返回“ ModelAndView”或“ String”(springmvc controller返回值类型)

哪个更好,在spring3控制器上返回“ ModelAndView”或“ String”(springmvc controller返回值类型)

返回ModelAndView的方式

@RequestMapping(value = "/list", method = RequestMethod.GET)public ModelAndView list(    @UserAuth UserAuth user,     ModelAndView mav) {    if (!user.isAuthenticated()) {        mav.setViewName("redirect:http://www.test.com/login.jsp");        return mav;    }    mav.setViewName("list");    mav.addObject("articles", listService.getLists());    return mav;}

返回字符串的方式

@RequestMapping(value = "/list", method = RequestMethod.GET)public String list(    @UserAuth UserAuth user,     Model model) {    if (!user.isAuthenticated()) {        return "redirect:http://www.test.com/login.jsp";    }    model.addAttribute("articles", listService.getLists());    return "list";}

这些工作相同。哪个更好的方法?有什么区别?

答案1

小编典典

没有更好的办法。两者都是完全有效的。您选择使用哪一种取决于哪个更适合您的应用程序-Spring允许您以任何一种方式进行操作。

从历史上看,这两种方法来自不同版本的Spring。该ModelAndView方法是在Spring
2.0之前的版本中从控制器返回模型和视图信息的主要方法。现在可以组合Model参数和String返回值,但是旧方法仍然有效。

ModelAndViewContainer、ModelMap、Model 详细介绍【享学 Spring MVC】

ModelAndViewContainer、ModelMap、Model 详细介绍【享学 Spring MVC】

每篇一句

一个开源的技术产品做得好不好,主要是看你能解决多少非功能性问题(因为功能性问题是所有产品都能够想到的)

前言

写这篇文章非我本意,因为我觉得对如题的这个几个类的了解还是比较基础且简单的一块内容,直到有超过两个同学问过我一些问题的时候:通过聊天发现小伙伴都听说过这几个类,但对于他们的使用、功能定位是傻傻分不清楚的(因为名字上都有很多的相似之处)。 那么书写本文就是当作一篇科普类文章记录下来,已经非常熟悉小伙伴就没太大必要往下继续阅读本文内容了,因为这块不算难的(当然我只是建议而已~)。

ModelAndViewContainer

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

ModelAndViewContainer:可以把它定义为 ModelAndView 上下文的容器,它承担着整个请求过程中的数据传递工作 --> 保存着 ModelView。官方 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.

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

当然它除了保存 ModelView 外,还额外提供了一些其它功能。下面我们先来熟悉熟悉它的 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:包括 defaultModleredirectModel
  • defaultModel 是默认使用的 Model,redirectModel 是用于传递 redirect 时的 Model
  • Controller 处理器入参写了 Model或ModelMap 类型时候,实际传入的是 defaultModel。 - defaultModel 它实际是 BindingAwareModel,是个 Map。而且继承了 ModelMap 又实现了 Model 接口,所以在处理器中使用 ModelModelMap 时,其实都是使用同一个对象~~~- 可参考 MapMethodProcessor,它最终调用的都是 mavContainer.getModel() 方法
  • 若处理器入参类型是 RedirectAttributes 类型,最终传入的是 redirectModel。 - 至于为何实际传入的是 defaultModel??参考:RedirectAttributesMethodArgumentResolver,使用的是 new RedirectAttributesModelMap(dataBinder)
  • 维护视图 view(兼容支持逻辑视图名称)
  • 维护是否 redirect 信息 , 及根据这个判断 HandlerAdapter 使用的是 defaultModel 或 redirectModel
  • 维护 @SessionAttributes 注解信息状态
  • 维护 handler 是否处理标记(重要)

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

requestHandled 属性

1、首先看看 isRequestHandled() 方法的使用: RequestMappingHandlerAdaptermavContainer.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 了~

2、setRequestHandled() 方法的使用 作为设置方法,调用的地方有好多个,总结如下:

  • AsyncTaskMethodReturnValueHandler:处理返回值类型是 WebAsyncTask 的方法
// 若返回null,就没必要继续处理了
if (returnValue == null) {
	mavContainer.setRequestHandled(true);
	return;
}
  • CallableMethodReturnValueHandler/DeferredResultMethodReturnValueHandler/StreamingResponseBodyReturnValueHandler:处理返回值类型是 Callable/DeferredResult/ListenableFuture/CompletionStage/StreamingResponseBody 的方法(原理同上)
  • HttpEntityMethodProcessor:返回值类型是 HttpEntity 的方法
// 看一看到,这种返回值的都会标注为已处理,这样就不再需要视图(渲染)了
	@Override
	public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
		mavContainer.setRequestHandled(true); // 第一句就是这句代码
		if (returnValue == null) {
			return;
		}
		... // 交给消息处理器去写		
		outputMessage.flush();
	}
  • 同上的原理的还有 HttpHeadersReturnValueHandler/RequestResponseBodyMethodProcessor/ResponseBodyEmitterReturnValueHandler 等等返回值处理器
  • ServletInvocableHandlerMethod/HandlerMethod 在处理 Handler 方法时,有时也会标注 true 已处理(比如:get 请求 NotModified / 已设置了 HttpStatus 状态码 /isRequestHandled()==true 等等 case)。除了这些 case,method 方法执行完成后可都会显示设置 false 的(因为执行完 handlerMethod 后,还需要交给视图渲染~)
  • ServletResponseMethodArgumentResolver:这唯一一个是处理入参时候的。若入参类型是 ServletResponse/OutputStream/Writer,并且 mavContainer != null,它就设置为 true 了(因为 Spring MVC 认为既然你自己引入了 response,那你就自己做输出吧,因此使用时此处是需要特别注意的细节地方 ~)
resolveArgument()方法:

		if (mavContainer != null) {
			mavContainer.setRequestHandled(true); // 相当于说你自己需要`ServletResponse`,那返回值就交给你自己处理吧~~~~
		}

本文最重要类:ModelAndViewContainer 部分就介绍到这。接下来就介绍就很简单了,轻松且愉快


Model

org.springframework.ui.Model 的概念不管是在 MVC 设计模式上,还是在 Spring MVC 里都是被经常提到的:它用于控制层给前端返回所需的数据(渲染所需的数据)

//  @since 2.5.1 它是一个接口
public interface Model {
	...
	// addAttribute/addAllAttributes/mergeAttributes/containsAttribute
	...
	// Return the current set of model attributes as a Map.
	Map<String, Object> asMap();
}

它的继承树如下: 在这里插入图片描述 最重要的那必须是 ExtendedModelMap 啊,它留到介绍 ModelMap 的时候再详说,简单看看其余子类。

RedirectAttributes

从命名就能看出是和重定向有关的,它扩展了 Model 接口:

// @since 3.1
public interface RedirectAttributes extends Model {
	...
	// 它扩展的三个方法,均和flash属性有关
	RedirectAttributes addFlashAttribute(String attributeName, @Nullable Object attributeValue);
	// 这里没指定key,因为key根据Conventions#getVariableName()自动生成
	RedirectAttributes addFlashAttribute(Object attributeValue);
	// Return the attributes candidate for flash storage or an empty Map.
	Map<String, ?> getFlashAttributes();
}
RedirectAttributesModelMap

它实现了 RedirectAttributes 接口,同时也继承自 ModelMap,所以 "间接" 实现了 Model 接口的所有方法。

public class RedirectAttributesModelMap extends ModelMap implements RedirectAttributes {
	@Nullable
	private final DataBinder dataBinder;
	private final ModelMap flashAttributes = new ModelMap();
	...
	@Override
	public RedirectAttributesModelMap addAttribute(String attributeName, @Nullable Object attributeValue) {
		super.addAttribute(attributeName, formatValue(attributeValue));
		return this;
	}

	// 可见这里的dataBinder是用于数据转换的
	// 把所有参数都转换为String类型(因为Http都是string传参嘛)
	@Nullable
	private String formatValue(@Nullable Object value) {
		if (value == null) {
			return null;
		}
		return (this.dataBinder != null ? this.dataBinder.convertIfNecessary(value, String.class) : value.toString());
	}
	...

	@Override
	public Map<String, Object> asMap() {
		return this;
	}
	@Override
	public RedirectAttributes addFlashAttribute(String attributeName, @Nullable Object attributeValue) {
		this.flashAttributes.addAttribute(attributeName, attributeValue);
		return this;
	}
	...
}

我认为它唯一自己的做的有意义的事:借助 DataBinder 把添加进来的属性参数会转为 String 类型(为何是转换为 String 类型,你有想过吗???)~

ConcurrentModel

它是 Spring5.0 后才有的,是线程安全的 Model,并没提供什么新鲜东西,略(运用于有线程安全问题的场景)


ModelMap

ModelMap 继承自 LinkedHashMap,因此它的本质其实就是个 Map 而已。 它的特点是:借助 Map 的能力间接的实现了 org.springframework.ui.Model 的接口方法,这种设计技巧更值得我们参考学习的(曲线救国的意思有木有~)。 在这里插入图片描述 so,这里只需要看看 ExtendedModelMap 即可。它自己继承自 ModelMap,没有啥特点,全部是调用父类的方法完成的接口方法复写,喵喵他的子类吧~

BindingAwareModelMap

注意:它和普通 ModelMap 的区别是:它能感知数据校验结果(如果放进来的 key 存在对应的绑定结果,并且你的 value 不是绑定结果本身。那就移除掉 MODEL_KEY_PREFIX + key 这个 key 的键值对~)。

public class BindingAwareModelMap extends ExtendedModelMap {

	// 注解复写了Map的put方法,一下子就拦截了所有的addAttr方法。。。
	@Override
	public Object put(String key, Object value) {
		removeBindingResultIfNecessary(key, value);
		return super.put(key, value);
	}
	@Override
	public void putAll(Map<? extends String, ?> map) {
		map.forEach(this::removeBindingResultIfNecessary);
		super.putAll(map);
	}

	// 本类处理的逻辑:
	private void removeBindingResultIfNecessary(Object key, Object value) {
		// key必须是String类型才会给与处理
		if (key instanceof String) {
			String attributeName = (String) key;
			if (!attributeName.startsWith(BindingResult.MODEL_KEY_PREFIX)) {
				String bindingResultKey = BindingResult.MODEL_KEY_PREFIX + attributeName;
				BindingResult bindingResult = (BindingResult) get(bindingResultKey);

				// 如果有校验结果,并且放进来的value值不是绑定结果本身,那就移除掉绑定结果(相当于覆盖掉)
				if (bindingResult != null && bindingResult.getTarget() != value) {
					remove(bindingResultKey);
				}
			}
		}
	}
}

Spring MVC 默认使用的就是这个 ModelMap,但它提供的感知功能大多数情况下我们都用不着。不过反正也不用你管,乖乖用着呗


ModelAndView

顾名思义,ModelAndView 指模型和视图的集合,既包含模型又包含视图;ModelAndView 一般可以作为 Controller 的返回值,所以它的实例是开发者自己手动创建的,这也是它和上面的主要区别(上面都是容器创建,然后注入给我们使用的~)。

因为这个类是直接面向开发者的,所以建议里面的一些 API 还是要熟悉点较好:

public class ModelAndView {
	@Nullable
	private Object view; // 可以是View,也可以是String
	@Nullable
	private ModelMap model;

	// 显然,你也可以自己就放置好一个http状态码进去
	@Nullable
	private HttpStatus status;	
	// 标记这个实例是否被调用过clear()方法~~~
	private boolean cleared = false;

	// 总共这几个属性:它提供的构造函数非常的多  这里我就不一一列出
	public void setViewName(@Nullable String viewName) {
		this.view = viewName;
	}
	public void setView(@Nullable View view) {
		this.view = view;
	}
	@Nullable
	public String getViewName() {
		return (this.view instanceof String ? (String) this.view : null);
	}
	@Nullable
	public View getView() {
		return (this.view instanceof View ? (View) this.view : null);
	}
	public boolean hasView() {
		return (this.view != null);
	}
	public boolean isReference() {
		return (this.view instanceof String);
	}

	// protected方法~~~
	@Nullable
	protected Map<String, Object> getModelInternal() {
		return this.model;
	}
	public ModelMap getModelMap() {
		if (this.model == null) {
			this.model = new ModelMap();
		}
		return this.model;
	}

	// 操作ModelMap的一些方法如下:
	// addObject/addAllObjects

	public void clear() {
		this.view = null;
		this.model = null;
		this.cleared = true;
	}
	// 前提是:this.view == null 
	public boolean isEmpty() {
		return (this.view == null && CollectionUtils.isEmpty(this.model));
	}
	
	// 竟然用的was,歪果仁果然严谨  哈哈
	public boolean wasCleared() {
		return (this.cleared && isEmpty());
	}
}

很多人疑问:为何 Controller 的处理方法不仅仅可以返回 ModelAndView,还可以通过返回 Map/Model/ModelMap 等来直接向页面传值呢???如果返回值是后三者,又是如何找到 view 完成渲染的呢?

这个问题我抛出来,本文不给答案。因为都聊到这了,此问题应该不算难的了,建议小伙伴必须自行弄懂缘由(请不要放过有用的知识点)。若实在有不懂之处可以给留言我会帮你解答的~答案参考提示:可参阅 ModelMethodProcessorModelMethodProcessor 对返回值的处理模块

绝大多数情况下,我都建议返回 ModelAndView,而不是其它那哥三。因为它哥三都没有指定视图名,所以通过 DispatcherServlet.applyDefaultViewName() 生成的视图名一般都不是我们需要的。(除非你的目录、命名等等都特别特别的规范,那顺便倒是可以省不少事~~~


ModelFactory

关于 ModelFactory 它的介绍,这篇文章 里算是已经详细讲解过了,这里再简述两句它的作用。 ModelFactory 是用来维护 Model 的,具体包含两个功能

  1. 初始化 Model
  2. 处理器执行后将 Model 中相应的参数更新到 SessionAttributes 中(处理 @ModelAttribute@SessionAttributes

总结

本以为本文不会很长的,没想到还是写成了超 10000 字的中篇文章。希望这篇文章能够帮助你对 Spring MVC 对模型、视图这块核心内容的理解,帮你扫除途中的一些障碍,共勉~== 若对 Spring、SpringBoot、MyBatis 等源码分析感兴趣,可加我 wx:fsx641385712,手动邀请你入群一起飞 ==

原文出处:https://www.cnblogs.com/fangshixiang/p/11446758.html

ModelAndViewContainer、ModelMap、Model详细介绍【享学Spring MVC】

ModelAndViewContainer、ModelMap、Model详细介绍【享学Spring MVC】

每篇一句

一个开源的技术产品做得好不好,主要是看你能解决多少非功能性问题(因为功能性问题是所有产品都能够想到的)

前言

写这篇文章非我本意,因为我觉得对如题的这个几个类的了解还是比较基础且简单的一块内容,直到有超过两个同学问过我一些问题的时候:通过聊天发现小伙伴都听说过这几个类,但对于他们的使用、功能定位是傻傻分不清楚的(因为名字上都有很多的相似之处)。
那么书写本文就是当作一篇科普类文章记录下来,已经非常熟悉小伙伴就没太大必要往下继续阅读本文内容了,因为这块不算难的(当然我只是建议而已~)。

ModelAndViewContainer

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

ModelAndViewContainer:可以把它定义为ModelAndView上下文的容器,它承担着整个请求过程中的数据传递工作-->保存着ModelView。官方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.

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

当然它除了保存ModelView外,还额外提供了一些其它功能。下面我们先来熟悉熟悉它的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:包括defaultModleredirectModel
  • 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()方法的使用:
RequestMappingHandlerAdaptermavContainer.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了~

2、setRequestHandled()方法的使用
作为设置方法,调用的地方有好多个,总结如下:

  • AsyncTaskMethodReturnValueHandler:处理返回值类型是WebAsyncTask的方法
// 若返回null,就没必要继续处理了
if (returnValue == null) {
    mavContainer.setRequestHandled(true);
    return;
}
  • CallableMethodReturnValueHandler/DeferredResultMethodReturnValueHandler/StreamingResponseBodyReturnValueHandler:处理返回值类型是Callable/DeferredResult/ListenableFuture/CompletionStage/StreamingResponseBody的方法(原理同上)
  • HttpEntityMethodProcessor:返回值类型是HttpEntity的方法
// 看一看到,这种返回值的都会标注为已处理,这样就不再需要视图(渲染)了
    @Override
    public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
        mavContainer.setRequestHandled(true); // 第一句就是这句代码
        if (returnValue == null) {
            return;
        }
        ... // 交给消息处理器去写        
        outputMessage.flush();
    }
  • 同上的原理的还有HttpHeadersReturnValueHandler/RequestResponseBodyMethodProcessor/ResponseBodyEmitterReturnValueHandler等等返回值处理器
  • ServletInvocableHandlerMethod/HandlerMethod在处理Handler方法时,有时也会标注true已处理(比如:get请求NotModified/已设置了HttpStatus状态码/isRequestHandled()==true等等case)。除了这些case,method方法执行完成后可都会显示设置false的(因为执行完handlerMethod后,还需要交给视图渲染~)
  • ServletResponseMethodArgumentResolver:这唯一一个是处理入参时候的。若入参类型是ServletResponse/OutputStream/Writer,并且mavContainer != null,它就设置为true了(因为Spring MVC认为既然你自己引入了response,那你就自己做输出吧,因此使用时此处是需要特别注意的细节地方~)
resolveArgument()方法:

        if (mavContainer != null) {
            mavContainer.setRequestHandled(true); // 相当于说你自己需要`ServletResponse`,那返回值就交给你自己处理吧~~~~
        }

本文最重要类:ModelAndViewContainer部分就介绍到这。接下来就介绍就很简单了,轻松且愉快


Model

org.springframework.ui.Model的概念不管是在MVC设计模式上,还是在Spring MVC里都是被经常提到的:它用于控制层给前端返回所需的数据(渲染所需的数据)

//  @since 2.5.1 它是一个接口
public interface Model {
    ...
    // addAttribute/addAllAttributes/mergeAttributes/containsAttribute
    ...
    // Return the current set of model attributes as a Map.
    Map<String, Object> asMap();
}

它的继承树如下:
在这里插入图片描述
最重要的那必须是ExtendedModelMap啊,它留到介绍ModelMap的时候再详说,简单看看其余子类。

RedirectAttributes

从命名就能看出是和重定向有关的,它扩展了Model接口:

// @since 3.1
public interface RedirectAttributes extends Model {
    ...
    // 它扩展的三个方法,均和flash属性有关
    RedirectAttributes addFlashAttribute(String attributeName, @Nullable Object attributeValue);
    // 这里没指定key,因为key根据Conventions#getVariableName()自动生成
    RedirectAttributes addFlashAttribute(Object attributeValue);
    // Return the attributes candidate for flash storage or an empty Map.
    Map<String, ?> getFlashAttributes();
}
RedirectAttributesModelMap

它实现了RedirectAttributes接口,同时也继承自ModelMap,所以"间接"实现了Model接口的所有方法。

public class RedirectAttributesModelMap extends ModelMap implements RedirectAttributes {
    @Nullable
    private final DataBinder dataBinder;
    private final ModelMap flashAttributes = new ModelMap();
    ...
    @Override
    public RedirectAttributesModelMap addAttribute(String attributeName, @Nullable Object attributeValue) {
        super.addAttribute(attributeName, formatValue(attributeValue));
        return this;
    }

    // 可见这里的dataBinder是用于数据转换的
    // 把所有参数都转换为String类型(因为Http都是string传参嘛)
    @Nullable
    private String formatValue(@Nullable Object value) {
        if (value == null) {
            return null;
        }
        return (this.dataBinder != null ? this.dataBinder.convertIfNecessary(value, String.class) : value.toString());
    }
    ...

    @Override
    public Map<String, Object> asMap() {
        return this;
    }
    @Override
    public RedirectAttributes addFlashAttribute(String attributeName, @Nullable Object attributeValue) {
        this.flashAttributes.addAttribute(attributeName, attributeValue);
        return this;
    }
    ...
}

我认为它唯一自己的做的有意义的事:借助DataBinder把添加进来的属性参数会转为String类型(为何是转换为String类型,你有想过吗???)~

ConcurrentModel

它是Spring5.0后才有的,是线程安全的Model,并没提供什么新鲜东西,略(运用于有线程安全问题的场景)


ModelMap

ModelMap继承自LinkedHashMap,因此它的本质其实就是个Map而已。
它的特点是:借助Map的能力间接的实现了org.springframework.ui.Model的接口方法,这种设计技巧更值得我们参考学习的(曲线救国的意思有木有~)。
在这里插入图片描述
so,这里只需要看看ExtendedModelMap即可。它自己继承自ModelMap,没有啥特点,全部是调用父类的方法完成的接口方法复写,喵喵他的子类吧~

BindingAwareModelMap

注意:它和普通ModelMap的区别是:它能感知数据校验结果(如果放进来的key存在对应的绑定结果,并且你的value不是绑定结果本身。那就移除掉MODEL_KEY_PREFIX + key这个key的键值对~)。

public class BindingAwareModelMap extends ExtendedModelMap {

    // 注解复写了Map的put方法,一下子就拦截了所有的addAttr方法。。。
    @Override
    public Object put(String key, Object value) {
        removeBindingResultIfNecessary(key, value);
        return super.put(key, value);
    }
    @Override
    public void putAll(Map<? extends String, ?> map) {
        map.forEach(this::removeBindingResultIfNecessary);
        super.putAll(map);
    }

    // 本类处理的逻辑:
    private void removeBindingResultIfNecessary(Object key, Object value) {
        // key必须是String类型才会给与处理
        if (key instanceof String) {
            String attributeName = (String) key;
            if (!attributeName.startsWith(BindingResult.MODEL_KEY_PREFIX)) {
                String bindingResultKey = BindingResult.MODEL_KEY_PREFIX + attributeName;
                BindingResult bindingResult = (BindingResult) get(bindingResultKey);

                // 如果有校验结果,并且放进来的value值不是绑定结果本身,那就移除掉绑定结果(相当于覆盖掉)
                if (bindingResult != null && bindingResult.getTarget() != value) {
                    remove(bindingResultKey);
                }
            }
        }
    }
}

Spring MVC默认使用的就是这个ModelMap,但它提供的感知功能大多数情况下我们都用不着。不过反正也不用你管,乖乖用着呗


ModelAndView

顾名思义,ModelAndView指模型和视图的集合,既包含模型又包含视图;ModelAndView一般可以作为Controller的返回值,所以它的实例是开发者自己手动创建的,这也是它和上面的主要区别(上面都是容器创建,然后注入给我们使用的~)。

因为这个类是直接面向开发者的,所以建议里面的一些API还是要熟悉点较好:

public class ModelAndView {
    @Nullable
    private Object view; // 可以是View,也可以是String
    @Nullable
    private ModelMap model;

    // 显然,你也可以自己就放置好一个http状态码进去
    @Nullable
    private HttpStatus status;    
    // 标记这个实例是否被调用过clear()方法~~~
    private boolean cleared = false;

    // 总共这几个属性:它提供的构造函数非常的多  这里我就不一一列出
    public void setViewName(@Nullable String viewName) {
        this.view = viewName;
    }
    public void setView(@Nullable View view) {
        this.view = view;
    }
    @Nullable
    public String getViewName() {
        return (this.view instanceof String ? (String) this.view : null);
    }
    @Nullable
    public View getView() {
        return (this.view instanceof View ? (View) this.view : null);
    }
    public boolean hasView() {
        return (this.view != null);
    }
    public boolean isReference() {
        return (this.view instanceof String);
    }

    // protected方法~~~
    @Nullable
    protected Map<String, Object> getModelInternal() {
        return this.model;
    }
    public ModelMap getModelMap() {
        if (this.model == null) {
            this.model = new ModelMap();
        }
        return this.model;
    }

    // 操作ModelMap的一些方法如下:
    // addObject/addAllObjects

    public void clear() {
        this.view = null;
        this.model = null;
        this.cleared = true;
    }
    // 前提是:this.view == null 
    public boolean isEmpty() {
        return (this.view == null && CollectionUtils.isEmpty(this.model));
    }
    
    // 竟然用的was,歪果仁果然严谨  哈哈
    public boolean wasCleared() {
        return (this.cleared && isEmpty());
    }
}

很多人疑问:为何Controller的处理方法不仅仅可以返回ModelAndView,还可以通过返回Map/Model/ModelMap等来直接向页面传值呢???如果返回值是后三者,又是如何找到view完成渲染的呢?

这个问题我抛出来,本文不给答案。因为都聊到这了,此问题应该不算难的了,建议小伙伴必须自行弄懂缘由(请不要放过有用的知识点)。若实在有不懂之处可以给留言我会帮你解答的~
答案参考提示:可参阅ModelMethodProcessorModelMethodProcessor对返回值的处理模块

绝大多数情况下,我都建议返回ModelAndView,而不是其它那哥三。因为它哥三都没有指定视图名,所以通过DispatcherServlet.applyDefaultViewName()生成的视图名一般都不是我们需要的。(除非你的目录、命名等等都特别特别的规范,那顺便倒是可以省不少事~~~

ModelFactory

关于ModelFactory它的介绍,这篇文章 里算是已经详细讲解过了,这里再简述两句它的作用。
ModelFactory是用来维护Model的,具体包含两个功能

  1. 初始化Model
  2. 处理器执行后将Model中相应的参数更新到SessionAttributes中(处理@ModelAttribute@SessionAttributes

总结

本以为本文不会很长的,没想到还是写成了超10000字的中篇文章。希望这篇文章能够帮助你对Spring MVC对模型、视图这块核心内容的理解,帮你扫除途中的一些障碍,共勉~
==若对Spring、SpringBoot、MyBatis等源码分析感兴趣,可加我wx:fsx641385712,手动邀请你入群一起飞==

Spring Controller ModelAndView 测试 MockMvc 空响应

Spring Controller ModelAndView 测试 MockMvc 空响应

MockMvc 中的关键字是 mock。这个框架没有设置一个完整的带有 JSP 引擎的 Servlet 容器。因此,它不会呈现您的控制器返回的视图,并且 MockHttpServletResponse 不包含主体(对于此用例)。

您可以使用 MockHttpServletResponse#getForwardedUrl() 获取 Servlet 容器会将请求转发到的 JSP 的 URL,该 URL 由您的视图名称以及 InternalResourceViewResolver 的前缀和后缀构成。

>

Spring MVC ModelAndView

Spring MVC ModelAndView

在Spring MVC的Controller中最重要的就是ModelAndView对象。


ModelAndView对象是 模型视图对象。MVC种,Model和View在这个对象都已经存在了。返回后可以告诉前台页面,View就是我需要跳转的页面。Model就是需要携带的参数。


import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
@RequestMapping("/index.do")
public class IndexController {
	@RequestMapping(params = "add")
	public ModelAndView add() {
		// Map<String, Object> params = new HashMap<String, Object>();
		// params.put("sex", "man");
		ModelMap params = new ModelMap();
		params.addAttribute("sex", "man");
		ModelAndView modelAndView = new ModelAndView("success", params);
		return modelAndView;
	}
}

构造返回的ModelAndView有两种方式。

第一种:返回HashMap,对于多个参数,我们需要构造一个HashMap后,返回。(如果返回只有一个Model,我们可以直接调用ModelAndView的构造方法,而不需要这样做)

第二种:Spring MVC中自带ModelMap对象,内部实现其实还是一个Map,我们可以直接addAttribute属性,构造ModelAndView对象。


注意:如果返回的Model对象有多个,我们可以继续调用 .addObject("XX",XX); 方法去添加模型对象。

 

我们今天的关于哪个更好,在spring3控制器上返回“ ModelAndView”或“ String”springmvc controller返回值类型的分享已经告一段落,感谢您的关注,如果您想了解更多关于ModelAndViewContainer、ModelMap、Model 详细介绍【享学 Spring MVC】、ModelAndViewContainer、ModelMap、Model详细介绍【享学Spring MVC】、Spring Controller ModelAndView 测试 MockMvc 空响应、Spring MVC ModelAndView的相关信息,请在本站查询。

本文标签: