首页 > 代码库 > spring mvc DispatcherServlet详解之三---request通过ModelAndView中获取View实例的过程

spring mvc DispatcherServlet详解之三---request通过ModelAndView中获取View实例的过程

整个spring mvc的架构如下图所示:

上篇文件讲解了DispatcherServlet第二步:通过request从Controller获取ModelAndView。现在来讲解第三步:request 从ModelAndView中获取view对象。

获取view对象一般是通过viewResolver来解析view name来完成的。若ModelAndView中view 不存在或者ModelAndView本身为null则填充默认值。代码如下:

ModelAndView中view 不存在或者ModelAndView本身为null

DispatcherServlet doService--->doDispatcher-->applyDefaultViewName(request, mv);

    /**     * Do we need view name translation?     */    private void applyDefaultViewName(HttpServletRequest request, ModelAndView mv) throws Exception {        if (mv != null && !mv.hasView()) {            mv.setViewName(getDefaultViewName(request));        }    }

从request中解析出默认view name

    /**     * Translate the supplied request into a default view name.     * @param request current HTTP servlet request     * @return the view name (or {@code null} if no default found)     * @throws Exception if view name translation failed     */    protected String getDefaultViewName(HttpServletRequest request) throws Exception {        return this.viewNameTranslator.getViewName(request);    }

