一、SpringMVC 使用 ModelAndView 来处理返回值问题。1.ModelAndView官方描述:
Holder for both Model and View in the web MVC framework. Note that these are entirely distinct. This class merely holds both to make it possible for a controller to return both model and view in a single return value.<p>Represents a model and view returned by a handler, to be resolved by a DispatcherServlet. The view can take the form of a String view name which will need to be resolved by a ViewResolver object; alternatively a View object can be specified directly. The model is a Map, allowing the use of multiple objects keyed by name. |
说明一下:在 springmvc 框架中,ModelAndView 表示同时持有 Model 和 View 。Model 和 View 是完全不同的。这个类使一个控制器返回单独的一个值同时包含 model 和 view 成为一种可能。同时也代表着一个处理器返回一个 model 和 view ,会被 DispatcherServlet 解析。View 对象如果是通过一个字符串形式的 view name 获取到的,则能被 ViewResolver 对象解析。或者也可以直接指定 View 对象。model 是一个 Map 类型,可以使用多个对象的键值对。2.这里要搞明白一点,为什么说是通过 ModelAndView 来处理返回值问题,事实上,返回的类型可以是很多种,可以是 ModelAndView 类型,也可以是String类型,还可以是View类型,还有很多。但是他们最终会被解析为 ModelAndView 类型的对象。通过源码来证实一下:org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter#invokeHandlerMethod在这个方法中:Object result = methodInvoker.invokeHandlerMethod(handlerMethod, handler, webRequest, implicitModel);ModelAndView mav = methodInvoker.getModelAndView(handlerMethod, handler.getClass(), result, implicitModel, webRequest);其中 result 就是我们调用 handler 方法后的返回值。通过 mehtodInvoker.getModelAndView() 方法将 result 最终解析为 ModelAndView 对象。来看看具体过程:public ModelAndView getModelAndView(Method handlerMethod, Class<?> handlerType, Object returnValue,ExtendedModelMap implicitModel, ServletWebRequest webRequest) throws Exception {ResponseStatus responseStatusAnn = AnnotationUtils.findAnnotation(handlerMethod, ResponseStatus.class);if (responseStatusAnn != null) {HttpStatus responseStatus = responseStatusAnn.value();String reason = responseStatusAnn.reason();if (!StringUtils.hasText(reason)) {webRequest.getResponse().setStatus(responseStatus.value());}else {webRequest.getResponse().sendError(responseStatus.value(), reason);}// to be picked up by the RedirectViewwebRequest.getRequest().setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, responseStatus);responseArgumentUsed = true;}// Invoke custom resolvers if present...if (customModelAndViewResolvers != null) {for (ModelAndViewResolver mavResolver : customModelAndViewResolvers) {ModelAndView mav = mavResolver.resolveModelAndView(handlerMethod, handlerType, returnValue, implicitModel, webRequest);if (mav != ModelAndViewResolver.UNRESOLVED) {return mav;}}}if (returnValue instanceof HttpEntity) {handleHttpEntityResponse((HttpEntity<?>) returnValue, webRequest);return null;}else if (AnnotationUtils.findAnnotation(handlerMethod, ResponseBody.class) != null) {handleResponseBody(returnValue, webRequest);return null;}else if (returnValue instanceof ModelAndView) {ModelAndView mav = (ModelAndView) returnValue;mav.getModelMap().mergeAttributes(implicitModel);return mav;}else if (returnValue instanceof Model) {return new ModelAndView().addAllObjects(implicitModel).addAllObjects(((Model) returnValue).asMap());}else if (returnValue instanceof View) {return new ModelAndView((View) returnValue).addAllObjects(implicitModel);}else if (AnnotationUtils.findAnnotation(handlerMethod, ModelAttribute.class) != null) {addReturnValueAsModelAttribute(handlerMethod, handlerType, returnValue, implicitModel);return new ModelAndView().addAllObjects(implicitModel);}else if (returnValue instanceof Map) {return new ModelAndView().addAllObjects(implicitModel).addAllObjects((Map<String, ?>) returnValue);}else if (returnValue instanceof String) {return new ModelAndView((String) returnValue).addAllObjects(implicitModel);}else if (returnValue == null) {// Either returned null or was "void" return.if (this.responseArgumentUsed || webRequest.isNotModified()) {return null;}else {// Assuming view name translation...return new ModelAndView().addAllObjects(implicitModel);}}else if (!BeanUtils.isSimpleProperty(returnValue.getClass())) {// Assume a single model attribute...addReturnValueAsModelAttribute(handlerMethod, handlerType, returnValue, implicitModel);return new ModelAndView().addAllObjects(implicitModel);}else {throw new IllegalArgumentException("Invalid handler method return value: " + returnValue);}}这个方法很重要,它描述的是将 handler 方法返回值解析为 ModelAndView 的过程,从中可以看出返回值可以为很多类型。建议大家都看看。这篇文章不对具体的每个返回值类型进行说明。3.从官方描述中,也可以看出 ModelAndView 是分为两部分的,Model 和 View。这里就分两部分来说明。其中 View 部分其实是视图渲染问题。4.Model 模型。这里所说的 Model ,不单单指的就是 Model 具体???个类。而是描述的 SpringMVC 如何将 数据存放到 Model 中,以便在目标页面中使用。(1)怎么存放上面部分已经说过,通过 ModelAndView 对象来处理返回值问题。那么就可以通过如下的方式向 Model 中存入数据。如:@RequestMapping("/testModelAndView")public ModelAndView testModelAndView() { ModelAndView mv = new ModelAndView(); mv.setViewName("success"); mv.addObject("testKey", "testValue"); return mv;}通过 ModelAndView 对象的 addObject() 方法 可以向模型中添加数据。事实上,可以在方法的入参处添加 Model 类型 或 Map 类型的参数,可以通过向其中添加数据来完成向模型中数据的添加。如:@RequestMapping("/testModelAndView02")public String testModelAndView2(Map map) { map.put("testKey", "testValue"); return "success";}为什么向 handler 方法的入参处添加 Model 类型或 Map 类型参数添加数据,就能完成 模型数据的添加。通过断点发现传入目标方法的 Map 实际类型是 BindingAwareModelMap 这个类型。源码解析:org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter#invokeHandlerMethod() 方法中:ExtendedModelMap implicitModel = new BindingAwareModelMap(); // 在这里创建的Object result = methodInvoker.invokeHandlerMethod(handlerMethod, handler, webRequest, implicitModel);org.springframework.web.bind.annotation.support.HandlerMethodInvoker#resolveHandlerArguments() 方法中 if (Model.class.isAssignableFrom(paramType) || Map.class.isAssignableFrom(paramType)) {if (!paramType.isAssignableFrom(implicitModel.getClass())) {throw new IllegalStateException("Argument [" + paramType.getSimpleName() + "] is of type " +"Model or Map but is not assignable from the actual model. You may need to switch " +"newer MVC infrastructure classes to use this argument.");}args[i] = implicitModel;//然后在这个地方进行的赋值。}可以看出在目标方法处的 所有 Map 类型(包括其子类型)或是 Model 类型(包括其子类型)的参数都会被转换为 BindingAwareModelMap 这个类型。这里对 BindingAwareModelMap 类型进行说明:(2)存放什么从上面可以看出,存放的是键值对类型的 Map 或 Model。(3)存放到何处 看下面这个例子:@RequestMapping("/testModelAndView")public ModelAndView testModelAndView() { ModelAndView mv = new ModelAndView(); mv.setViewName("success"); mv.addObject("testKey", "testValue"); return mv;} success.jsp通过测试,发现 Model 中的数据默认被存放到了 Request 域中。5.总结SpringMVC 通过 ModelAndView 解决了 handler 方法返回值问题,明白了 handler 方法返回值可以是何种类型,为什么说 ModelAndView 解决了 handler方法返回值问题,因为 handler 方法的返回值最终都会被转换成 ModelAndView 对象。也详细的介绍了 Model 可以作为 handler 方法的入参使用,这里所说的 Model 也不仅仅是指 Model 这个类型,也指实现了 Model 或 Map 接口的类型。也明白了 Model 中存放的什么,存放到了哪里。至此,SpringMVC 方法的返回值问题已经学习完。接下来要学习的是:既然返回值都已经有了,那么该如何去处理呢?——即handler 方法返回值处理问题,也指视图渲染问题。另外,还有两个注解没有进行说明,@SessionAttribute和@ModelAttribute,仔细来说,其实他们两个注解也可以算到 Model 中,这里会再写一篇文章来单独说明它两。Spring学习之第一个Spring MVC程序(IDEA开发环境) http://www.linuxidc.com/Linux/2016-06/132658.htmSpringMVC总结篇 http://www.linuxidc.com/Linux/2016-06/132659.htmSpring+SpringMVC企业快速开发架构搭建 http://www.linuxidc.com/Linux/2015-09/122942.htmSpringMVC的乱码处理 http://www.linuxidc.com/Linux/2015-07/120542.htmSpring MVC+Spring3+Hibernate4开发环境搭建 http://www.linuxidc.com/Linux/2013-07/87119.htm Spring MVC整合Freemarker基于注解方式 http://www.linuxidc.com/Linux/2013-02/79660.htm 基于注解的Spring MVC简单介绍 http://www.linuxidc.com/Linux/2012-02/54896.htmSpringMVC详细示例实战教程 http://www.linuxidc.com/Linux/2015-06/118461.htmSpring MVC 框架搭建及详解 http://www.linuxidc.com/Linux/2012-01/52740.htmSpringMVC 异常处理 http://www.linuxidc.com/Linux/2015-06/119049.htm
本文永久更新链接地址:http://www.linuxidc.com/Linux/2016-08/133972.htm