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类:

SpringMVC中的处理模型数据ModelAndView
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;
    }
}
SpringMVC中的处理模型数据ModelAndView

修改index.jsp,添加链接:

    <a href="testModelAndView">test ModelAndView</a>
    <br />

修改/WEB-INF/views/success.jsp,编辑添加内容:

    current time:${requestScope.currentTime}
    <br>

点击链接地址,显示结果:

SpringMVC中的处理模型数据ModelAndView

对TestModelAndView.java中“ modelAndView.addObject("currentTime", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));”该行添加断点,进行调试:

根据调试信息,索引到DispatcherServlet的doDispatcher方法中:

SpringMVC中的处理模型数据ModelAndView

DispatcherServlet的doDispatcher方法源代码为:

SpringMVC中的处理模型数据ModelAndView
 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     }
SpringMVC中的处理模型数据ModelAndView

结合第20行和第55行,我们可以得知:不管SpringMVC的handler类方法返回值是ModelAndView、String,也不管SpringMVC的handler类方法的入参是Map、Model、MapModel等,在SpringMVC内部都会把请求返回结果封装为一个ModelAndView。

ModelAndView实际上内部存储结构就是一个Map<String,Object>,具体请查看ModelAndView源代码

SpringMVC中的处理模型数据ModelAndView View Code

、ModelMap源代码

SpringMVC中的处理模型数据ModelAndView View Code

、LinkedHashMap源代码

SpringMVC中的处理模型数据ModelAndView View Code

继续调试,找到DispatcherServlet的doDispatcher第72行并进入processDispatchResult方法:

SpringMVC中的处理模型数据ModelAndView
 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     }
SpringMVC中的处理模型数据ModelAndView

继续调试,找到DispatcherServlet的processDispatchResult第25行并进入render方法:

SpringMVC中的处理模型数据ModelAndView
 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     }
SpringMVC中的处理模型数据ModelAndView

继续调试,找到是View接口类的render接口方法,CTRL+T查找引用类结构:

SpringMVC中的处理模型数据ModelAndView

进入AbstractView抽象类,找到render方法:

SpringMVC中的处理模型数据ModelAndView
 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     }
SpringMVC中的处理模型数据ModelAndView

找到第18行方法renderMergedOutputModel方法,并进入该方法:

protected abstract void renderMergedOutputModel(
            Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception;

此方法是AbstractView的一个抽象方法,CTRL+T:

SpringMVC中的处理模型数据ModelAndView

从上图中我们可以发现renderMergedOutputModel的实现类中包含了InternalResourceView,而我们的web.xml配置的springDispatcherServlet指定的类就是该类,因此直接查看InternalResourceView类即可。

进入InternalResourceView类renderMergedOutputModel方法:

SpringMVC中的处理模型数据ModelAndView
 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     }
SpringMVC中的处理模型数据ModelAndView

找到第6行exposeModelAsRequestAttributes(model, request);方法并进入,此时进入方法归属类为AbstractView:

SpringMVC中的处理模型数据ModelAndView
 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中的处理模型数据ModelAndView

从该方法中我们可以总结出一个结论:

不管SpringMVC的handler类方法返回值是ModelAndView、String,也不管SpringMVC的handler类方法的入参是Map、Model、MapModel等,这些参数信息都会被存放到SpringMVC的request请求域中,这也是为什么success.jsp中显示currentTime时,采用${requestScope.currentTime}的原因。