0 前言

学Java开发的同学始终避不开Spring,学JavaWeb开发的同学就更避不开SpringMVC了。凡是有点追求的程序员肯定也想读一读Spring和SpringMVC的源码,我也不例外,做JavaWeb开发有两年多了,平时开发中也有debug进SpringMVC源码,但始终没有对SpringMVC源码有一个整体的、全面的了解。这次,我将带着读者一览SpringMVC,将它的核心流程和边边角角尽量清楚地展示给大家,为什么起名SpringMVC 解毒,其含义是:在这里,一次的、全面的了解SpringMVC,然后似庖丁解牛般使用SpringMVC而设计你的业务。我阅读的源码是基于4.3.0.RELEASE版本,读者如果想看到同样的代码和效果,也请采用该版本。

Spring是应用于企业级Java程序开发的框架,它提供了控制反转(IOC)、面向切面编程、对象生命周期管理等一系列特性。而SpringMVC是基于Spring的JavaWeb开发框架,可以这么说,只使用Spring核心框架也可以进行web开发,但SpringMVC可以让你把精神集中在业务代码开发,而不是各种协议异常处理。

以下是我在阅读源码时整理的类图关系。

=================此处应有图,然而图太大了,根本看不清=============

这张图实际上并没有展示SpringMVC框架中所有类,只有常用的一些。刚看到这张图谁都会懵,但是我想当你阅读完所有文章后,回头再看这张图,也会会心一笑。解读框架的逻辑是先从主要入口类着手,然后针对入口类中使用到的的每个接口分别再详细展开,在接口实现类中如又遇到接口,之后再会对接口详细展开。这一系列文章不会对Spring的bean管理着墨太多,如果想顺利地阅读,我希望你有这些基础:Servlet规范,SpringBeanDefinition解析,SpringBean容器初始化等,如果没有的话,你可能阅读起来很吃力,不过可以参考我写的其他文章。

1 MVC 简介

MVC 是一种设计模式,它能够让项目组织架构变得比较合理,适合阅读与理解。

JavaWeb开发自Servlet规范出现,最开始是直接继承Servlet进行开发,需要向用户返回数据时,直接向outputstream写字符串。然后出现了JSP技术,以前是在Java代码里写html,JSP就是在html里写java,JSP定义了一系列的标签,允许在html代码里加入Java代码,运行时再编译成Servlet再运行。也就是说你写的Servlet项目只需要容器能够解析Servlet,而如果写的JSP,则需要容器能够编译JSP。如果业务URI较多,一般会有多个Servlet或JSP,显然,这两种开发方式对快速业务开发不合适。

SpringMVC是怎样解决上述问题的呢?SpringMVC提供了一个统一的Servlet实现类来接管用户的Servlet,然后允许使用者通过配置的方式指定URI与具体实现方法之间的关系。使用者只管写Java代码,而不需要管http字符流如何解析为方法入参,返回值如何解析为http字符流。

2 DispatcherServlet

DispatcherServlet 是前一章中提到的前置控制器,接管了之前需要用户写的Servlet。要了解 DispatcherServlet 就要了解它的父类。我们就从 HttpServletBean 开始吧。’

2.1 HttpServletBean分析

HttpServletBean 继承了 javax.servlet.http.HttpServlet ,这里就不详述了。我们都知道,Servlet的生命周期自init方法,期间通过service方法响应请求,而在程序关闭时会调用destroy方法,在 HttpServletBean 中,他实现了init方法。

/**
 * Map config parameters onto bean properties of this servlet, and
 * invoke subclass initialization.
 * @throws ServletException if bean properties are invalid (or required
 * properties are missing), or if subclass initialization fails.
 */
@Override
public final void init() throws ServletException {
    if (logger.isDebugEnabled()) {
        logger.debug("Initializing servlet '" + getServletName() + "'");
    }

    // Set bean properties from init parameters.
    try {
        PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
        BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
        ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
        bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
        initBeanWrapper(bw);
        bw.setPropertyValues(pvs, true);
    }
    catch (BeansException ex) {
        logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
        throw ex;
    }

    // Let subclasses do whatever initialization they like.
    initServletBean();

    if (logger.isDebugEnabled()) {
        logger.debug("Servlet '" + getServletName() + "' configured successfully");
    }
}

/**
 * Subclasses may override this to perform custom initialization.
 * All bean properties of this servlet will have been set before this
 * method is invoked.
 * <p>This default implementation is empty.
 * @throws ServletException if subclass initialization fails
 */
protected void initServletBean() throws ServletException {
}

在这个init方法中,做了一些资源处理,最重要的是给我们留下了一个空方法initServletBean,看这个名字就知道应该是要在这个方法里初始化ServletBean的。

2.2 FrameworkServlet分析

2.2.1 FrameworkServlet初始化分析

看完HttpServletBean,接下来就该它的子类 FrameworkServlet了。果不其然,果真实现了initServletBean方法。

/**
 * Overridden method of {@link HttpServletBean}, invoked after any bean properties
 * have been set. Creates this servlet's WebApplicationContext.
 */
@Override
protected final void initServletBean() throws ServletException {
    getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
    if (this.logger.isInfoEnabled()) {
        this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
    }
    long startTime = System.currentTimeMillis();

    try {
        this.webApplicationContext = initWebApplicationContext();
        initFrameworkServlet();
    }
    catch (ServletException ex) {
        this.logger.error("Context initialization failed", ex);
        throw ex;
    }
    catch (RuntimeException ex) {
        this.logger.error("Context initialization failed", ex);
        throw ex;
    }

    if (this.logger.isInfoEnabled()) {
        long elapsedTime = System.currentTimeMillis() - startTime;
        this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
                elapsedTime + " ms");
    }
}

饭要慢慢吃,这个类代码虽多,但是不要着急。这个实现方法做了两件事,一个是初始化web应用上下文和初始化框架servlet,另一个是统计初始化时间。

先看初始化web应用上下文吧。

/**
 * Initialize and publish the WebApplicationContext for this servlet.
 * <p>Delegates to {@link #createWebApplicationContext} for actual creation
 * of the context. Can be overridden in subclasses.
 * @return the WebApplicationContext instance
 * @see #FrameworkServlet(WebApplicationContext)
 * @see #setContextClass
 * @see #setContextConfigLocation
 */
