SpringMvc渲染视图
发布日期:2021-05-16 13:48:19 浏览次数:10 分类:博客文章

本文共 27865 字,大约阅读时间需要 92 分钟。

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

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

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

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

package com.mmc.modelandview;import java.util.Date;import java.util.Map;import org.springframework.stereotype.Controller;import org.springframework.ui.Model;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.SessionAttributes;import org.springframework.web.servlet.ModelAndView;import org.springframework.web.servlet.View;import org.springframework.web.servlet.view.InternalResourceView;import org.springframework.web.servlet.view.jasperreports.JasperReportsPdfView;import com.mmc.common.CommonParam;@Controllerpublic class TestModelAndView {        @RequestMapping("testModelAndView")    public ModelAndView testModelAndView(){        ModelAndView modelAndView=new ModelAndView(CommonParam.SUCCESS);        modelAndView.addObject("time", new Date());        return modelAndView;    }                @RequestMapping("testModel")    public String testModel(Model model){        model.addAttribute("time",new Date());        return CommonParam.SUCCESS;    }        @RequestMapping("testMap")    public String testMap(Map
map){ map.put("person", "lixiaolu"); return CommonParam.SUCCESS; } @RequestMapping("testView") public View testView(View view){ view=new JasperReportsPdfView(); return view; } @RequestMapping("testString") public String testString(){ return CommonParam.SUCCESS; } @RequestMapping("testForward") public String testForward(){ return "forward:/hello"; } @RequestMapping("testVoid") public 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 = null;            Exception dispatchException = null;            try {                processedRequest = checkMultipart(request);                multipartRequestParsed = processedRequest != request;                // Determine handler for the current request.                mappedHandler = getHandler(processedRequest);                if (mappedHandler == null || mappedHandler.getHandler() == null) {                    noHandlerFound(processedRequest, response);                    return;                }                // 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) {                        return;                    }                }                if (!mappedHandler.applyPreHandle(processedRequest, response)) {                    return;                }                try {                    // Actually invoke the handler.                    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());                }                finally {                    if (asyncManager.isConcurrentHandlingStarted()) {                        return;                    }                }           //检查view有没有设置,如果没有给他设置一个默认的                applyDefaultViewName(request, mv);            //传给我们的拦截器,也就是说在拦截器里我们可以操作视图的映射                mappedHandler.applyPostHandle(processedRequest, response, mv);            }            catch (Exception ex) {                dispatchException = ex;            }        //就是去处理映射了            processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);        }        catch (Exception ex) {            triggerAfterCompletion(processedRequest, response, mappedHandler, ex);        }        catch (Error err) {            triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);        }        finally {            if (asyncManager.isConcurrentHandlingStarted()) {                // Instead of postHandle and afterCompletion                mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);                return;            }            // Clean up any resources used by a multipart request.            if (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;        if (mv.isReference()) {            // We need to resolve the view name.        //返回一个View对象             view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);            if (view == null) {                throw new 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();            if (view == null) {                throw 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.        if (logger.isDebugEnabled()) {            logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'");        }        try {         //渲染视图            view.render(mv.getModelInternal(), request, response);        }        catch (Exception ex) {            if (logger.isDebugEnabled()) {                logger.debug("Error rendering view [" + view + "] in DispatcherServlet with name '"                        + getServletName() + "'", ex);            }            throw ex;        }    }

 

@Override    public void render(Map
model, HttpServletRequest request, HttpServletResponse response) throws Exception { if (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
mergedModel = createMergedOutputModel(model, request, response);      //判断是否读取本地缓存 prepareResponse(request, response);       //进一步处理,下面贴出它的代码 renderMergedOutputModel(mergedModel, request, response); }@Overriprotected void renderMergedOutputModel(
Map
model, HttpServletRequest request, HttpServletResponse response) throws 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 == null) { throw 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. if (useInclude(requestToExpose, response)) { response.setContentType(getContentType()); if (logger.isDebugEnabled()) { logger.debug("Including resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'"); } rd.include(requestToExpose, response); } else { // Note: The forwarded resource is supposed to determine the content type itself. if (logger.isDebugEnabled()) { logger.debug("Forwarding to resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'"); } rd.forward(requestToExpose, response); } }

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

第二种:返回类型是Map

InvocableHandlerMethod类中有下面的方法:

public final Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer,            Object... providedArgs) throws 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);        if (logger.isTraceEnabled()) {            logger.trace("Method [" + getMethod().getName() + "] returned [" + returnValue + "]");        }        return returnValue;    }

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

private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,            ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {        modelFactory.updateModel(webRequest, mavContainer);        if (mavContainer.isRequestHandled()) {            return null;        }      //取出model        ModelMap model = mavContainer.getModel();        //构建对象        ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model);        if (!mavContainer.isViewReference()) {            mav.setView((View) mavContainer.getView());        }        if (model instanceof RedirectAttributes) {            Map
flashAttributes = ((RedirectAttributes) model).getFlashAttributes(); HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class); RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes); } return mav; }

 第三种:返回类型是Model

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

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

