SpringMvc渲染视图

这篇博文讨论的问题是从ModelAndView如何渲染到页面。

首先要知道每个请求处理完之后都会返回一个ModelAndView对象。

这里我分6种情况来分析,代表6种返回类型:

  1. ModelAndView
  2. Map,ModelMap
  3. Model
  4. View
  5. String
  6. Void

我先贴出我的测试的后台代码:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
    <!-- 配置扫描的包 -->
    context:component-scan base-package="com"></context:component-scan>
    
    
     配置视图解析器,将方法的返回值映射到一个实际的物理视图 bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        property name="prefix" value="/pages/result/"property="suffix"=".jsp"</beanmvc:default-servlet-handler/>
    mvc:annotation-driven>
 
  
beans>
package com.mmc.modelandview;

import java.util.Date;
 java.util.Map;

 org.springframework.stereotype.Controller;
 org.springframework.ui.Model;
 org.springframework.web.bind.annotation.RequestMapping;
 org.springframework.web.bind.annotation.SessionAttributes;
 org.springframework.web.servlet.ModelAndView;
 org.springframework.web.servlet.View;
 org.springframework.web.servlet.view.InternalResourceView;
 org.springframework.web.servlet.view.jasperreports.JasperReportsPdfView;

 com.mmc.common.CommonParam;