protected WebApplicationContext initWebApplicationContext() {
    WebApplicationContext rootContext =
            WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    WebApplicationContext wac = null;

    if (this.webApplicationContext != null) {
        // A context instance was injected at construction time -> use it
        wac = this.webApplicationContext;
        if (wac instanceof ConfigurableWebApplicationContext) {
            ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
            if (!cwac.isActive()) {
                // The context has not yet been refreshed -> provide services such as
                // setting the parent context, setting the application context id, etc
                if (cwac.getParent() == null) {
                    // The context instance was injected without an explicit parent -> set
                    // the root application context (if any; may be null) as the parent
                    cwac.setParent(rootContext);
                }
                configureAndRefreshWebApplicationContext(cwac);
            }
        }
    }
    if (wac == null) {
        // No context instance was injected at construction time -> see if one
        // has been registered in the servlet context. If one exists, it is assumed
        // that the parent context (if any) has already been set and that the
        // user has performed any initialization such as setting the context id
        wac = findWebApplicationContext();
    }
    if (wac == null) {
        // No context instance is defined for this servlet -> create a local one
        wac = createWebApplicationContext(rootContext);
    }

    if (!this.refreshEventReceived) {
        // Either the context is not a ConfigurableApplicationContext with refresh
        // support or the context injected at construction time had already been
        // refreshed -> trigger initial onRefresh manually here.
        onRefresh(wac);
    }

    if (this.publishContext) {
        // Publish the context as a servlet context attribute.
        String attrName = getServletContextAttributeName();
        getServletContext().setAttribute(attrName, wac);
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
                    "' as ServletContext attribute with name [" + attrName + "]");
        }
    }

    return wac;
}

/**
 * Template method which can be overridden to add servlet-specific refresh work.
 * Called after successful context refresh.
 * <p>This implementation is empty.
 * @param context the current WebApplicationContext
 * @see #refresh()
 */
protected void onRefresh(ApplicationContext context) {
    // For subclasses: do nothing by default.
}

在这个方法中,程序先会获取rootContext,rootContext在什么时候会有呢?在你在web.xml中设置了<context-param>标签。什么意思呢?当你有多个Servlet,需要在多个Servlet中间公用一些对象或者数据,就需要设置公共的上下文。公共的上下文也就是在这里被加载到单个Servlet的。

接下来判断对象中的web应用上下文是否已经有了,如果有了,说明是通过别的方式注入进来的,这时候需要给web应用上下文设置父上下文,然后重新初始化web应用上下文。不过这里就不用细究了,一般情况下web应用上下文不会被提前注入。

接下来尝试从容器上下文中获取web应用上下文,一般情况下,也不会从这里创建成功的。

接下来才是最中心的,创建 XmlWebApplicationContext 上下文,xml配置文件虽写在web.xml中,其实也就在这里被用到,用于生成上下文。想看源码吗?其实也没什么,就和在Main方法创建上下文一样。

/**
 * Instantiate the WebApplicationContext for this servlet, either a default
 * {@link org.springframework.web.context.support.XmlWebApplicationContext}
 * or a {@link #setContextClass custom context class}, if set.
 * <p>This implementation expects custom contexts to implement the
 * {@link org.springframework.web.context.ConfigurableWebApplicationContext}
 * interface. Can be overridden in subclasses.
 * <p>Do not forget to register this servlet instance as application listener on the
 * created context (for triggering its {@link #onRefresh callback}, and to call
 * {@link org.springframework.context.ConfigurableApplicationContext#refresh()}
 * before returning the context instance.
 * @param parent the parent ApplicationContext to use, or {@code null} if none
 * @return the WebApplicationContext for this servlet
 * @see org.springframework.web.context.support.XmlWebApplicationContext
 */
protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
    Class<?> contextClass = getContextClass();
    if (this.logger.isDebugEnabled()) {
        this.logger.debug("Servlet with name '" + getServletName() +
                "' will try to create custom WebApplicationContext context of class '" +
                contextClass.getName() + "'" + ", using parent context [" + parent + "]");
    }
    if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
        throw new ApplicationContextException(
                "Fatal initialization error in servlet with name '" + getServletName() +
                "': custom WebApplicationContext class [" + contextClass.getName() +
                "] is not of type ConfigurableWebApplicationContext");
    }
    ConfigurableWebApplicationContext wac =
            (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

    wac.setEnvironment(getEnvironment());
    wac.setParent(parent);
    wac.setConfigLocation(getContextConfigLocation());

    configureAndRefreshWebApplicationContext(wac);

    return wac;
}

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
    if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
        // The application context id is still set to its original default value
        // -> assign a more useful id based on available information
        if (this.contextId != null) {
            wac.setId(this.contextId);
        }
        else {
            // Generate default id...
            wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
                    ObjectUtils.getDisplayString(getServletContext().getContextPath()) + "/" + getServletName());
        }
    }

    wac.setServletContext(getServletContext());
    wac.setServletConfig(getServletConfig());
    wac.setNamespace(getNamespace());
    wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));

    // The wac environment's #initPropertySources will be called in any case when the context
    // is refreshed; do it eagerly here to ensure servlet property sources are in place for
    // use in any post-processing or initialization that occurs below prior to #refresh
    ConfigurableEnvironment env = wac.getEnvironment();
    if (env instanceof ConfigurableWebEnvironment) {
        ((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
    }

    postProcessWebApplicationContext(wac);
    applyInitializers(wac);
    wac.refresh();
}

虽然说一样,其实也不一样,上下文对象是通过反射创建的,然后再注入属性,初始化。为啥要这样?嘿嘿,我想到的原因是可以控制上下文对象初始化的时间/顺序/环绕监听。以后我们也可以这么搞啦。

这里虽然说得很简单,我指的是上下文的创建很简单,但是在SpringMVC框架中设置了很多自定义标签解析器,方便使用者开发,这些我们都放到后边用到的时候再讲。

web应用上下文创建完后,FrameworkServlet 还给留了一个onRefresh方法供子类实现。

2.2.2 FrameworkServlet销毁分析

FrameworkServlet 不仅实现了initServletBean方法,还实现了Servlet的destroy方法。该方法会调用web应用上文的close方法,其作用就是完成web应用上下文对对象生命周期的管理。

/**
 * Close the WebApplicationContext of this servlet.
 * @see org.springframework.context.ConfigurableApplicationContext#close()
 */
@Override
public void destroy() {
    getServletContext().log("Destroying Spring FrameworkServlet '" + getServletName() + "'");
    // Only call close() on WebApplicationContext if locally managed...
    if (this.webApplicationContext instanceof ConfigurableApplicationContext && !this.webApplicationContextInjected) {
        ((ConfigurableApplicationContext) this.webApplicationContext).close();
    }
}

2.2.3 FrameworkServlet处理请求分析

针对HttpServlet抽象类将Servlet中的service方法分装成了各个doMethod方法,FrameworkServlet 又将各个方法集中到了processRequest方法中。

/**
 * Process this request, publishing an event regardless of the outcome.
 * <p>The actual event handling is performed by the abstract
 * {@link #doService} template method.
 */
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {

    long startTime = System.currentTimeMillis();
    Throwable failureCause = null;

    LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
    LocaleContext localeContext = buildLocaleContext(request);

    RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
    ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());

    initContextHolders(request, localeContext, requestAttributes);

    try {
        doService(request, response);
    }
    catch (ServletException ex) {
        failureCause = ex;
        throw ex;
    }
    catch (IOException ex) {
        failureCause = ex;
        throw ex;
    }
    catch (Throwable ex) {
        failureCause = ex;
        throw new NestedServletException("Request processing failed", ex);
    }

    finally {
        resetContextHolders(request, previousLocaleContext, previousAttributes);
        if (requestAttributes != null) {
            requestAttributes.requestCompleted();
        }

        if (logger.isDebugEnabled()) {
            if (failureCause != null) {
                this.logger.debug("Could not complete request", failureCause);
            }
            else {
                if (asyncManager.isConcurrentHandlingStarted()) {
                    logger.debug("Leaving response open for concurrent processing");
                }
                else {
                    this.logger.debug("Successfully completed request");
                }
            }
        }

        publishRequestHandledEvent(request, response, startTime, failureCause);
    }
}