mavContainer对象初始化时,会初始化一个默认的BindingAwareModelMap属性。将默认的这个BindingAwareModelMap属性放入arg,然后把arg传入我们的业务方法,然后业务方法接收到这个参数,往这个参数放值。就像我的例子里那样
public String testModel(Model model){        model.addAttribute("time",new Date());        return CommonParam.SUCCESS;    }

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

第四种:返回类型是View

这种我还没搞懂怎么用

第五种:返回类型是String

这个类ServletInvocableHandlerMethod的方法里:

public final void invokeAndHandle(ServletWebRequest webRequest,            ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {      //这里返回我的String类型的值        Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);        setResponseStatus(webRequest);        if (returnValue == null) {            if (isRequestNotModified(webRequest) || hasResponseStatus() || mavContainer.isRequestHandled()) {                mavContainer.setRequestHandled(true);                return;            }        }        else if (StringUtils.hasText(this.responseReason)) {            mavContainer.setRequestHandled(true);            return;        }        mavContainer.setRequestHandled(false);        try {         //进一步处理返回值            this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);        }        catch (Exception ex) {            if (logger.isTraceEnabled()) {                logger.trace(getReturnValueHandlingErrorMessage("Error handling return value", returnValue), ex);            }            throw ex;        }    }

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

@Override    public void handleReturnValue(            Object returnValue, MethodParameter returnType,            ModelAndViewContainer mavContainer, NativeWebRequest webRequest)            throws Exception {        if (returnValue == null) {            return;        }        else if (returnValue instanceof String) {        //把返回值赋给mavContainer的ViewName            String viewName = (String) returnValue;            mavContainer.setViewName(viewName);            if (isRedirectViewName(viewName)) {                mavContainer.setRedirectModelScenario(true);            }        }        else {            // should not happen            throw new UnsupportedOperationException("Unexpected return type: " +                    returnType.getParameterType().getName() + " in method: " + returnType.getMethod());        }    }

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

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

在ViewNameMethodReturnValueHandler类中的handleReturnValue方法中:

@Override    public void handleReturnValue(            Object returnValue, MethodParameter returnType,            ModelAndViewContainer mavContainer, NativeWebRequest webRequest)            throws Exception {        if (returnValue == null) {            return;        }        else if (returnValue instanceof String) {        //同样是把带有redirect的字符串赋给mavContainer对象。            String viewName = (String) returnValue;            mavContainer.setViewName(viewName);            if (isRedirectViewName(viewName)) {                mavContainer.setRedirectModelScenario(true);            }        }        else {            // should not happen            throw new UnsupportedOperationException("Unexpected return type: " +                    returnType.getParameterType().getName() + " in method: " + returnType.getMethod());        }    }

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

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

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

@Override    protected void renderMergedOutputModel(Map
model, HttpServletRequest request, HttpServletResponse response) throws IOException { String targetUrl = createTargetUrl(model, request); targetUrl = updateTargetUrl(targetUrl, model, request, response); FlashMap flashMap = RequestContextUtils.getOutputFlashMap(request); if (!CollectionUtils.isEmpty(flashMap)) { UriComponents uriComponents = UriComponentsBuilder.fromUriString(targetUrl).build(); flashMap.setTargetRequestPath(uriComponents.getPath()); flashMap.addTargetRequestParams(uriComponents.getQueryParams()); FlashMapManager flashMapManager = RequestContextUtils.getFlashMapManager(request); if (flashMapManager == null) { throw new IllegalStateException("FlashMapManager not found despite output FlashMap having been set"); } flashMapManager.saveOutputFlashMap(flashMap, request, response); } sendRedirect(request, response, targetUrl, this.http10Compatible); }

然后就是response.sendRedirect(encodedRedirectURL);

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

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;        if (mv.isReference()) {            // We need to resolve the view name.        //选择不同的类型的视图处理器构建视图对象             view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);            if (view == null) {                throw new 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();            if (view == null) {                throw 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.        if (logger.isDebugEnabled()) {            logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'");        }        try {            view.render(mv.getModelInternal(), request, response);        }        catch (Exception ex) {            if (logger.isDebugEnabled()) {                logger.debug("Error rendering view [" + view + "] in DispatcherServlet with name '"                        + getServletName() + "'", ex);            }            throw ex;        }    }

这个是DispatcherServlet中的resolveViewName方法。

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

@Override    protected View createView(String viewName, Locale locale) throws Exception {        // If this resolver is not supposed to handle the given view,        // return null to pass on to the next resolver in the chain.        if (!canHandle(viewName, locale)) {            return null;        }        // Check for special "redirect:" prefix.        if (viewName.startsWith(REDIRECT_URL_PREFIX)) {            String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());            RedirectView view = new RedirectView(redirectUrl, isRedirectContextRelative(), isRedirectHttp10Compatible());            return 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.        return super.createView(viewName, locale);    }

然后调用InternalResourceView类的renderMergedOutputModel方法:

@Override    protected void renderMergedOutputModel(            Map
model, HttpServletRequest request, HttpServletResponse response) throws Exception { // Determine which request handle to expose to the RequestDispatcher. HttpServletRequest requestToExpose = getRequestToExpose(request); // Expose the model object as request attributes. 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 == null) { throw 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. if (useInclude(requestToExpose, response)) { response.setContentType(getContentType()); if (logger.isDebugEnabled()) { logger.debug("Including resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'"); } rd.include(requestToExpose, response); } else { // Note: The forwarded resource is supposed to determine the content type itself. if (logger.isDebugEnabled()) { logger.debug("Forwarding to resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'"); }        //调用转发方法 rd.forward(requestToExpose, response); } }

 第六种:返回类型是Void

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

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

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

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

一直到ServletInvocableHandlerMethod类的这个方法

public final void invokeAndHandle(ServletWebRequest webRequest,            ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {        Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);        setResponseStatus(webRequest);        if (returnValue == null) {            if (isRequestNotModified(webRequest) || hasResponseStatus() || mavContainer.isRequestHandled()) {                mavContainer.setRequestHandled(true);                return;            }        }        else if (StringUtils.hasText(this.responseReason)) {            mavContainer.setRequestHandled(true);            return;        }        mavContainer.setRequestHandled(false);        try {         //主要看这里,handleReturnValue,处理返回值,那我们点进去看看他怎么处理null值            this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);        }        catch (Exception ex) {            if (logger.isTraceEnabled()) {                logger.trace(getReturnValueHandlingErrorMessage("Error handling return value", returnValue), ex);            }            throw ex;        }    }

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

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 = null;            Exception dispatchException = null;            try {                processedRequest = checkMultipart(request);                multipartRequestParsed = processedRequest != request;                // Determine handler for the current request.                mappedHandler = getHandler(processedRequest);                if (mappedHandler == null || mappedHandler.getHandler() == null) {                    noHandlerFound(processedRequest, response);                    return;                }                // 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) {                        return;                    }                }                if (!mappedHandler.applyPreHandle(processedRequest, response)) {                    return;                }                try {                    // Actually invoke the handler.                    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());                }                finally {                    if (asyncManager.isConcurrentHandlingStarted()) {                        return;                    }                }          //最后发现view为null时,这里处理了                applyDefaultViewName(request, mv);                mappedHandler.applyPostHandle(processedRequest, response, mv);            }            catch (Exception ex) {                dispatchException = ex;            }            processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);        }        catch (Exception ex) {            triggerAfterCompletion(processedRequest, response, mappedHandler, ex);        }        catch (Error err) {            triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);        }        finally {            if (asyncManager.isConcurrentHandlingStarted()) {                // Instead of postHandle and afterCompletion                mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);                return;            }            // Clean up any resources used by a multipart request.            if (multipartRequestParsed) {                cleanupMultipart(processedRequest);            }        }    }

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

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

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

 

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

   下载的。我这里用的是4.0.0的版本。最后在总结一下吧。

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

创建一个ModelAndView对象。

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

上一篇:SpringMvc HttpMessageConverter之@ResponseBody
下一篇:SpringMvc拦截器运行原理。

发表评论

最新留言

留言是一种美德,欢迎回访!
[***.207.175.100]2025年04月21日 01时47分29秒