springmvc 之视图解析器源码分析

springmvc在处理器方法中通常返回的是逻辑视图,如何定位到真正的页面,就需要通过视图解析器。
springmvc里提供了多个视图解析器,InternalResourceViewResolver就是其中之一:

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/PAGE/" />
        <property name="suffix" value=".jsp" />
    </bean>

现在源码分析什么时候用到视图解析器

拿doGet方法举例子
FrameworkServlet类

protected final void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        //省略
            doService(request, response);
        //省略
    }

DispatcherServlet 类

protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
            //省略
            doDispatch(request, response);
            //省略
    }
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        //省略
            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);
                //如果自己配置了自定义的HandlerExceptionResolver将会在这个方法里处理 这个放到下一篇说吧
                mv = processHandlerException(request, response, handler, exception);
                errorView = (mv != null);
            }
        }


        if (mv != null && !mv.wasCleared()) {
        // 解析视图并进行视图的渲染
            render(mv, request, response);
            if (errorView) {
                WebUtils.clearErrorRequestAttributes(request);
            }
        }
        //省略
    }
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
        //省略
            // 由ViewResolver解析View
            view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);
            //省略
            //视图在渲染时会把Model传入
            view.render(mv.getModelInternal(), request, response);
    }

循环遍历你配置的视图解析器,viewResolvers是进过order排序的

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;
    }

我们这里是InternalResourceViewResolver继承了AbstractCachingViewResolver
该方法首先会判断有没有缓存,要是有缓存,它会先去缓存中通过viewName查找是否有View对象的存在,要是没有,它会通过viewName创建一个新的View对象,并将View对象存入缓存中,这样再次遇到同样的视图名的时候就可以直接在缓存中取出View对象了

@Override
    public View resolveViewName(String viewName, Locale locale) throws Exception {
        if (!isCache()) {
            return createView(viewName, locale);
        }
        else {
            Object cacheKey = getCacheKey(viewName, locale);
            View view = this.viewAccessCache.get(cacheKey);
            if (view == null) {
                synchronized (this.viewCreationCache) {
                    view = this.viewCreationCache.get(cacheKey);
                    if (view == null) {
                        // Ask the subclass to create the View object.
                        view = createView(viewName, locale);
                        if (view == null && this.cacheUnresolved) {
                            view = UNRESOLVED_VIEW;
                        }
                        if (view != null) {
                            this.viewAccessCache.put(cacheKey, view);
                            this.viewCreationCache.put(cacheKey, view);
                            if (logger.isTraceEnabled()) {
                                logger.trace("Cached view [" + cacheKey + "]");
                            }
                        }
                    }
                }
            }
            return (view != UNRESOLVED_VIEW ? view : null);
        }
    }

AbstractCachingViewResolver

protected View createView(String viewName, Locale locale) throws Exception {
        return loadView(viewName, locale);
    }

InternalResourceViewResolver继承了UrlBasedViewResolver
UrlBasedViewResolver

protected View loadView(String viewName, Locale locale) throws Exception {
        AbstractUrlBasedView view = buildView(viewName);
        View result = applyLifecycleMethods(viewName, view);
        return (view.checkResource(locale) ? result : null);
    }

UrlBasedViewResolver的buildView方法会获取一个View对象,这个对象会将视图以什么格式呈现给用户,例如如果是jsp显示呈现给用户的话,那这个view对象就是JstlView,默认的是JstlView。在这个方法中我们看到了getPrefix() + viewName + getSuffix()这样一段代码,这就是对视图路径的一个拼接了,getPrefix()方法获取前缀,也就是我们在配置文件中配置的 <property name="prefix" value="/WEB-INF/PAGE/"/> 的value中的值了,getSuffix()方法就是获取后缀值了,也就是我们在配置文件中配置的<property name="suffix" value=".jsp"/> 的value中的值。这样就将将视图的物理路径找到了,并赋值到View的URL属性中去。

protected AbstractUrlBasedView buildView(String viewName) throws Exception {
        AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(getViewClass());
        view.setUrl(getPrefix() + viewName + getSuffix());

        String contentType = getContentType();
        if (contentType != null) {
            view.setContentType(contentType);
        }

        view.setRequestContextAttribute(getRequestContextAttribute());
        view.setAttributesMap(getAttributesMap());

        Boolean exposePathVariables = getExposePathVariables();
        if (exposePathVariables != null) {
            view.setExposePathVariables(exposePathVariables);
        }
        Boolean exposeContextBeansAsAttributes = getExposeContextBeansAsAttributes();
        if (exposeContextBeansAsAttributes != null) {
            view.setExposeContextBeansAsAttributes(exposeContextBeansAsAttributes);
        }
        String[] exposedContextBeanNames = getExposedContextBeanNames();
        if (exposedContextBeanNames != null) {
            view.setExposedContextBeanNames(exposedContextBeanNames);
        }

        return view;
    }

这里写图片描述

就这样我们得到了一个View对象,这个视图的name就是逻辑视图名,因为当将View对象放在缓存的时候,我们可以通过逻辑视图名在缓存中找出View对象。我们在获取到View对象的时候,我们还要将View进行渲染,并呈现给用户。我们再来看看View中的render方法。

扫描二维码关注公众号,回复: 1471678 查看本文章

View是个接口,View由AbstractView实现。AbstractView中的render方法

@Override
    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中
        Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
        //主要是对response头进行了一些属性设置
        prepareResponse(request, response);
        renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
    }

renderMergedOutputModel方法由AbstractView的孙子类InternalResourceView实现
InternalResourceView的renderMergedOutputModel方法
我们获取到视图的物理路径,然后将这段路径传给RequestDispatcher对象,再调用RequestDispatcher的forward方法将页面呈现给用户,这样就走完了视图的解析了。

@Override
    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);
        }
    }

这里写图片描述

猜你喜欢

转载自blog.csdn.net/hepei120/article/details/78278948