美文网首页
SpringMVC源码关于视图解析渲染

SpringMVC源码关于视图解析渲染

作者: 代码potty | 来源:发表于2018-09-08 11:59 被阅读0次

之前我们完成了springmvc各组件的初始化,然后进入doDispatch()的方法,通过handlerMappings迭代出order优先级最高的handlerMapping,然后调用handlerMapping的gethandler()方法获取handlerExcutionChain对象,接着我们通过getHandlerAdapter()方法,传入handler作为参数,获得匹配的handlerAdapter,获取到handlerExcutionChain和handlerAdapter后,我们调用适配器的handler(req,res,handler)的方式获取ModelAndView对象,这是前几天完成的部分。以下引用链接https://blog.csdn.net/wangbiao007/article/details/50510274 来展示整个流程

image.png

通过ModelAndView我们获取到了model和viewName,程序接着往下走,到方法applyDefaultViewName(processedRequest, mv)的调用,这个方法内部如下:

    private void applyDefaultViewName(HttpServletRequest request, ModelAndView mv) throws Exception {
            if (mv != null && !mv.hasView()) {
                mv.setViewName(getDefaultViewName(request));
            }
        }

如果先前我们获取到mv,并且mv是有viewName的,那么这个方法没有用,因为这个方法就是在我们有mv但是没有viewName的情况下,给我们设置一个默认的viewName

程序接着往下走,到方法mappedHandler.applyPostHandle(processedRequest, response, mv)的调用,这个内部如下:

    void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) throws Exception {
            HandlerInterceptor[] interceptors = getInterceptors();
            if (!ObjectUtils.isEmpty(interceptors)) {
                for (int i = interceptors.length - 1; i >= 0; i--) {
                    HandlerInterceptor interceptor = interceptors[i];
                    interceptor.postHandle(request, response, this.handler, mv);
                }
            }
        }

获取到项目的拦截器,然后判断拦截器是否存在,存在则对该请求进行测试(postHandler),如果有一个拦截器不成功,则请求失败。

程序接着往下走,到本次最重要的视图解析和渲染部分,也就是调用方法processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException)
该方法的代码如下:

    private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
                HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {
    
            boolean errorView = false;
    
            if (exception != null) {
                if (exception instanceof ModelAndViewDefiningException) {
                    logger.debug("ModelAndViewDefiningException encountered", exception);
                    mv = ((ModelAndViewDefiningException) exception).getModelAndView();
                }
                else {
                    Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
                    mv = processHandlerException(request, response, handler, exception);
                    errorView = (mv != null);
                }
            }
    
            // Did the handler return a view to render?
            if (mv != null && !mv.wasCleared()) {
                render(mv, request, response);
                if (errorView) {
                    WebUtils.clearErrorRequestAttributes(request);
                }
            }
            else {
                if (logger.isDebugEnabled()) {
                    logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +
                            "': assuming HandlerAdapter completed request handling");
                }
            }
    
            if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
                // Concurrent handling started during a forward
                return;
            }
    
            if (mappedHandler != null) {
                mappedHandler.triggerAfterCompletion(request, response, null);
            }
        }

首先判断一下是否异常,如果出现异常,判断异常是否是ModelAndViewDefiningException的实例,如果是就直接调用getModelAndView方法返回一个mv,否则调用processHanlderException方法初始化mv对象

如果没有异常出现,那么判断mv是否为空,为空抛出异常,不为空则判断mv,wasCleared()方法返回的值是否为真,我们来看下这个方法的实现:

    public boolean wasCleared() {
            return (this.cleared && isEmpty());
        }

首先这个方法存在于ModelAndView类中,cleared的默认值设置为false,如果执行了clear()方法,则cleared的值置为true,接着是isEmpty()方法判断:

  public boolean isEmpty() {
    return (this.view == null && CollectionUtils.isEmpty(this.model));
}

判断完成后,调用DispatcherServlet的render(mv,req,res)方法,代码如下:

    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 {
                if (mv.getStatus() != null) {
                    response.setStatus(mv.getStatus().value());
                }
                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;
            }
        }

首先介绍一下Locale对象,设置整个系统的运行语言,然后根据系统的运行语言来展示对应语言的页面,平时我们总会出现乱码的情况,那么可以看下response的语言类型到底是否是我们设置的情况。

这个方法中有两部分,第一部分是解析视图,在if(mv.isReference()){}里头,第二部分是视图渲染,在try{}catch{}代码块中的,view.render(mv.getModelInternal(),req,res)
首先介绍一下第一部分解析视图,mv.isReference()是判断viewName是否存在,存在,则通过resolveViewName(mv.getViewName,mv.getModelInternal,locale,request)方法进行解析,该方法的代码如下:

    protected View resolveViewName(String viewName, Map<String, Object> model, Locale locale,
                HttpServletRequest request) throws Exception {
    
            for (ViewResolver viewResolver : this.viewResolvers) {
                View view = viewResolver.resolveViewName(viewName, locale);
                if (view != null) {
                    return view;
                }
            }
            return null;
        }       