/**
 * Subclasses must implement this method to do the work of request handling,
 * receiving a centralized callback for GET, POST, PUT and DELETE.
 * <p>The contract is essentially the same as that for the commonly overridden
 * {@code doGet} or {@code doPost} methods of HttpServlet.
 * <p>This class intercepts calls to ensure that exception handling and
 * event publication takes place.
 * @param request current HTTP request
 * @param response current HTTP response
 * @throws Exception in case of any kind of processing failure
 * @see javax.servlet.http.HttpServlet#doGet
 * @see javax.servlet.http.HttpServlet#doPost
 */
protected abstract void doService(HttpServletRequest request, HttpServletResponse response)
        throws Exception;

在processRequest方法中,又留下一个doService方法供子类调用。

2.3 DispatcherServlet初始化分析

FrameworkServlet 中,完成了对web应用上下文的创建,并留下了一个onRefresh方法供子类初始化使用。然鹅, FrameworkServlet 就一个子类,它就是DispatcherServlet。留给DispatcherServlet 初始化的入口只有一个那就是onRefresh方法,来看看onRefresh方法吧。

/**
 * This implementation calls {@link #initStrategies}.
 */
@Override
protected void onRefresh(ApplicationContext context) {
    initStrategies(context);
}

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

onRefresh方法又调用了initStrategies方法,在initStrategies方法里又调用了9个初始化方法。初始化的属性我会做一个简单的介绍,在之后章节会分别再具体介绍。接下来我们分别来看看这几个初始化方法。

2.3.1 initMultipartResolver方法

/**
 * Initialize the MultipartResolver used by this class.
 * <p>If no bean is defined with the given name in the BeanFactory for this namespace,
 * no multipart handling is provided.
 */
private void initMultipartResolver(ApplicationContext context) {
    try {
        this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
        if (logger.isDebugEnabled()) {
            logger.debug("Using MultipartResolver [" + this.multipartResolver + "]");
        }
    }
    catch (NoSuchBeanDefinitionException ex) {
        // Default is no multipart resolver.
        this.multipartResolver = null;
        if (logger.isDebugEnabled()) {
            logger.debug("Unable to locate MultipartResolver with name '" + MULTIPART_RESOLVER_BEAN_NAME +
                    "': no multipart request handling provided");
        }
    }
}

其中,常量MULTIPART_RESOLVER_BEAN_NAME值为multipartResolver。

这里我就不卖关子了,这个multipartResolver就是用来解析POST请求中文件部分的。也就是说想设置文件解析器,那这个bean的id必须为multipartResolver。

2.3.2 initLocaleResolver 方法

/**
 * Initialize the LocaleResolver used by this class.
 * <p>If no bean is defined with the given name in the BeanFactory for this namespace,
 * we default to AcceptHeaderLocaleResolver.
 */
private void initLocaleResolver(ApplicationContext context) {
    try {
        this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class);
        if (logger.isDebugEnabled()) {
            logger.debug("Using LocaleResolver [" + this.localeResolver + "]");
        }
    }
    catch (NoSuchBeanDefinitionException ex) {
        // We need to use the default.
        this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);
        if (logger.isDebugEnabled()) {
            logger.debug("Unable to locate LocaleResolver with name '" + LOCALE_RESOLVER_BEAN_NAME +
                    "': using default [" + this.localeResolver + "]");
        }
    }
}

其中,常量LOCALE_RESOLVER_BEAN_NAME值为localeResolver。

这个是初始化用户区域解析器,什么是用户区域Locale?我们知道不同国家有不同语言,我们怎么能知道用户所在国家区域?一般来说浏览器请求时会携带一个header头Accept-Language,标识用户可以接受哪些语言。但是还有这种情况,用户希望在这次浏览中查看别的语言版本,前端也允许用户转变语言,那么就不能只从请求头判断了。

所以用户区域解析器不能只有一种,这里SpringMVC允许开发者自定义一个用户区域解析器。如果你想给SpringMVC注入一个自己的用户区域解析器,那么这个bean的id必须为localeResolver。如果不注入的话,默认使用的是从请求头获取用户区域解析器AcceptHeaderLocaleResolver

2.3.3 initThemeResolver方法

/**
 * Initialize the ThemeResolver used by this class.
 * <p>If no bean is defined with the given name in the BeanFactory for this namespace,
 * we default to a FixedThemeResolver.
 */