@Controller
public  TestModelAndView {
    
    @RequestMapping("testModelAndView")
    public ModelAndView testModelAndView(){
        ModelAndView modelAndView=new ModelAndView(CommonParam.SUCCESS);
        modelAndView.addObject("time", Date());
        return modelAndView;
    }
    
    
    
    @RequestMapping("testModel" String testModel(Model model){
        model.addAttribute("time",1)"> CommonParam.SUCCESS;
    }
    
    @RequestMapping("testMap"public String testMap(Map<String,Object> map){
        map.put("person","lixiaolu");
          CommonParam.SUCCESS;
    }
    
    
    @RequestMapping("testView" View testView(View view){
        view= JasperReportsPdfView();
         view;
    }
    
    
    @RequestMapping("testString" String testString(){
         CommonParam.SUCCESS;
    } 
    
    @RequestMapping("testForward" String testForward(){
        return "forward:/hello";
    }
    
    @RequestMapping("testVoid"void testVoid(){
        System.out.println("执行testVoid方法");
    }
      
}

 

第一种:返回值是ModelAndView类型:

protected void doDispatch(HttpServletRequest request,HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        boolean multipartRequestParsed = false;

        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

        try {
            ModelAndView mv = ;
            Exception dispatchException = ;

             {
                processedRequest = checkMultipart(request);
                multipartRequestParsed = processedRequest != request;

                // Determine handler for the current request.
                mappedHandler = getHandler(processedRequest);
                if (mappedHandler == null || mappedHandler.getHandler() == ) {
                    noHandlerFound(processedRequest,response);
                    ;
                }

                 Determine handler adapter for the current request.
                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

                 Process last-modified header,if supported by the handler.
                String method = request.getMethod();
                boolean isGet = "GET".equals(method);
                if (isGet || "HEAD".equals(method)) {
                    long lastModified = ha.getLastModified(request,mappedHandler.getHandler());
                    if (logger.isDebugEnabled()) {
                        String requestUri = urlPathHelper.getRequestUri(request);
                        logger.debug("Last-Modified value for [" + requestUri + "] is: " + lastModified);
                    }
                    if (new ServletWebRequest(request,response).checkNotModified(lastModified) && isGet) {
                        ;
                    }
                }

                if (!mappedHandler.applyPreHandle(processedRequest,response)) {
                     {
                     Actually invoke the handler.
                    mv = ha.handle(processedRequest,response,mappedHandler.getHandler());
                }
                finally (asyncManager.isConcurrentHandlingStarted()) {
                        ;
                    }
                }
           //检查view有没有设置,如果没有给他设置一个默认的
                applyDefaultViewName(request,mv);
         //传给我们的拦截器,也就是说在拦截器里我们可以操作视图的映射 mappedHandler.applyPostHandle(processedRequest,mv);
}
catch (Exception ex) { dispatchException = ex; }
       //就是去处理映射了 processDispatchResult(processedRequest,mappedHandler,mv,dispatchException); }
(Exception ex) { triggerAfterCompletion(processedRequest,ex); } (Error err) { triggerAfterCompletionWithError(processedRequest,err); } { (asyncManager.isConcurrentHandlingStarted()) { Instead of postHandle and afterCompletion mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest,response); ; } Clean up any resources used by a multipart request. (multipartRequestParsed) { cleanupMultipart(processedRequest); } } }

这个方法进去之后,经过一些判断,会执行到一个render(mv,request,response);方法,这个方法就是渲染视图的方法。这个方法进去之后

protected void render(ModelAndView mv,HttpServletRequest request,HttpServletResponse response) throws Exception {        // Determine locale for request and apply it to the response.        Locale locale = this.localeResolver.resolveLocale(request);       
response.setLocale(locale); View view;
(mv.isReference()) { We need to resolve the view name.
       //返回一个View对象
view = resolveViewName(mv.getViewName(),mv.getModelInternal(),locale,request); if (view == ) { throw ServletException( "Could not resolve view with name '" + mv.getViewName() + "' in servlet with name '" + getServletName() + "'"); } } else No need to lookup: the ModelAndView object contains the actual View object. view = mv.getView(); new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " + "View object in servlet with name '" + getServletName() + "'"); } } Delegate to the View object for rendering. (logger.isDebugEnabled()) { logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'"); } {
        //渲染视图 view.render(mv.getModelInternal(),response); }
(Exception ex) { (logger.isDebugEnabled()) { logger.debug("Error rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'",ex); } throw ex; } }

 

@Override
    void render(Map<String,?> model,1)"> Exception {
         (logger.isTraceEnabled()) {
            logger.trace("Rendering view with name '" + this.beanName + "' with model " + model +
                " and static attributes " + this.staticAttributes);
        }
      //将我ModelAndView中modelAndView.addObject("time",new Date());的值放入mergeModel中
        Map<String,Object> mergedModel = createMergedOutputModel(model,response);
      //判断是否读取本地缓存
        prepareResponse(request,response);
      //进一步处理,下面贴出它的代码 renderMergedOutputModel(mergedModel,response); }
@Overrivoid renderMergedOutputModel(
            Map<String,Object> model,1)"> Exception {

         Determine which request handle to expose to the RequestDispatcher.
     //确定哪些请求处理暴露给RequestDispatcher。
HttpServletRequest requestToExpose = getRequestToExpose(request); Expose the model object as request attributes.
     //把model里面的值放入request中
exposeModelAsRequestAttributes(model,requestToExpose); Expose helpers as request attributes,if any. exposeHelpers(requestToExpose); Determine the path for the request dispatcher.
     //获取要返回的地址
String dispatcherPath = prepareForRendering(requestToExpose,response); Obtain a RequestDispatcher for the target resource (typically a JSP).
     //确定一个请求处理器为这些参数资源
RequestDispatcher rd = getRequestDispatcher(requestToExpose,dispatcherPath); if (rd == ) { new ServletException("Could not get RequestDispatcher for [" + getUrl() + "]: Check that the corresponding file exists within your web application archive!"); } If already included or response already committed,perform include,else forward. (useInclude(requestToExpose,response)) { response.setContentType(getContentType()); (logger.isDebugEnabled()) { logger.debug("Including resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'"); } rd.include(requestToExpose,response); } Note: The forwarded resource is supposed to determine the content type itself. (logger.isDebugEnabled()) { logger.debug("Forwarding to resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'"); } rd.forward(requestToExpose,response); } }

当要转发的地址,和要携带的信息都放入到requestToExpose里之后,就调用rd.forward(requestToExpose,response);转发请求到页面。

第二种:返回类型是Map

InvocableHandlerMethod类中有下面的方法:

final Object invokeForRequest(NativeWebRequest request,ModelAndViewContainer mavContainer,Object... providedArgs)  Exception {
     //mavContainer对象初始化时,会初始化一个默认的BindingAwareModelMap属性。将默认的这个BindingAwareModelMap属性放入arg

Object[] args = getMethodArgumentValues(request,mavContainer,providedArgs); if (logger.isTraceEnabled()) { StringBuilder sb = new StringBuilder("Invoking ["); sb.append(this.getBeanType().getSimpleName()).append("."); sb.append(getMethod().getName()).append("] method with arguments "); sb.append(Arrays.asList(args)); logger.trace(sb.toString()); }
     //执行我的业务方法,把arg数组传入,把我里面的mavContainer默认的modelMap赋上值
        Object returnValue = invoke(args);
         (logger.isTraceEnabled()) {
            logger.trace("Method [" + getMethod().getName() + "] returned [" + returnValue + "]" returnValue;
    }

 然后将model取出来,和我返回的String一起构建ModelAndView对象。

private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,ModelFactory modelFactory,NativeWebRequest webRequest)  Exception {

        modelFactory.updateModel(webRequest,mavContainer);

         (mavContainer.isRequestHandled()) {
            return ;
        }
     //取出model ModelMap model
= mavContainer.getModel();
   //构建对象 ModelAndView mav = new ModelAndView(mavContainer.getViewName(),model);
mavContainer.isViewReference()) { mav.setView((View) mavContainer.getView()); } if (model instanceof RedirectAttributes) { Map<String,?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes(); HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.); RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes); } mav; }

 第三种:返回类型是Model

需要分析的就是我们加入model中的参数是怎么加入到modelAndView对象的。

其实是和Map类型也是一样的,将

mavContainer对象初始化时,会初始化一个默认的BindingAwareModelMap属性。将默认的这个BindingAwareModelMap属性放入arg,然后把arg传入我们的业务方法,然后业务方法接收到这个参数,往这个参数放值。就像我的例子里那样
  CommonParam.SUCCESS;
    }