迭代viewResolverss对象,然后调用viewResolver的resolverViewName(viewName,locale)方法,springMVC给我们提供了多种的resolverViewName的方法实现


image.png

通过这些实现来返回我们的view对象。

如果之前判断isReference为false,则直接调用view.getView的方法获取view。

解析成功view之后,就是执行渲染操作了,渲染操作调用刚刚解析获得的view的render方法,该方法的内部实现如下:

    public void render(Map<String, ?> 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);
            }
    
            Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
            prepareResponse(request, response);
            renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
        }

首先先获取到需要加入到request的model的map集合,调用方法createMergedOutputModel方法,然后调用prepareResponse(request,response),该方法是设置响应请求头的两个参数,代码如下:

    protected void prepareResponse(HttpServletRequest request, HttpServletResponse response) {
            if (generatesDownloadContent()) {
                response.setHeader("Pragma", "private");
                response.setHeader("Cache-Control", "private, must-revalidate");
            }
        }       

最后一步就是调用renderMergedOutputModel()方法去将mergeModel合到请求里,该方法的代码实现如下:

    protected void renderMergedOutputModel(
                Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
    
            // Expose the model object as request attributes.
            exposeModelAsRequestAttributes(model, request);
    
            // Expose helpers as request attributes, if any.
            exposeHelpers(request);
    
            // Determine the path for the request dispatcher.
            String dispatcherPath = prepareForRendering(request, response);
    
            // Obtain a RequestDispatcher for the target resource (typically a JSP).
            RequestDispatcher rd = getRequestDispatcher(request, 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(request, response)) {
                response.setContentType(getContentType());
                if (logger.isDebugEnabled()) {
                    logger.debug("Including resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
                }
                rd.include(request, 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(request, response);
            }
        }

前面四行都是配置requestDispatcher用的,我主要看了下方法exposeModelAsRequestAttributes(),代码如下:

    protected void exposeModelAsRequestAttributes(Map<String, Object> model, HttpServletRequest request) throws Exception {
            for (Map.Entry<String, Object> entry : model.entrySet()) {
                String modelName = entry.getKey();
                Object modelValue = entry.getValue();
                if (modelValue != null) {
                    request.setAttribute(modelName, modelValue);
                    if (logger.isDebugEnabled()) {
                        logger.debug("Added model object '" + modelName + "' of type [" + modelValue.getClass().getName() +
                                "] to request in view with name '" + getBeanName() + "'");
                    }
                }
                else {
                    request.removeAttribute(modelName);
                    if (logger.isDebugEnabled()) {
                        logger.debug("Removed model object '" + modelName +
                                "' from request in view with name '" + getBeanName() + "'");
                    }
                }
            }
        }

发现了什么,我们不使用框架的时候,如果想要将model传到前端,就是需要使用request.setAttribute()的方法,也就是这个方法封装了我们对于model到前端的操作。

RequestDispatcher 对象配置完成后,就是选择它要进行的操作了,是使用include还是forward进行跳转。至此,视图的解析与渲染工作都完成了,并且也发送到了前端页面,完成了一个请求的整个流程。

总结
在获取到ModelAndView对象后,先判断在mv存在的情况下viewName是否存在,不存在就给定一个初始值,之后对请求进行拦截判断,如果有一个不通过则请求失败,都通过则进行下一步,调用DispatcherServlet.render()方法进行视图的解析和渲染,调用resolverViewName()方法迭代viewResolvers,如果viewResolver.resolveViewName(viewName, locale)的返回结果不为空,则返回该结果,解析成功。
然后再进行视图的渲染工作,通过前面获取到的view,调用该对象的render(model,req,res)方法,方法内先获取该请求的Controller方法中设置的model,然后设置响应请求头,最后调用renderMergedOutputModel将model放到request中通过requestDispatcher.include(req,res)或者requestDispatcher.forward(req,res)的方式传到前端,展现给用户。

参考链接:
https://www.cnblogs.com/solverpeng/p/5743609.html
https://blog.csdn.net/qq924862077/article/details/52878507
https://www.jianshu.com/p/5f91b6d7591a
https://blog.csdn.net/prince2270/article/details/5891085

相关文章

网友评论

      本文标题:SpringMVC源码关于视图解析渲染

      本文链接:https://www.haomeiwen.com/subject/qdukgftx.html