我们先了解一下这个viewNameTranslator是怎么得到的吧?从容器中获取bean,bean 名称为:DefaultRequestToViewNameTranslator

    /**     * Initialize the strategy objects that this servlet uses.     * <p>May be overridden in subclasses in order to initialize further strategy objects.     */    protected void initStrategies(ApplicationContext context) {        initMultipartResolver(context);        initLocaleResolver(context);        initThemeResolver(context);        initHandlerMappings(context);        initHandlerAdapters(context);        initHandlerExceptionResolvers(context);        initRequestToViewNameTranslator(context);        initViewResolvers(context);        initFlashMapManager(context);    }     * Initialize the RequestToViewNameTranslator used by this servlet instance.     * <p>If no implementation is configured then we default to DefaultRequestToViewNameTranslator.     */    private void initRequestToViewNameTranslator(ApplicationContext context) {        try {            this.viewNameTranslator =                    context.getBean(REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME, RequestToViewNameTranslator.class);            if (logger.isDebugEnabled()) {                logger.debug("Using RequestToViewNameTranslator [" + this.viewNameTranslator + "]");            }        }        catch (NoSuchBeanDefinitionException ex) {            // We need to use the default.            this.viewNameTranslator = getDefaultStrategy(context, RequestToViewNameTranslator.class);            if (logger.isDebugEnabled()) {                logger.debug("Unable to locate RequestToViewNameTranslator with name ‘" +                        REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME + "‘: using default [" + this.viewNameTranslator +                        "]");            }        }    }

获取默认view name的真正实现方法如下:

/**     * Translates the request URI of the incoming {@link HttpServletRequest}     * into the view name based on the configured parameters.     * @see org.springframework.web.util.UrlPathHelper#getLookupPathForRequest     * @see #transformPath     */    @Override    public String getViewName(HttpServletRequest request) {        String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);        return (this.prefix + transformPath(lookupPath) + this.suffix);    }    /**     * Transform the request URI (in the context of the webapp) stripping     * slashes and extensions, and replacing the separator as required.     * @param lookupPath the lookup path for the current request,     * as determined by the UrlPathHelper     * @return the transformed path, with slashes and extensions stripped     * if desired     */    protected String transformPath(String lookupPath) {        String path = lookupPath;        if (this.stripLeadingSlash && path.startsWith(SLASH)) {            path = path.substring(1);        }        if (this.stripTrailingSlash && path.endsWith(SLASH)) {            path = path.substring(0, path.length() - 1);        }        if (this.stripExtension) {            path = StringUtils.stripFilenameExtension(path);        }        if (!SLASH.equals(this.separator)) {            path = StringUtils.replace(path, SLASH, this.separator);        }        return path;    }

完整实现如下:

/**     * Return the mapping lookup path for the given request, within the current     * servlet mapping if applicable, else within the web application.     * <p>Detects include request URL if called within a RequestDispatcher include.     * @param request current HTTP request     * @return the lookup path     * @see #getPathWithinApplication     * @see #getPathWithinServletMapping     */    public String getLookupPathForRequest(HttpServletRequest request) {        // Always use full path within current servlet context?        if (this.alwaysUseFullPath) {            return getPathWithinApplication(request);        }        // Else, use path within current servlet mapping if applicable        String rest = getPathWithinServletMapping(request);        if (!"".equals(rest)) {            return rest;        }        else {            return getPathWithinApplication(request);        }    }    /**     * Return the path within the servlet mapping for the given request,     * i.e. the part of the request‘s URL beyond the part that called the servlet,     * or "" if the whole URL has been used to identify the servlet.     * <p>Detects include request URL if called within a RequestDispatcher include.     * <p>E.g.: servlet mapping = "/test/*"; request URI = "/test/a" -> "/a".     * <p>E.g.: servlet mapping = "/test"; request URI = "/test" -> "".     * <p>E.g.: servlet mapping = "/*.test"; request URI = "/a.test" -> "".     * @param request current HTTP request     * @return the path within the servlet mapping, or ""     */    public String getPathWithinServletMapping(HttpServletRequest request) {        String pathWithinApp = getPathWithinApplication(request);        String servletPath = getServletPath(request);        String path = getRemainingPath(pathWithinApp, servletPath, false);        if (path != null) {            // Normal case: URI contains servlet path.            return path;        }        else {            // Special case: URI is different from servlet path.            String pathInfo = request.getPathInfo();            if (pathInfo != null) {                // Use path info if available. Indicates index page within a servlet mapping?                // e.g. with index page: URI="/", servletPath="/index.html"                return pathInfo;            }            if (!this.urlDecode) {                // No path info... (not mapped by prefix, nor by extension, nor "/*")                // For the default servlet mapping (i.e. "/"), urlDecode=false can                // cause issues since getServletPath() returns a decoded path.                // If decoding pathWithinApp yields a match just use pathWithinApp.                path = getRemainingPath(decodeInternal(request, pathWithinApp), servletPath, false);                if (path != null) {                    return pathWithinApp;                }            }            // Otherwise, use the full servlet path.            return servletPath;        }    }    /**     * Return the path within the web application for the given request.     * <p>Detects include request URL if called within a RequestDispatcher include.     * @param request current HTTP request     * @return the path within the web application     */    public String getPathWithinApplication(HttpServletRequest request) {        String contextPath = getContextPath(request);        String requestUri = getRequestUri(request);        String path = getRemainingPath(requestUri, contextPath, true);        if (path != null) {            // Normal case: URI contains context path.            return (StringUtils.hasText(path) ? path : "/");        }        else {            return requestUri;        }    }    /**     * Match the given "mapping" to the start of the "requestUri" and if there     * is a match return the extra part. This method is needed because the     * context path and the servlet path returned by the HttpServletRequest are     * stripped of semicolon content unlike the requesUri.     */    private String getRemainingPath(String requestUri, String mapping, boolean ignoreCase) {        int index1 = 0;        int index2 = 0;        for (; (index1 < requestUri.length()) && (index2 < mapping.length()); index1++, index2++) {            char c1 = requestUri.charAt(index1);            char c2 = mapping.charAt(index2);            if (c1 == ‘;‘) {                index1 = requestUri.indexOf(‘/‘, index1);                if (index1 == -1) {                    return null;                }                c1 = requestUri.charAt(index1);            }            if (c1 == c2) {                continue;            }            if (ignoreCase && (Character.toLowerCase(c1) == Character.toLowerCase(c2))) {                continue;            }            return null;        }        if (index2 != mapping.length()) {            return null;        }        if (index1 == requestUri.length()) {            return "";        }        else if (requestUri.charAt(index1) == ‘;‘) {            index1 = requestUri.indexOf(‘/‘, index1);        }        return (index1 != -1 ? requestUri.substring(index1) : "");    }

ModelAndView中view 定义为view name,解析为view实例。

DispatcherServlet doService--->doDispatcher-->processDispatchResult--->render--->resolveViewName

    /**     * Resolve the given view name into a View object (to be rendered).     * <p>The default implementations asks all ViewResolvers of this dispatcher.     * Can be overridden for custom resolution strategies, potentially based on     * specific model attributes or request parameters.     * @param viewName the name of the view to resolve     * @param model the model to be passed to the view     * @param locale the current locale     * @param request current HTTP servlet request     * @return the View object, or {@code null} if none found     * @throws Exception if the view cannot be resolved     * (typically in case of problems creating an actual View object)     * @see ViewResolver#resolveViewName     */    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是具体实现:

    @Override    protected AbstractUrlBasedView buildView(String viewName) throws Exception {        InternalResourceView view = (InternalResourceView) super.buildView(viewName);        if (this.alwaysInclude != null) {            view.setAlwaysInclude(this.alwaysInclude);        }        if (this.exposeContextBeansAsAttributes != null) {            view.setExposeContextBeansAsAttributes(this.exposeContextBeansAsAttributes);        }        if (this.exposedContextBeanNames != null) {            view.setExposedContextBeanNames(this.exposedContextBeanNames);        }        view.setPreventDispatchLoop(true);        return view;    }

/**
* Creates a new View instance of the specified view class and configures it.
* Does <i>not</i> perform any lookup for pre-defined View instances.
* <p>Spring lifecycle methods as defined by the bean container do not have to
* be called here; those will be applied by the {@code loadView} method
* after this method returns.
* <p>Subclasses will typically call {@code super.buildView(viewName)}
* first, before setting further properties themselves. {@code loadView}
* will then apply Spring lifecycle methods at the end of this process.
* @param viewName the name of the view to build
* @return the View instance
* @throws Exception if the view couldn‘t be resolved
* @see #loadView(String, java.util.Locale)
*/
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);
}

return view;
}

 

ModelAndView 本身存在view实例

            // No need to lookup: the ModelAndView object contains the actual View object.            view = mv.getView();

不需要查询,使用getView方法即可获取到。

小结:

   本文主要讲解request怎么从ModelAndView中获取view实例的,分三种情况:mv本身为空或者mv中view为空时,使用默认的view name;mv本身中view 为string 表示viewname而非view实例,那么根据view name使用viewResolver转换成view实例;mv本身有view实例则使用getview方法获取。

 

spring mvc DispatcherServlet详解之三---request通过ModelAndView中获取View实例的过程