private void initThemeResolver(ApplicationContext context) {
    try {
        this.themeResolver = context.getBean(THEME_RESOLVER_BEAN_NAME, ThemeResolver.class);
        if (logger.isDebugEnabled()) {
            logger.debug("Using ThemeResolver [" + this.themeResolver + "]");
        }
    }
    catch (NoSuchBeanDefinitionException ex) {
        // We need to use the default.
        this.themeResolver = getDefaultStrategy(context, ThemeResolver.class);
        if (logger.isDebugEnabled()) {
            logger.debug("Unable to locate ThemeResolver with name '" + THEME_RESOLVER_BEAN_NAME +
                    "': using default [" + this.themeResolver + "]");
        }
    }
}

其中,常量THEME_RESOLVER_BEAN_NAME值为themeResolver。

这是SpringMVC提供的主题解析器,可以这么来理解,用户区域解析器用来解析用户所在国家,主题解析器则是允许用户选择展示页面的样式。具体怎么用,后面章节会讲到的。如果要自定义主题解析器,那么bean的id名必须为themeResolver。如果没有设置,默认为FiexedThemeResolver,其设置的主题名为theme。

2.3.4 initHandlerMappings方法

/**
 * Initialize the HandlerMappings used by this class.
 * <p>If no HandlerMapping beans are defined in the BeanFactory for this namespace,
 * we default to BeanNameUrlHandlerMapping.
 */
private void initHandlerMappings(ApplicationContext context) {
    this.handlerMappings = null;

    if (this.detectAllHandlerMappings) {
        // Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
        Map<String, HandlerMapping> matchingBeans =
                BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
        if (!matchingBeans.isEmpty()) {
            this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
            // We keep HandlerMappings in sorted order.
            AnnotationAwareOrderComparator.sort(this.handlerMappings);
        }
    }
    else {
        try {
            HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
            this.handlerMappings = Collections.singletonList(hm);
        }
        catch (NoSuchBeanDefinitionException ex) {
            // Ignore, we'll add a default HandlerMapping later.
        }
    }

    // Ensure we have at least one HandlerMapping, by registering
    // a default HandlerMapping if no other mappings are found.
    if (this.handlerMappings == null) {
        this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
        if (logger.isDebugEnabled()) {
            logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
        }
    }
}

HandlerMapping我们以后会经常看到。

其中,常量HANDLER_MAPPING_BEAN_NAME值为handlerMapping。

detectAllHandlerMappings 属性决定是否检测所有HandlerMapping,可以在web.xml设置,默认为true。web.xml中的设置方式为:

<init-param>  
    <param-name>detectAllHandlerMappings</param-name>  
    <param-value>false</param-value>  
</init-param>  

在这个方法中,通过detectAllHandlerMappings判断是否要获取全部HandlerMapping,一般情况下,会获取全部。获取全部HandlerMapping时,会从web应用上下文和父上下文中获取所有HandlerMapping。如果不是获取全部,则会只获取id为handlerMapping的bean。如果一个HandlerMapping都没获取到,则会默认创建BeanNameUrlHandlerMappingDefaultAnnotationHandlerMapping

看到这,你会发现怎么老有默认创建的数据,为什么默认是这些呢?别着急,等讲完这些初始化方法,再来看看默认数据。

2.3.5 initHandlerAdapters方法

/**
 * Initialize the HandlerAdapters used by this class.
 * <p>If no HandlerAdapter beans are defined in the BeanFactory for this namespace,
 * we default to SimpleControllerHandlerAdapter.
 */
private void initHandlerAdapters(ApplicationContext context) {
    this.handlerAdapters = null;

    if (this.detectAllHandlerAdapters) {
        // Find all HandlerAdapters in the ApplicationContext, including ancestor contexts.
        Map<String, HandlerAdapter> matchingBeans =
                BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
        if (!matchingBeans.isEmpty()) {
            this.handlerAdapters = new ArrayList<HandlerAdapter>(matchingBeans.values());
            // We keep HandlerAdapters in sorted order.
            AnnotationAwareOrderComparator.sort(this.handlerAdapters);
        }
    }
    else {
        try {
            HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class);
            this.handlerAdapters = Collections.singletonList(ha);
        }
        catch (NoSuchBeanDefinitionException ex) {
            // Ignore, we'll add a default HandlerAdapter later.
        }
    }

    // Ensure we have at least some HandlerAdapters, by registering
    // default HandlerAdapters if no other adapters are found.
    if (this.handlerAdapters == null) {
        this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);
        if (logger.isDebugEnabled()) {
            logger.debug("No HandlerAdapters found in servlet '" + getServletName() + "': using default");
        }
    }
}

HandlerAdapter我们以后也会经常看到,它的初始化和HandlerMapping类似。

其中,常量HANDLER_ADAPTER_BEAN_NAME值为handlerAdapter。

detectAllHandlerAdapters 属性决定是否检测所有HandlerAdapter,可以在web.xml设置,默认为true。web.xml中的设置方式为:

<init-param>  
    <param-name>detectAllHandlerAdapters</param-name>  
    <param-value>false</param-value>  
</init-param>  

在这个方法中,会判断是否要获取全部HandlerAdapter,一般情况下,是全部获取。获取全部HandlerAdapter时,会从web应用上下文和父上下文中获取所有HandlerAdapter。如果不是获取全部,则会只获取beanId为常量HANDLER_ADAPTER_BEAN_NAME值handlerAdapter的bean。如果一个HandlerAdapter都没获取到,则会默认创建SimpleControllerHandlerAdapterHttpRequestHandlerAdapterAnnotationMethodHandlerAdapter

2.3.6 initHandlerExceptionResolvers方法

/**
 * Initialize the HandlerExceptionResolver used by this class.
 * <p>If no bean is defined with the given name in the BeanFactory for this namespace,
 * we default to no exception resolver.
 */