放好之后,model就有值了,然后根据这个model和我返回的字符串一起构建ModelAndView对象。

第四种:返回类型是View

这种我还没搞懂怎么用

第五种:返回类型是String

这个类ServletInvocableHandlerMethod的方法里:

final  invokeAndHandle(ServletWebRequest webRequest,1)"> Exception {
      //这里返回我的String类型的值
        Object returnValue = invokeForRequest(webRequest,providedArgs);

        setResponseStatus(webRequest);

        if (returnValue == if (isRequestNotModified(webRequest) || hasResponseStatus() || mavContainer.isRequestHandled()) {
                mavContainer.setRequestHandled(true);
                ;
            }
        }
        else if (StringUtils.hasText(.responseReason)) {
            mavContainer.setRequestHandled();
            ;
        }

        mavContainer.setRequestHandled();

         {
        //进一步处理返回值
.returnValueHandlers.handleReturnValue(returnValue,getReturnValueType(returnValue),webRequest); } (logger.isTraceEnabled()) { logger.trace(getReturnValueHandlingErrorMessage("Error handling return value" ex; } }

然后到ViewNameMethodReturnValueHandler类的这个方法里:

 handleReturnValue(
            Object returnValue,MethodParameter returnType,NativeWebRequest webRequest)
            ;
        }
        if (returnValue  String) {
       //把返回值赋给mavContainer的ViewName String viewName
= (String) returnValue; mavContainer.setViewName(viewName); (isRedirectViewName(viewName)) { mavContainer.setRedirectModelScenario( should not happen new UnsupportedOperationException("Unexpected return type: " + returnType.getParameterType().getName() + " in method: " + returnType.getMethod()); } }

然后通过mavContainer对象来构造一个ModelAndView对象,然后渲染视图。

第五种特别篇:返回值中带有redirect的字符串

在ViewNameMethodReturnValueHandler类中的handleReturnValue方法中:

//同样是把带有redirect的字符串赋给mavContainer对象。
            String viewName  (String) returnValue;
            mavContainer.setViewName(viewName);
             returnType.getMethod());
        }
    }

然后在DispatcherServlet类中有这个方法:

protected View resolveViewName(String viewName,Map<String,1)"> model,Locale locale,HttpServletRequest request) for (ViewResolver viewResolver : .viewResolvers) {
       //获取view,View是一个接口,这里的view实际类型是RedirectView View view
= viewResolver.resolveViewName(viewName,locale); if (view != view; } } ; }

然后根据View类型的不同,调用的也是不同的renderMergedOutputModel方法,这是RedirectView类的方法:

void renderMergedOutputModel(Map<String,HttpServletResponse response)  IOException {

        String targetUrl = createTargetUrl(model,request);
        targetUrl = updateTargetUrl(targetUrl,model,response);

        FlashMap flashMap = RequestContextUtils.getOutputFlashMap(request);
        CollectionUtils.isEmpty(flashMap)) {
            UriComponents uriComponents = UriComponentsBuilder.fromUriString(targetUrl).build();
            flashMap.setTargetRequestPath(uriComponents.getPath());
            flashMap.addTargetRequestParams(uriComponents.getQueryParams());
            FlashMapManager flashMapManager = RequestContextUtils.getFlashMapManager(request);
            if (flashMapManager == new IllegalStateException("FlashMapManager not found despite output FlashMap having been set");
            }
            flashMapManager.saveOutputFlashMap(flashMap,response);
        }

        sendRedirect(request,targetUrl,.http10Compatible);
    }

然后就是response.sendRedirect(encodedRedirectURL);

第五种特别篇之带forward的字符串:

void render(ModelAndView mv,1)"> Determine locale for request and apply it to the response.
        Locale locale = .localeResolver.resolveLocale(request);
        response.setLocale(locale);

        View view;
         We need to resolve the view name.
       //选择不同的类型的视图处理器构建视图对象
{ view.render(mv.getModelInternal(),response); } ex; } }

这个是DispatcherServlet中的resolveViewName方法。

然后是选择了AbstractCachingViewResolver这个视图处理器的resolveViewName方法,去创建视图。然后调用了他的子类UrlBasedViewResolver的创建视图方法:

protected View createView(String viewName,Locale locale)  If this resolver is not supposed to handle the given view, return null to pass on to the next resolver in the chain.
        canHandle(viewName,locale)) {
             Check for special "redirect:" prefix.
         (viewName.startsWith(REDIRECT_URL_PREFIX)) {
            String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
            RedirectView view =  RedirectView(redirectUrl,isRedirectContextRelative(),isRedirectHttp10Compatible());
             applyLifecycleMethods(viewName,view);
        }
         Check for special "forward:" prefix.
     //获得一个forwardUrl
if (viewName.startsWith(FORWARD_URL_PREFIX)) { String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length()); return new InternalResourceView(forwardUrl); } Else fall back to superclass implementation: calling loadView. super.createView(viewName,locale); }

然后调用InternalResourceView类的renderMergedOutputModel方法:

 renderMergedOutputModel(
            Map<String,1)"> Determine which request handle to expose to the RequestDispatcher.
        HttpServletRequest requestToExpose = Expose the model object as request attributes.
 Determine the path for the request dispatcher.
        String dispatcherPath = Obtain a RequestDispatcher for the target resource (typically a JSP).
        RequestDispatcher rd =);
            }
       //调用转发方法 rd.forward(requestToExpose,response); } }

 第六种:返回类型是Void

这时我们需要考虑两个问题,第一:返回值是Void也就是没有返回值,他的ModalAndView对象是什么样的?第二:他将转到哪个页面,还是会直接报错?

带着这两个问题,我们看源码:

以DispatcherServlet中的这句代码往下看,

mv = ha.handle(processedRequest,mappedHandler.getHandler());

一直到ServletInvocableHandlerMethod类的这个方法

 Exception {

        Object returnValue = if (returnValue == null) {
            if (isRequestNotModified(webRequest) || hasResponseStatus() || mavContainer.isRequestHandled()) {
                mavContainer.setRequestHandled(true);
                return;
            }
        }
         {
       //主要看这里,handleReturnValue,处理返回值,那我们点进去看看他怎么处理null值
ex; } }

然而结果就是他看到return是null,什么都没处理就返回了,只好再往下看。

;
                    }
                }
          //最后发现view为null时,这里处理了
                applyDefaultViewName(request,mv);
                mappedHandler.applyPostHandle(processedRequest,mv);
            }
             ex;
            }
            processDispatchResult(processedRequest,dispatchException);
        }
         (multipartRequestParsed) {
                cleanupMultipart(processedRequest);
            }
        }
    }

接下来是组装地址的方法:这个是处理地址组装的类UrlBasedViewResolver

protected View loadView(String viewName,1)"> Exception {
     //进行组装 AbstractUrlBasedView view buildView(viewName); View result =return (view.checkResource(locale) ? result : ); }
protected AbstractUrlBasedView buildView(String viewName)  Exception {
        AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(getViewClass());
     //对应的我最上方spring.mvc里面的prefix和suffix的配置的地址,把它们连接起来 view.setUrl(getPrefix()
+ viewName + getSuffix()); String contentType = getContentType(); if (contentType != ) { view.setContentType(contentType); } view.setRequestContextAttribute(getRequestContextAttribute()); view.setAttributesMap(getAttributesMap()); this.exposePathVariables != ) { view.setExposePathVariables(exposePathVariables); } view; }

到此,要转向哪一个地址,ModelAndView对象是什么样都已经大致清楚了。然后就是调用rd.forward(requestToExpose,response);方法,渲染到页面

 

里面代码很多,思路也不是很清楚。有意义的地方只是给小伙伴们提供了几个方向,要想看springMvc是如何渲染视图的,可以从他的返回类型入手,然后debug一步步的看。源码我是在这里:

https://mvnrepository.com/search?q=spring-ui&p=2     下载的。我这里用的是4.0.0的版本。最后在总结一下吧。

首先,要构造一个ModelAndView对象,这个对象的构建思路是,我创建一个数组,把一个对象放在这个数组里,然后把数组给你,你往这个数组里放值,然后我去取这个数组里的值。然后获取的值我拿来

创建一个ModelAndView对象。

第二步,去渲染视图,有很多的渲染视图的处理器,系统会去判断选择一个处理器,来处理你的请求,大概的不同就是,我最终是调doFord还是doRedirect,我的返回值的header应该设置什么等等。

相关文章

这篇文章主要介绍了spring的事务传播属性REQUIRED_NESTED的原...
今天小编给大家分享的是一文解析spring中事务的传播机制,相...
这篇文章主要介绍了SpringCloudAlibaba和SpringCloud有什么区...
本篇文章和大家了解一下SpringCloud整合XXL-Job的几个步骤。...
本篇文章和大家了解一下Spring延迟初始化会遇到什么问题。有...
这篇文章主要介绍了怎么使用Spring提供的不同缓存注解实现缓...