一、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 RedirectView webRequest.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 中,這裡會再寫一篇文章來單獨說明它兩。