private void initHandlerExceptionResolvers(ApplicationContext context) {
    this.handlerExceptionResolvers = null;

    if (this.detectAllHandlerExceptionResolvers) {
        // Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts.
        Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils
                .beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);
        if (!matchingBeans.isEmpty()) {
            this.handlerExceptionResolvers = new ArrayList<HandlerExceptionResolver>(matchingBeans.values());
            // We keep HandlerExceptionResolvers in sorted order.
            AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);
        }
    }
    else {
        try {
            HandlerExceptionResolver her =
                    context.getBean(HANDLER_EXCEPTION_RESOLVER_BEAN_NAME, HandlerExceptionResolver.class);
            this.handlerExceptionResolvers = Collections.singletonList(her);
        }
        catch (NoSuchBeanDefinitionException ex) {
            // Ignore, no HandlerExceptionResolver is fine too.
        }
    }

    // Ensure we have at least some HandlerExceptionResolvers, by registering
    // default HandlerExceptionResolvers if no other resolvers are found.
    if (this.handlerExceptionResolvers == null) {
        this.handlerExceptionResolvers = getDefaultStrategies(context, HandlerExceptionResolver.class);
        if (logger.isDebugEnabled()) {
            logger.debug("No HandlerExceptionResolvers found in servlet '" + getServletName() + "': using default");
        }
    }
}

其中,常量HANDLER_EXCEPTION_RESOLVER_BEAN_NAME值为 handlerExceptionResolver。

detectAllHandlerExceptionResolvers 属性决定是否检测所有 HandlerExceptionResolver,可以在web.xml设置,默认为true。web.xml中的设置方式为:

<init-param>  
    <param-name>detectAllHandlerExceptionResolvers</param-name>  
    <param-value>false</param-value>  
</init-param>  

在调用用户方法报错时,SpringMVC会把相关信息交给HandlerExceptionResolver解析,默认情况下会获取web应用上下文和父上下文中所有的HandlerExceptionResolver,如果指定只取一个,那么只取id为handlerExceptionResolver的bean。如果一个都没获取到,则默认创建AnnotationMethodHandlerExceptionResolverResponseStatusExceptionResolverDefaultHandlerExceptionResolver

2.3.7 initRequestToViewNameTranslator方法

/**
 * 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 +
                    "]");
        }
    }
}

其中,常量REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME值为 viewNameTranslator。

当前置控制器DispatcherServlet调用完用户方法后,用户方法既没有返回可用的视图名,也没有给response写数据,则需要通过 RequestToViewNameTranslator 生成一个默认的视图名。如果想自定义视图翻译器,那么bean的id必须为viewNameTranslator。如果没有获取到bean,则默认创建DefaultRequestToViewNameTranslator,默认视图翻译器根据URI来生成视图名。

2.3.8 initViewResolvers方法

/**
 * Initialize the ViewResolvers used by this class.
 * <p>If no ViewResolver beans are defined in the BeanFactory for this
 * namespace, we default to InternalResourceViewResolver.
 */
private void initViewResolvers(ApplicationContext context) {
    this.viewResolvers = null;

    if (this.detectAllViewResolvers) {
        // Find all ViewResolvers in the ApplicationContext, including ancestor contexts.
        Map<String, ViewResolver> matchingBeans =
                BeanFactoryUtils.beansOfTypeIncludingAncestors(context, ViewResolver.class, true, false);
        if (!matchingBeans.isEmpty()) {
            this.viewResolvers = new ArrayList<ViewResolver>(matchingBeans.values());
            // We keep ViewResolvers in sorted order.
            AnnotationAwareOrderComparator.sort(this.viewResolvers);
        }
    }
    else {
        try {
            ViewResolver vr = context.getBean(VIEW_RESOLVER_BEAN_NAME, ViewResolver.class);
            this.viewResolvers = Collections.singletonList(vr);
        }
        catch (NoSuchBeanDefinitionException ex) {
            // Ignore, we'll add a default ViewResolver later.
        }
    }

    // Ensure we have at least one ViewResolver, by registering
    // a default ViewResolver if no other resolvers are found.
    if (this.viewResolvers == null) {
        this.viewResolvers = getDefaultStrategies(context, ViewResolver.class);
        if (logger.isDebugEnabled()) {
            logger.debug("No ViewResolvers found in servlet '" + getServletName() + "': using default");
        }
    }
}

ViewResolver我们以后也会经常看到。

其中,常量VIEW_RESOLVER_BEAN_NAME值为viewResolver。

默认情况下,初始化方法会获取wen应用上下文和父上下文中所有的ViewResolver。如果指定只取一个,那么将会取id名为常量viewResolver的bean。如果都没有获取到,则会默认创建InternalResourceViewResolver

2.3.9 initFlashMapManager方法

/**
 * Initialize the {@link FlashMapManager} used by this servlet instance.
 * <p>If no implementation is configured then we default to
 * {@code org.springframework.web.servlet.support.DefaultFlashMapManager}.
 */
private void initFlashMapManager(ApplicationContext context) {
    try {
        this.flashMapManager = context.getBean(FLASH_MAP_MANAGER_BEAN_NAME, FlashMapManager.class);
        if (logger.isDebugEnabled()) {
            logger.debug("Using FlashMapManager [" + this.flashMapManager + "]");
        }
    }
    catch (NoSuchBeanDefinitionException ex) {
        // We need to use the default.
        this.flashMapManager = getDefaultStrategy(context, FlashMapManager.class);
        if (logger.isDebugEnabled()) {
            logger.debug("Unable to locate FlashMapManager with name '" +
                    FLASH_MAP_MANAGER_BEAN_NAME + "': using default [" + this.flashMapManager + "]");
        }
    }
}

其中,常量FLASH_MAP_MANAGER_BEAN_NAME值为flashMapManager。

当开发者使用redirect重定向用户到另一个页面,当前的数据将会丢失,使用FlashMap可以解决这个问题,FlashMapManager就是管理FlashMap的。如果想自定义FlashMapManager,那么bean的id必须为flashMapManager。如果没有获取到bean,则默认创建SessionFlashMapManager

2.3.10 DispatcherServlet初始化方法中的默认值

在前9个方法中,我们发现在上下文中没有得到相应的实现类对象时,会默认指定一些默认值。这是怎么回事呢?

先来看看这个静态方法块。

/**
 * Name of the class path resource (relative to the DispatcherServlet class)
 * that defines DispatcherServlet's default strategy names.
 */
private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";

private static final Properties defaultStrategies;

