SpringMVC中的处理模型数据ModelAndView
from:http://www.cnblogs.com/yy3b2007com/p/8202769.html
Spring MVC提供了以下几种途径输出模型数据:
1)ModelAndView:处理方法返回值类型为ModelAndView时,方法体即可通过该对象添加模型数据;
2)Map及Model:处理方法入参为org.springframework.ui.Model、org.springframework.ui.ModelMap或java.util.Map时,处理方法返回时,Map中的数据会自动被添加到模型中;
3)@SessionAttributes:将模型中的某个属性暂存到HttpSeession中,以便多个请求之间可以共享这个属性;
4)@ModelAttribute:方法入参标注该注解后,入参的对象就会放到数据模型中。
ModelAndView
用法示例:
添加TestModelAndView.java handler类:
package com.dx.springlearn.hanlders; import java.text.SimpleDateFormat; import java.util.Date; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.servlet.ModelAndView; @Controller public class TestModelData { private final String SUCCESS = "success"; @RequestMapping("/testModelAndView") public ModelAndView testModelAndView() { String viewName = SUCCESS; ModelAndView modelAndView = new ModelAndView(viewName); modelAndView.addObject("currentTime", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); return modelAndView; } }
修改index.jsp,添加链接:
<a href="testModelAndView">test ModelAndView</a> <br />
修改/WEB-INF/views/success.jsp,编辑添加内容:
current time:${requestScope.currentTime} <br>
点击链接地址,显示结果:
对TestModelAndView.java中“ modelAndView.addObject("currentTime", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));”该行添加断点,进行调试:
根据调试信息,索引到DispatcherServlet的doDispatcher方法中:
DispatcherServlet的doDispatcher方法源代码为:
1 /** 2 * Process the actual dispatching to the handler. 3 * <p>The handler will be obtained by applying the servlet's HandlerMappings in order. 4 * The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters 5 * to find the first that supports the handler class. 6 * <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers 7 * themselves to decide which methods are acceptable. 8 * @param request current HTTP request 9 * @param response current HTTP response 10 * @throws Exception in case of any kind of processing failure 11 */ 12 protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { 13 HttpServletRequest processedRequest = request; 14 HandlerExecutionChain mappedHandler = null; 15 boolean multipartRequestParsed = false; 16 17 WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); 18 19 try { 20 ModelAndView mv = null; 21 Exception dispatchException = null; 22 23 try { 24 processedRequest = checkMultipart(request); 25 multipartRequestParsed = (processedRequest != request); 26 27 // Determine handler for the current request. 28 mappedHandler = getHandler(processedRequest); 29 if (mappedHandler == null) { 30 noHandlerFound(processedRequest, response); 31 return; 32 } 33 34 // Determine handler adapter for the current request. 35 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); 36 37 // Process last-modified header, if supported by the handler. 38 String method = request.getMethod(); 39 boolean isGet = "GET".equals(method); 40 if (isGet || "HEAD".equals(method)) { 41 long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); 42 if (logger.isDebugEnabled()) { 43 logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified); 44 } 45 if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { 46 return; 47 } 48 } 49 50 if (!mappedHandler.applyPreHandle(processedRequest, response)) { 51 return; 52 } 53 54 // Actually invoke the handler. 55 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); 56 57 if (asyncManager.isConcurrentHandlingStarted()) { 58 return; 59 } 60 61 applyDefaultViewName(processedRequest, mv); 62 mappedHandler.applyPostHandle(processedRequest, response, mv); 63 } 64 catch (Exception ex) { 65 dispatchException = ex; 66 } 67 catch (Throwable err) { 68 // As of 4.3, we're processing Errors thrown from handler methods as well, 69 // making them available for @ExceptionHandler methods and other scenarios. 70 dispatchException = new NestedServletException("Handler dispatch failed", err); 71 } 72 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); 73 } 74 catch (Exception ex) { 75 triggerAfterCompletion(processedRequest, response, mappedHandler, ex); 76 } 77 catch (Throwable err) { 78 triggerAfterCompletion(processedRequest, response, mappedHandler, 79 new NestedServletException("Handler processing failed", err)); 80 } 81 finally { 82 if (asyncManager.isConcurrentHandlingStarted()) { 83 // Instead of postHandle and afterCompletion 84 if (mappedHandler != null) { 85 mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); 86 } 87 } 88 else { 89 // Clean up any resources used by a multipart request. 90 if (multipartRequestParsed) { 91 cleanupMultipart(processedRequest); 92 } 93 } 94 } 95 }
结合第20行和第55行,我们可以得知:不管SpringMVC的handler类方法返回值是ModelAndView、String,也不管SpringMVC的handler类方法的入参是Map、Model、MapModel等,在SpringMVC内部都会把请求返回结果封装为一个ModelAndView。
ModelAndView实际上内部存储结构就是一个Map<String,Object>,具体请查看ModelAndView源代码
、ModelMap源代码
、LinkedHashMap源代码
继续调试,找到DispatcherServlet的doDispatcher第72行并进入processDispatchResult方法:
1 /** 2 * Handle the result of handler selection and handler invocation, which is 3 * either a ModelAndView or an Exception to be resolved to a ModelAndView. 4 */ 5 private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, 6 @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, 7 @Nullable Exception exception) throws Exception { 8 9 boolean errorView = false; 10 11 if (exception != null) { 12 if (exception instanceof ModelAndViewDefiningException) { 13 logger.debug("ModelAndViewDefiningException encountered", exception); 14 mv = ((ModelAndViewDefiningException) exception).getModelAndView(); 15 } 16 else { 17 Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null); 18 mv = processHandlerException(request, response, handler, exception); 19 errorView = (mv != null); 20 } 21 } 22 23 // Did the handler return a view to render? 24 if (mv != null && !mv.wasCleared()) { 25 render(mv, request, response); 26 if (errorView) { 27 WebUtils.clearErrorRequestAttributes(request); 28 } 29 } 30 else { 31 if (logger.isDebugEnabled()) { 32 logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() + 33 "': assuming HandlerAdapter completed request handling"); 34 } 35 } 36 37 if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) { 38 // Concurrent handling started during a forward 39 return; 40 } 41 42 if (mappedHandler != null) { 43 mappedHandler.triggerAfterCompletion(request, response, null); 44 } 45 }
继续调试,找到DispatcherServlet的processDispatchResult第25行并进入render方法:
1 /** 2 * Render the given ModelAndView. 3 * <p>This is the last stage in handling a request. It may involve resolving the view by name. 4 * @param mv the ModelAndView to render 5 * @param request current HTTP servlet request 6 * @param response current HTTP servlet response 7 * @throws ServletException if view is missing or cannot be resolved 8 * @throws Exception if there's a problem rendering the view 9 */ 10 protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception { 11 // Determine locale for request and apply it to the response. 12 Locale locale = 13 (this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale()); 14 response.setLocale(locale); 15 16 View view; 17 String viewName = mv.getViewName(); 18 if (viewName != null) { 19 // We need to resolve the view name. 20 view = resolveViewName(viewName, mv.getModelInternal(), locale, request); 21 if (view == null) { 22 throw new ServletException("Could not resolve view with name '" + mv.getViewName() + 23 "' in servlet with name '" + getServletName() + "'"); 24 } 25 } 26 else { 27 // No need to lookup: the ModelAndView object contains the actual View object. 28 view = mv.getView(); 29 if (view == null) { 30 throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " + 31 "View object in servlet with name '" + getServletName() + "'"); 32 } 33 } 34 35 // Delegate to the View object for rendering. 36 if (logger.isDebugEnabled()) { 37 logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'"); 38 } 39 try { 40 if (mv.getStatus() != null) { 41 response.setStatus(mv.getStatus().value()); 42 } 43 view.render(mv.getModelInternal(), request, response); 44 } 45 catch (Exception ex) { 46 if (logger.isDebugEnabled()) { 47 logger.debug("Error rendering view [" + view + "] in DispatcherServlet with name '" + 48 getServletName() + "'", ex); 49 } 50 throw ex; 51 } 52 }
继续调试,找到是View接口类的render接口方法,CTRL+T查找引用类结构:
进入AbstractView抽象类,找到render方法:
1 /** 2 * Prepares the view given the specified model, merging it with static 3 * attributes and a RequestContext attribute, if necessary. 4 * Delegates to renderMergedOutputModel for the actual rendering. 5 * @see #renderMergedOutputModel 6 */ 7 @Override 8 public void render(@Nullable Map<String, ?> model, HttpServletRequest request, 9 HttpServletResponse response) throws Exception { 10 11 if (logger.isTraceEnabled()) { 12 logger.trace("Rendering view with name '" + this.beanName + "' with model " + model + 13 " and static attributes " + this.staticAttributes); 14 } 15 16 Map<String, Object> mergedModel = createMergedOutputModel(model, request, response); 17 prepareResponse(request, response); 18 renderMergedOutputModel(mergedModel, getRequestToExpose(request), response); 19 }
找到第18行方法renderMergedOutputModel方法,并进入该方法:
protected abstract void renderMergedOutputModel( Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception;
此方法是AbstractView的一个抽象方法,CTRL+T:
从上图中我们可以发现renderMergedOutputModel的实现类中包含了InternalResourceView,而我们的web.xml配置的springDispatcherServlet指定的类就是该类,因此直接查看InternalResourceView类即可。
进入InternalResourceView类renderMergedOutputModel方法:
1 @Override 2 protected void renderMergedOutputModel( 3 Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception { 4 5 // Expose the model object as request attributes. 6 exposeModelAsRequestAttributes(model, request); 7 8 // Expose helpers as request attributes, if any. 9 exposeHelpers(request); 10 11 // Determine the path for the request dispatcher. 12 String dispatcherPath = prepareForRendering(request, response); 13 14 // Obtain a RequestDispatcher for the target resource (typically a JSP). 15 RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath); 16 if (rd == null) { 17 throw new ServletException("Could not get RequestDispatcher for [" + getUrl() + 18 "]: Check that the corresponding file exists within your web application archive!"); 19 } 20 21 // If already included or response already committed, perform include, else forward. 22 if (useInclude(request, response)) { 23 response.setContentType(getContentType()); 24 if (logger.isDebugEnabled()) { 25 logger.debug("Including resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'"); 26 } 27 rd.include(request, response); 28 } 29 30 else { 31 // Note: The forwarded resource is supposed to determine the content type itself. 32 if (logger.isDebugEnabled()) { 33 logger.debug("Forwarding to resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'"); 34 } 35 rd.forward(request, response); 36 } 37 }
找到第6行exposeModelAsRequestAttributes(model, request);方法并进入,此时进入方法归属类为AbstractView:
1 /** 2 * Expose the model objects in the given map as request attributes. 3 * Names will be taken from the model Map. 4 * This method is suitable for all resources reachable by {@link javax.servlet.RequestDispatcher}. 5 * @param model Map of model objects to expose 6 * @param request current HTTP request 7 */ 8 protected void exposeModelAsRequestAttributes(Map<String, Object> model, 9 HttpServletRequest request) throws Exception { 10 11 model.forEach((modelName, modelValue) -> { 12 if (modelValue != null) { 13 request.setAttribute(modelName, modelValue); 14 if (logger.isDebugEnabled()) { 15 logger.debug("Added model object '" + modelName + "' of type [" + modelValue.getClass().getName() + 16 "] to request in view with name '" + getBeanName() + "'"); 17 } 18 } 19 else { 20 request.removeAttribute(modelName); 21 if (logger.isDebugEnabled()) { 22 logger.debug("Removed model object '" + modelName + 23 "' from request in view with name '" + getBeanName() + "'"); 24 } 25 } 26 }); 27 }
从该方法中我们可以总结出一个结论:
不管SpringMVC的handler类方法返回值是ModelAndView、String,也不管SpringMVC的handler类方法的入参是Map、Model、MapModel等,这些参数信息都会被存放到SpringMVC的request请求域中,这也是为什么success.jsp中显示currentTime时,采用${requestScope.currentTime}的原因。