springmvc 之视图解析器源码分析

来源:互联网 发布:mysql-5.5.57安装 编辑:程序博客网 时间:2024/05/18 15:56

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方法。

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

这里写图片描述

原创粉丝点击