static {
    // Load default strategy implementations from properties file.
    // This is currently strictly internal and not meant to be customized
    // by application developers.
    try {
        ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
        defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
    }
    catch (IOException ex) {
        throw new IllegalStateException("Could not load 'DispatcherServlet.properties': " + ex.getMessage());
    }
}

这块代码的含义是读取DispatcherServlet类所在包下文件名为DispatcherServlet.properties的文件

我们再看看DispatcherServlet.properties文件内容

# Default implementation classes for DispatcherServlet's strategy interfaces.
# Used as fallback when no matching beans are found in the DispatcherServlet context.
# Not meant to be customized by application developers.

org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver

org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver

org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
    org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping

org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
    org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
    org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter

org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\
    org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
    org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver

org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator

org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver

org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager

看到了吗?所有的默认值均来自这里,这就简单了,我们再看看哪里使用到了defaultStrategies属性。

/**
 * Return the default strategy object for the given strategy interface.
 * <p>The default implementation delegates to {@link #getDefaultStrategies},
 * expecting a single object in the list.
 * @param context the current WebApplicationContext
 * @param strategyInterface the strategy interface
 * @return the corresponding strategy object
 * @see #getDefaultStrategies
 */
protected <T> T getDefaultStrategy(ApplicationContext context, Class<T> strategyInterface) {
    List<T> strategies = getDefaultStrategies(context, strategyInterface);
    if (strategies.size() != 1) {
        throw new BeanInitializationException(
                "DispatcherServlet needs exactly 1 strategy for interface [" + strategyInterface.getName() + "]");
    }
    return strategies.get(0);
}

/**
 * Create a List of default strategy objects for the given strategy interface.
 * <p>The default implementation uses the "DispatcherServlet.properties" file (in the same
 * package as the DispatcherServlet class) to determine the class names. It instantiates
 * the strategy objects through the context's BeanFactory.
 * @param context the current WebApplicationContext
 * @param strategyInterface the strategy interface
 * @return the List of corresponding strategy objects
 */
@SuppressWarnings("unchecked")
protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
    String key = strategyInterface.getName();
    String value = defaultStrategies.getProperty(key);
    if (value != null) {
        String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
        List<T> strategies = new ArrayList<T>(classNames.length);
        for (String className : classNames) {
            try {
                Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
                Object strategy = createDefaultStrategy(context, clazz);
                strategies.add((T) strategy);
            }
            catch (ClassNotFoundException ex) {
                throw new BeanInitializationException(
                        "Could not find DispatcherServlet's default strategy class [" + className +
                                "] for interface [" + key + "]", ex);
            }
            catch (LinkageError err) {
                throw new BeanInitializationException(
                        "Error loading DispatcherServlet's default strategy class [" + className +
                                "] for interface [" + key + "]: problem with class file or dependent class", err);
            }
        }
        return strategies;
    }
    else {
        return new LinkedList<T>();
    }
}

/**
 * Create a default strategy.
 * <p>The default implementation uses
 * {@link org.springframework.beans.factory.config.AutowireCapableBeanFactory#createBean}.
 * @param context the current WebApplicationContext
 * @param clazz the strategy implementation class to instantiate
 * @return the fully configured strategy instance
 * @see org.springframework.context.ApplicationContext#getAutowireCapableBeanFactory()
 * @see org.springframework.beans.factory.config.AutowireCapableBeanFactory#createBean
 */
protected Object createDefaultStrategy(ApplicationContext context, Class<?> clazz) {
    return context.getAutowireCapableBeanFactory().createBean(clazz);
}

这块代码略读就行,我们就知道默认数据从哪来就行。

2.3.11 小结

DispatcherServlet的初始化部分总算是理清楚了。以上几节,我们了解了SpringMVC通过继承HttpServlet从而生成了一个前置控制器,通过层层转包Servlet的init方法,在FramworkServlet中执行initServletBean方法生成了web应用上下文,在DispatcherServlet中执行onRefresh方法初始化了DispatcherServlet中的相关属性。

2.4 DispatcherServlet处理请求分析

由于FramworkServlet将处理请求集中到了doService方法中,DispatcherServlet处理请求分析也将从doServie方法实现中展开。

2.4.1 doService方法

doService方法比较简单,向request设置一些属性信息,然后调用doDispatch方法进行请求处理。

/**
 * Exposes the DispatcherServlet-specific request attributes and delegates to {@link #doDispatch}
 * for the actual dispatching.
 */
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
    if (logger.isDebugEnabled()) {
        String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : "";
        logger.debug("DispatcherServlet with name '" + getServletName() + "'" + resumed +
                " processing " + request.getMethod() + " request for [" + getRequestUri(request) + "]");
    }

    // Keep a snapshot of the request attributes in case of an include,
    // to be able to restore the original attributes after the include.
    Map<String, Object> attributesSnapshot = null;
    if (WebUtils.isIncludeRequest(request)) {
        attributesSnapshot = new HashMap<String, Object>();
        Enumeration<?> attrNames = request.getAttributeNames();
        while (attrNames.hasMoreElements()) {
            String attrName = (String) attrNames.nextElement();
            if (this.cleanupAfterInclude || attrName.startsWith("org.springframework.web.servlet")) {
                attributesSnapshot.put(attrName, request.getAttribute(attrName));
            }
        }
    }

    // Make framework objects available to handlers and view objects.
    request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
    request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
    request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
    request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());

    FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
    if (inputFlashMap != null) {
        request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
    }
    request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
    request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);

    try {
        doDispatch(request, response);
    }
    finally {
        if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
            // Restore the original attribute snapshot, in case of an include.
            if (attributesSnapshot != null) {
                restoreAttributesAfterInclude(request, attributesSnapshot);
            }
        }
    }
}

我们看到,向request的属性中注入了web应用上下文,用户区域解析器,主题解析器和flashMapManager,这些都是在初始化时生成的。

2.4.2 doDispatch方法

doDispatch方法在较高程度上抽象MVC,在这个方法里,你将看到很多接口。请打起精神,仔细体会。

/**
 * Process the actual dispatching to the handler.
 * <p>The handler will be obtained by applying the servlet's HandlerMappings in order.
 * The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters
 * to find the first that supports the handler class.
 * <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers
 * themselves to decide which methods are acceptable.
 * @param request current HTTP request
 * @param response current HTTP response
 * @throws Exception in case of any kind of processing failure
 */
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()) {
                    logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
                }
                if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                    return;
                }
            }

            if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                return;
            }

            // Actually invoke the handler.
            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

            if (asyncManager.isConcurrentHandlingStarted()) {
                return;
            }

            applyDefaultViewName(processedRequest, mv);
            mappedHandler.applyPostHandle(processedRequest, response, mv);
        }
        catch (Exception ex) {
            dispatchException = ex;
        }
        catch (Throwable err) {
            // As of 4.3, we're processing Errors thrown from handler methods as well,
            // making them available for @ExceptionHandler methods and other scenarios.
            dispatchException = new NestedServletException("Handler dispatch failed", err);
        }
        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    }
    catch (Exception ex) {
        triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
    }
    catch (Throwable err) {
        triggerAfterCompletion(processedRequest, response, mappedHandler,
                new NestedServletException("Handler processing failed", err));
    }
    finally {
        if (asyncManager.isConcurrentHandlingStarted()) {
            // Instead of postHandle and afterCompletion
            if (mappedHandler != null) {
                mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
            }
        }
        else {
            // Clean up any resources used by a multipart request.
            if (multipartRequestParsed) {
                cleanupMultipart(processedRequest);
            }
        }
    }
}

在第17行代码中,尝试从request中获取一个WebAsyncManager,Async特性是出现在Servlet3.0规范中,允许Servlet容器将socket的所有权交给用户业务线程处理,从而释放出Servlet容器的线程,供更多连接请求使用。目前我们先不对这个特性做过多描述,如果想了解更多内容,可以参考我的其他文章。

在第24行代码中,检测是否包含POST请求的文件上传。checkMultipart 方法会先判断 multipartResolver 属性是否为null。如果不为null,就调用 multipartResolver 接口的 isMultipart 方法判断是不是文件上传。如果是文件上传,并且入参request没有解析成文件上传request,且request的属性中没有文件上传异常,就调用 multipartResolver 接口的 resolveMultipart 方法返回文件上传request。否则返回原request。代码如下:

/**
 * Convert the request into a multipart request, and make multipart resolver available.
 * <p>If no multipart resolver is set, simply use the existing request.
 * @param request current HTTP request
 * @return the processed request (multipart wrapper if necessary)
 * @see MultipartResolver#resolveMultipart
 */
protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
    if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
        if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {
            logger.debug("Request is already a MultipartHttpServletRequest - if not in a forward, " +
                    "this typically results from an additional MultipartFilter in web.xml");
        }
        else if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) instanceof MultipartException) {
            logger.debug("Multipart resolution failed for current request before - " +
                    "skipping re-resolution for undisturbed error rendering");
        }
        else {
            return this.multipartResolver.resolveMultipart(request);
        }
    }
    // If not returned before: return original request.
    return request;
}

在第28行代码中,调用了getHandler方法挑选出一个合适的HandlerExecutionChain(也被成为执行链)。在 2.3.4 节初始化 HandlerMapping 中,我们会得到一组 HandlerMapping 实现类对象。在getHandler方法中,就是循环这些HandlerMapping,如果有一个HandlerMapping调用方法得到了非空的HandlerExecutionChain对象,就说明这个HandlerMapping可以映射到一个执行链。代码如下:

/**
 * Return the HandlerExecutionChain for this request.
 * <p>Tries all handler mappings in order.
 * @param request current HTTP request
 * @return the HandlerExecutionChain, or {@code null} if no handler could be found
 */
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    for (HandlerMapping hm : this.handlerMappings) {
        if (logger.isTraceEnabled()) {
            logger.trace(
                    "Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
        }
        HandlerExecutionChain handler = hm.getHandler(request);
        if (handler != null) {
            return handler;
        }
    }
    return null;
}

在第29行到32行代码中,会判断有没有得到一个执行链。如果没有得到执行链,说明开发者没有编写URI对应的方法或文件。这时候 noHandlerFound 方法有两种选择,如果属性 throwExceptionIfNoHandlerFound 为true,抛出异常,否则向response发送一个404状态码然后结束。throwExceptionIfNoHandlerFound 属性可以在web.xml设置,默认为 false。web.xml中的设置方式为:

<init-param>  
    <param-name>throwExceptionIfNoHandlerFound</param-name>  
    <param-value>true</param-value>  
</init-param>  

noHandlerFound方法代码如下:

/**
 * No handler found -> set appropriate HTTP response status.
 * @param request current HTTP request
 * @param response current HTTP response
 * @throws Exception if preparing the response failed
 */
protected void noHandlerFound(HttpServletRequest request, HttpServletResponse response) throws Exception {
    if (pageNotFoundLogger.isWarnEnabled()) {
        pageNotFoundLogger.warn("No mapping found for HTTP request with URI [" + getRequestUri(request) +
                "] in DispatcherServlet with name '" + getServletName() + "'");
    }
    if (this.throwExceptionIfNoHandlerFound) {
        throw new NoHandlerFoundException(request.getMethod(), getRequestUri(request),
                new ServletServerHttpRequest(request).getHeaders());
    }
    else {
        response.sendError(HttpServletResponse.SC_NOT_FOUND);
    }
}

在第35行代码中,调用 getHandlerAdapter 方法取得一个 HandlerAdapter ,HandlerAdapter 是由 2.3.5节中initHandlerAdapter初始化生成的,如果所有 HandlerAdapter 都不支持该handler,就会抛出异常。由于执行链中的 handler 类型为Object,DispatcherServlet 并不能执行handler,所以需要一个 HandlerAdapter 适配器来执行它。怎么判断一个 HandlerAdapter 是否能够执行 handler呢?HandlerAdapter 接口有一个 supports 方法是用来判断是否支持的。getHandlerAdapter 方法如下:

/**
 * Return the HandlerAdapter for this handler object.
 * @param handler the handler object to find an adapter for
 * @throws ServletException if no HandlerAdapter can be found for the handler. This is a fatal error.
 */
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
    for (HandlerAdapter ha : this.handlerAdapters) {
        if (logger.isTraceEnabled()) {
            logger.trace("Testing handler adapter [" + ha + "]");
        }
        if (ha.supports(handler)) {
            return ha;
        }
    }
    throw new ServletException("No adapter for handler [" + handler +
            "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}

在第50行到第52行代码中,会先调用执行链的applyPreHandle方法,看名字就知道是申请预处理。如果预处理返回了false结果,就直接返回,实际情况中预处理主要是判断是否已登录、记录开始日志、注入相应上下文等功能。

在第55行代码中,由 HandlerAdapter 来执行之前从 HandlerMapping 中获得的handler ,返回的是 ModelAndView。

在第57行到59行代码中,还是Servlet3.0规范特性,如果是异步执行的,就直接返回不做任何处理,这里也不做细究。

在第61行代码中,尝试注入默认视图名。什么意思呢?得到的 ModelAndView 可能并没有指定view,这时候需要设置一个默认的view。还记得在 2.3.7 初始化 viewNameTranslator 视图翻译器吧,就是在这里工作的。方法如下:

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

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

在第62行代码中,调用执行链的applyPostHandle方法,看名字就知道是申请后处理。实际情况中后处理主要是修改 ModelAndView 。

在第72行代码中,会对整个处理结果做一个整理。因为在整个处理过程中,有很多地方会抛出异常并被设置到 dispatchException 参数中,也有可能得到正常结果。在 processDispatchResult 方法中,会先判断是否有异常产生,如果有异常产生,会给 HandlerExceptionResolver 一个机会去捕捉异常,HandlerExceptionResolver 是在 2.3.6节初始化的,如果有一个 HandlerExceptionResolver 可以产生正常的 ModelAndView,就会继续正常执行。

如果产生了 ModelAndView,就会调用render方法真正的向response写入经过view和model渲染的字节数据。如果不是异步执行的,还会触发执行链的 triggerAfterCompletion 完成后触发方法。具体的方法如下:

/**
 * Handle the result of handler selection and handler invocation, which is
 * either a ModelAndView or an Exception to be resolved to a ModelAndView.
 */
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);
    }
}

/**
 * Determine an error ModelAndView via the registered HandlerExceptionResolvers.
 * @param request current HTTP request
 * @param response current HTTP response
 * @param handler the executed handler, or {@code null} if none chosen at the time of the exception
 * (for example, if multipart resolution failed)
 * @param ex the exception that got thrown during handler execution
 * @return a corresponding ModelAndView to forward to
 * @throws Exception if no error ModelAndView found
 */
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
        Object handler, Exception ex) throws Exception {

    // Check registered HandlerExceptionResolvers...
    ModelAndView exMv = null;
    for (HandlerExceptionResolver handlerExceptionResolver : this.handlerExceptionResolvers) {
        exMv = handlerExceptionResolver.resolveException(request, response, handler, ex);
        if (exMv != null) {
            break;
        }
    }
    if (exMv != null) {
        if (exMv.isEmpty()) {
            request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
            return null;
        }
        // We might still need view name translation for a plain error model...
        if (!exMv.hasView()) {
            exMv.setViewName(getDefaultViewName(request));
        }
        if (logger.isDebugEnabled()) {
            logger.debug("Handler execution resulted in exception - forwarding to resolved error view: " + exMv, ex);
        }
        WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
        return exMv;
    }

    throw ex;
}

如果在 processDispatchResult 处理异常或正常结果时还抛出了异常,就会被第74到第80行代码cache住,调用执行链的 triggerAfterCompletion 完成后触发方法。

最后第81行到第93行,会判断是否需要调用执行链的异步执行开始触发器和是否需要清除文件上传的中间临时磁盘数据。

2.4.3 render方法

render方法单独提出来,因为前置处理器作为MVC的C已经调用了在MVC中属于M的可执行链。如果返回了模型数据 ModelAndView,就需要调用MVC中的V进行视图渲染了。

render方法相比于 doDispatch 方法还是很简单的。首先它会通过 2.3.2节初始化的用户区域解析器解析到区域信息,然后调用 2.3.8节初始化的 viewResolver 获取View实现类对象。得到了View实现类对象后,直接调用View接口的render方法,整合模型数据,向response写入字节数据。以下是具体代码:

/**
 * Render the given ModelAndView.
 * <p>This is the last stage in handling a request. It may involve resolving the view by name.
 * @param mv the ModelAndView to render
 * @param request current HTTP servlet request
 * @param response current HTTP servlet response
 * @throws ServletException if view is missing or cannot be resolved
 * @throws Exception if there's a problem rendering the view
 */
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;
    }
}


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

2.4.4 小结

处理请求比起初始化看来真是复杂了很多。不过只要抓住MVC模式思想,还是比较容易理清楚的。

  1. 首先得到requesr后,需要给request注入一些属性,方便处理,例如web应用上下文、用户区域解析器、主题解析器,flashMapManager等等。
  2. 其次再判断是否是POST文件上传请求,如果是就解析该请求。
  3. 之后就通过 HandlerMapping 获取执行链,如果没得到执行链,要么抛异常,要么发送一个404响应然后结束。
  4. 得到了执行链,需要获取可以执行执行链的 HandlerAdapter ,如果没获取到就抛异常。
  5. 得到了 HandlerAdapter 后,先调用执行链的预处理器,如果返回false,就结束此次请求。
  6. 调用 HandlerAdapter 执行执行链。
  7. 之后就处理默认视图名,调用执行链的后置处理器。
  8. C和M之间的事情处理完后,该考虑V了
  9. 此时,如果之前产生了异常,给 HandlerExceptionResolver 一个机会生成一个正常的 ModelAndView。如果没有生成,只好再抛异常。
  10. 此时还没抛异常,说明有正常的 ModelAndView。
  11. 获取用户区域信息,通过 ViewResolver 得到View接口,调用View接口的render方法渲染页面。
  12. 调用执行链的完成后触发方法。
  13. 处理V的事情如果抛了异常,还会调用执行链的完成后触发方法。
  14. 最终处理一下异步的触发器事件,销毁一下文件上传产生的临时磁盘文件。

在第2章,我们简单过了DispatcherServlet和他的父类。可以看出,我只是囫囵吞枣般的解析了DispatcherServlet和他父类的初始化、销毁和请求处理。对,我就是刻意这么做的,就是不让大家陷入到SpringMVC的复杂嵌套逻辑中。在接下来的这几章咱们来分别深入了解一下在DispatcherServlet中出现的各种接口。

标签: java, springmvc, spring

添加新评论