SpringMVC源码阅读笔记-DispatcherServlet

原文链接: http://my.oschina.net/ojeta/blog/716550

DispatcherServlet

分析过ContextLoaderListener的代码之后,再去分析DispatcherServlet的代码会发现有很多的相似性。 在分析之前我们先看一下spring-mvc.xml中的内容,它位于src/main/webapp/WEB-INF/spring-mvc.xml,内容为

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p" xmlns:c="http://www.springframework.org/schema/c" xmlns:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="personal.wanghui.quickstart" use-default-filters="false">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" />
        <context:include-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice" />
    </context:component-scan>

    <mvc:annotation-driven>
        <mvc:message-converters register-defaults="true">
            <bean class="org.springframework.http.converter.StringHttpMessageConverter" c:defaultCharset="UTF-8" />
        </mvc:message-converters>
        <mvc:argument-resolvers>
            <bean class="personal.wanghui.quickstart.web.bind.method.SubjectMethodArgumentResolver" />
        </mvc:argument-resolvers>
    </mvc:annotation-driven>
    
    <!-- 定义JSP文件的位置 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" 
        p:viewClass="org.springframework.web.servlet.view.JstlView"
        p:prefix="/WEB-INF/views/" 
        p:suffix=".jsp" />
        
    <!-- 文件上传 -->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">  
        <property name="defaultEncoding" value="UTF-8" />
    </bean>

    <!-- 容器默认的DefaultServletHandler处理 所有静态内容与无RequestMapping处理的URL -->
    <mvc:default-servlet-handler />

</beans>

首先,看下DispatcherServlet的继承结构:
image

结构不算复杂。注意到DispatcherServet继承了Servlet标准中的HttpServlet。那么,根据Servlet的相关知识,DispatcherServlet也要符合Servlet的生命周期,即初始化、处理请求并响应、销毁,对应的方法即是init()doGet()/doPost()..、destroy()。而DispatcherServlet只要重写这些方法,即可实现相应的功能。接下来我们一个一个分析。

重写init()

DispatcherServlet并没有自己重写init(),而是在HttpServletBean中重写的。看源码org.springframework.web.servlet.HttpServletBean.init()

@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); ---- (1)
        BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this); ---- (2)
        ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext()); ---- (3)
        bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment())); ----- (4)
        initBeanWrapper(bw); ---- (5)
        bw.setPropertyValues(pvs, true); ---- (6)
    }
    catch (BeansException ex) {
        logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
        throw ex;
    }

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

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

这里先介绍Spring的一个概念,叫做BeanWrapper。BeanWrapper是一个接口,实现类是BeanWrapperImpl,它可以将普通的JavaBean包装为BeanWrapper,然后可以通过void setPropertyValue(String propertyName, Object value)Object getPropertyValue(String propertyName)等方法来实现对属性值的设置和获取。它有两点很重要的特性:

  1. 可以不用考虑属性值的类型,而直接调用setPropertyValue()方法即可,BeanWrapper内置了大量的属性编辑器PropertyEditor,会在内部实现类型转换。
  2. propertyName可以支持嵌套,比如setProperyValue("wife.name", "zcc")。 另外,如果JavaBean的某属性类型,不在默认的属性编辑器的范围内,还可以自定义属性编辑器。更多关于BeanWrapper的知识,可以参考《Spring 3.x企业应用开发实战》第5章 Spring容器高级主题,5.1.4节 BeanWrapper。

核心代码逻辑梳理

  • (1) 将ServletConfig中的参数包装成PropertyValues。看下源码
public ServletConfigPropertyValues(ServletConfig config, Set<String> requiredProperties)
    throws ServletException {

    Set<String> missingProps = (requiredProperties != null && !requiredProperties.isEmpty()) ?
            new HashSet<String>(requiredProperties) : null;

    Enumeration<String> en = config.getInitParameterNames();
    while (en.hasMoreElements()) {
        String property = en.nextElement(); ---- [1]
        Object value = config.getInitParameter(property); ---- [2]
        addPropertyValue(new PropertyValue(property, value)); ---- [3]
        if (missingProps != null) {
            missingProps.remove(property);
        }
    }

    // Fail if we are still missing properties.
    if (missingProps != null && missingProps.size() > 0) {
        throw new ServletException(
            "Initialization from ServletConfig for servlet '" + config.getServletName() +
            "' failed; the following required properties were missing: " +
            StringUtils.collectionToDelimitedString(missingProps, ", "));
    }
}

[1]和[2]从ServletConfig中取出每个通过web.xml配置的初始化参数的名和值,在[3]中将每个名值对都包装为PropertyValue对象,并调用addPropertyValue(PropertyValue pv)添加PropertyValue对象。

  • (2) 将this(指DispatcherServlet)包装为BeanWrapper。看下源码
public static BeanWrapper forBeanPropertyAccess(Object target) {
    return new BeanWrapperImpl(target);
}

使用BeanWrapperImpl将目标对象包装

  • (3) 创建一个资源加载器,使可以从WebRoot路径下加载文件。看下源码
public class ServletContextResourceLoader extends DefaultResourceLoader {

	private final ServletContext servletContext;

	public ServletContextResourceLoader(ServletContext servletContext) {
		this.servletContext = servletContext;
	}

	@Override
	protected Resource getResourceByPath(String path) {
		return new ServletContextResource(this.servletContext, path);
	}

}

这里介绍一下Spring的Resource体系。先看继承结构:
image
这里我们先不去看具体的实现类,先看看最顶层的两个接口的定义

public interface InputStreamSource {

    InputStream getInputStream() throws IOException;

}
public interface Resource extends InputStreamSource {

    boolean exists();

    boolean isReadable();

    boolean isOpen();

    URL getURL() throws IOException;

    URI getURI() throws IOException;

    File getFile() throws IOException;

    long contentLength() throws IOException;

    long lastModified() throws IOException;

    Resource createRelative(String relativePath) throws IOException;

    String getFilename();

    String getDescription();

}

由接口定义我们看出,所有的Resource实现类,都会具有访问相应资源的常用方法,比如判断是否存在、是否可读、获取URL、获取文件对象、获取内容长度、获取文件名、以相对路径创建一个资源,并且还可以获取输入流。

接下来看下我们这里涉及到的子类ServletContextResource,这里只看一下它的构造方法和getInputStream()方法

public ServletContextResource(ServletContext servletContext, String path) {
    // check ServletContext
    Assert.notNull(servletContext, "Cannot resolve ServletContextResource without ServletContext");
    this.servletContext = servletContext;

    // check path
    Assert.notNull(path, "Path is required");
    String pathToUse = StringUtils.cleanPath(path);
    if (!pathToUse.startsWith("/")) {
        pathToUse = "/" + pathToUse;
    }
    this.path = pathToUse;
}

@Override
public InputStream getInputStream() throws IOException {
    InputStream is = this.servletContext.getResourceAsStream(this.path);
    if (is == null) {
        throw new FileNotFoundException("Could not open " + getDescription());
    }
    return is;
}

在构造方法中,只是将ServletContext封装起来,然后对路径做一些小小的处理,如果路径不是以/开头的,就加上/。
getInputStream()中,获取路径的输入流直接调用ServletContext.getResourceAsStream(String path)方法返回。

  • (4) 为BeanWrapper注册自定义的属性编辑器,使实现如果属性的类型是Resource的话,如何实现类型转换。这里重点看一下ResourceEditor的代码
public ResourceEditor(ResourceLoader resourceLoader, PropertyResolver propertyResolver) { ---- [1]
    this(resourceLoader, propertyResolver, true);
}

public ResourceEditor(ResourceLoader resourceLoader, PropertyResolver propertyResolver,  ---- [2]
    boolean ignoreUnresolvablePlaceholders) {
    Assert.notNull(resourceLoader, "ResourceLoader must not be null");
    this.resourceLoader = resourceLoader;
    this.propertyResolver = propertyResolver;
    this.ignoreUnresolvablePlaceholders = ignoreUnresolvablePlaceholders;
}


@Override
public void setAsText(String text) {
    if (StringUtils.hasText(text)) {
        String locationToUse = resolvePath(text).trim();
        setValue(this.resourceLoader.getResource(locationToUse)); ---- [3]
    }
    else {
        setValue(null);
    }
}

protected String resolvePath(String path) {
    if (this.propertyResolver == null) {
        this.propertyResolver = new StandardEnvironment();
    }
    return (this.ignoreUnresolvablePlaceholders ? this.propertyResolver.resolvePlaceholders(path) :
            this.propertyResolver.resolveRequiredPlaceholders(path));
}

@Override
public String getAsText() {
    Resource value = (Resource) getValue();
    try {
        // Try to determine URL for resource.
        return (value != null ? value.getURL().toExternalForm() : "");
    }
    catch (IOException ex) {
        // Couldn't determine resource URL - return null to indicate
        // that there is no appropriate text representation.
        return null;
    }
}

代码中调用的是构造方法[1],[1]又调用了[2],可以看到在setAsText(String text)getAsText()两个方法中,实现了String类型的path和Resource之间的相互转换。
[3]处实际上就调用的是ServletContextResourceLoader.getResource(String location),这个方法在ServletContextResourceLoader的父类DefaultResourceLoader中,代码如下

@Override
public Resource getResource(String location) {
    Assert.notNull(location, "Location must not be null");
    if (location.startsWith("/")) {
        return getResourceByPath(location);
    }
    else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
        return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
    }
    else {
        try {
            // Try to parse the location as a URL...
            URL url = new URL(location);
            return new UrlResource(url);
        }
        catch (MalformedURLException ex) {
            // No URL -> resolve as resource path.
            return getResourceByPath(location);
        }
    }
}

protected Resource getResourceByPath(String path) {
    return new ClassPathContextResource(path, getClassLoader());
}

虽然在DefaultResourceLoader中提供了getResourceByPath(String path)的默认实现,但它是从classpath下加载资源的,而恰好在子类ServletContextResourceLoader重写了此方法,使其可以从WebRoot下加载文件。

  • (5) 初始化BeanWrapper,这里是一个空方法,子类也没有去重写。
  • (6) 向BeanWrapper中设置属性值。
  • (7) 空方法,留给子类实现。下面会详细说明这个方法。

小结 DispatcherServlet在父类中重写了标准的HttpServletinit()方法,为后续处理请求做一些初始化工作。在重写的init()中,将初始化参数包装为PropertyValues,将DispatcherServlet包装为BeanWrapper,利用BeanWrapper的特性来设置属性。

initServletBean()

这个方法是在子类中实现的,代码路径org.springframework.web.servlet.FrameworkServlet.initServletBean()

@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(); ---- (1)
        initFrameworkServlet(); ---- (2)
    }
    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");
    }
}

核心代码逻辑梳理

  • (1) 初始化WebApplicationContext。前面已经构造了一个Spring容器,这里"又"构造了一个Spring容器。看下源码
protected WebApplicationContext initWebApplicationContext() {
    WebApplicationContext rootContext =
            WebApplicationContextUtils.getWebApplicationContext(getServletContext()); ---- (1)
    WebApplicationContext wac = null;

    if (this.webApplicationContext != null) { ---- (2)
        // 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(); ---- (3)
    }
    if (wac == null) {
        // No context instance is defined for this servlet -> create a local one
        wac = createWebApplicationContext(rootContext); ---- (4)
    }

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

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

    return wac;
}

核心代码逻辑梳理

  • (1) 直接看源码
public static WebApplicationContext getWebApplicationContext(ServletContext sc) {
    return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
}

public static WebApplicationContext getWebApplicationContext(ServletContext sc, String attrName) {
    Assert.notNull(sc, "ServletContext must not be null");
    Object attr = sc.getAttribute(attrName);
    if (attr == null) {
        return null;
    }
    if (attr instanceof RuntimeException) {
        throw (RuntimeException) attr;
    }
    if (attr instanceof Error) {
        throw (Error) attr;
    }
    if (attr instanceof Exception) {
        throw new IllegalStateException((Exception) attr);
    }
    if (!(attr instanceof WebApplicationContext)) {
        throw new IllegalStateException("Context attribute is not of type WebApplicationContext: " + attr);
    }
    return (WebApplicationContext) attr;
}

代码大意就是从ServletContext中根据key(值为WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE)获取WebApplicationContext
回想之前我们对ContextLoaderListener的分析,它就是把Spring容器放到了ServletContext中,而key恰好就是WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,所以这里取到的就是之前通过ContextLoaderListener初始化好的Spring容器。 所以rootContext就是Spring的根容器,也是父容器。

  • (2) 是判断this.webApplicationContext是否为空,之前并没有向这个属性中写入值,所以这里是空的。变量wac代表SpringMVC自己的容器,此时也还是null。
  • (3) 查询是否有自己的WebApplication。源码如下
protected WebApplicationContext findWebApplicationContext() {
    String attrName = getContextAttribute();
    if (attrName == null) {
        return null;
    }
    WebApplicationContext wac =
            WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName);
    if (wac == null) {
        throw new IllegalStateException("No WebApplicationContext found: initializer not registered?");
    }
    return wac;
}

还是尝试从ServletContext中获取WebApplicationContext,但key是通过getContextAttribute()确定的。默认情况下,如果没有调用setContextAttribute(String contextAttribute),那么getContextAttribute()就是返回null的。所以,这个方法的返回值也是null

  • (4) 既然没有外部传入,在ServletContext中也没找到自己的SpringMVC容器,那么就创建一个。看源码
protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) {
    return createWebApplicationContext((ApplicationContext) parent);
}

protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
    Class<?> contextClass = getContextClass(); ---- (1)
    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); ---- (2)

    wac.setEnvironment(getEnvironment()); ---- (3)
    wac.setParent(parent); ---- (4)
    wac.setConfigLocation(getContextConfigLocation()); ---- (5)

    configureAndRefreshWebApplicationContext(wac); ---- (6)

    return wac;
}

核心代码逻辑分析

  • 方法以Spring父(根)容器作为参数传入。
  • (1) 获取contextClass,在ContextLoaderListener也有这么个类似的方法。看下getContextClass()方法的代码
public static final Class<?> DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext.class;
private Class<?> contextClass = DEFAULT_CONTEXT_CLASS;

public void setContextClass(Class<?> contextClass) {
    this.contextClass = contextClass;
}

public Class<?> getContextClass() {
    return this.contextClass;
}

如果没有调用过setContextClass(Class<?> contextClass),那么getContextClass()的返回值就是XmlWebApplicationContext.class又一个XmlWebApplicationContext

  • (2) 就是获取到contextClass,然后实例化,和Spring容器的实例化是一样的。
  • (3) 向当前Spring容器设置环境对象。
  • (4) 设置当前Spring容器与父容器的引用关系。由此,我们看到,SpringMVC容器和Spring容器是父子关系
  • (5) 设置配置文件路径。如果在配置DispatcherServlet时指定了contextConfigLocation参数,这里就会获取到参数值。在哪里设置的?就是BeanWrapper包装了DispatcherServlet,然后setPropertyValues()啊。
  • (6) 一看方法名就很熟悉,和初始化父容器的方法名都一样的。提醒一下org.springframework.web.context.ContextLoader.configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc)。看下源码
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())); ---- [1]

    // 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);  ---- [2]
    applyInitializers(wac); ---- [3]
    wac.refresh();  ---- [4]
}

核心代码逻辑梳理

  • [1] 这里编程式的添加了一个应用监听器,关于应用监听器前面已经有所介绍了。直接看下ContextRefreshListener的源码
private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        FrameworkServlet.this.onApplicationEvent(event);
    }
}

代码很简单,实现了ApplicationListener接口,监听ContextRefreshedEvent事件,当ContextRefreshedEvent事件被分发后,会触发FrameworkServlet.this.onApplicationEvent(event);,看下这个方法的代码:

public void onApplicationEvent(ContextRefreshedEvent event) {
    this.refreshEventReceived = true;
    onRefresh(event.getApplicationContext());
}

onRefresh(ApplicationContext context)交给了子类实现org.springframework.web.servlet.DispatcherServlet.onRefresh(ApplicationContext context) 好了,这条线先放这。等下面讲到事件分发的时候再说。

  • [2] 对SpringMVC容器做一些后处理。目前,这是个空方法,可以交留子类重写。
  • [3] 应用初始化器。依然与ContextLoader.customizeContext(ServletContext sc, ConfigurableWebApplicationContext wac) 类似,不再说了。
  • [4] 与ContextLoader.configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc)中的wac.refresh();完全一样,也是调用AbstractApplicationContext.refresh()

这里注意,在AbstractApplicationContext.refresh()中,最后会分发ContextRefreshedEvent事件,此时由于在子容器中注册了一个监听ContextRefreshedEvent事件的ApplicationListener。那么根据之前的分析,此时会调用org.springframework.web.servlet.DispatcherServlet.onRefresh(ApplicationContext context),并且传入的参数就是SpringMVC容器。看下源码:

protected void onRefresh(ApplicationContext context) {
    initStrategies(context);
}

protected void initStrategies(ApplicationContext context) {
    initMultipartResolver(context);
    initLocaleResolver(context);
    initThemeResolver(context);
    initHandlerMappings(context);
    initHandlerAdapters(context);
    initHandlerExceptionResolvers(context);
    initRequestToViewNameTranslator(context);
    initViewResolvers(context);
    initFlashMapManager(context);
}

会初始化一堆策略。在initStrategies(ApplicationContext context)方法中,又分别调用了好多方法。我们逐一看一下。

initMultipartResolver(ApplicationContext context)

看下源码

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

尝试从SpringMVC容器中获取上传文件解析器的bean,将其赋值给this.multipartResolver;如果没找到会抛出NoSuchBeanDefinitionException异常,catch到异常后,将this.multipartResolver的值设置为null

initLocaleResolver(ApplicationContext context)

看下源码

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

和上面方法大体类似,尝试获取本地语言解析器的bean。但这里会在没有找到这样的bean时,做一些默认处理,即this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);。看下getDefaultStrategy(ApplicationContext context, Class<T> strategyInterface)方法的源码:

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

@SuppressWarnings("unchecked")
protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
    String key = strategyInterface.getName();
    String value = defaultStrategies.getProperty(key); ---- (1)
    if (value != null) {
        String[] classNames = StringUtils.commaDelimitedListToStringArray(value); ---- (2)
        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); ---- (3)
                strategies.add((T) strategy); ---- (4)
            }
            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>();
    }
}

真正执行获取默认策略的是getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface),它返回一个List<T>getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface)方法返回的是这个List<T>中的第1个值。

  • (1) 从defaultStrategies中根据接口的名字获取值,defaultStrategies?很熟悉的变量名,之前在分析ContextLoaderListener中遇到过。那defaultStrategies到底是什么呢?看下源码
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());
    }
}

嗯,变量的赋值逻辑也很相似。原来它就是实现了从classpath下读取DispatcherServlet.properties文件。那看下DispatcherServlet.properties中的内容吧。它位于spring-webmvc-[version].jar的org.springframework.web.servlet包下。

# 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.getProperty(key)返回的就是这个接口默认的实现类。如果有多个实现类会用逗号连接起来。

  • (2) 通过逗号把这些实现类拆分成数组。
  • (3) 在遍历获取到每个实现类的Class<?>后,去动态地创建SpringBean。这里简单看下源码org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(Class<T> beanClass)
public <T> T createBean(Class<T> beanClass) throws BeansException {
    // Use prototype bean definition, to avoid registering bean as dependent bean.
    RootBeanDefinition bd = new RootBeanDefinition(beanClass);
    bd.setScope(SCOPE_PROTOTYPE);
    bd.allowCaching = ClassUtils.isCacheSafe(beanClass, getBeanClassLoader());
    return (T) createBean(beanClass.getName(), bd, null);
}

要注意的是,动态创建的这些SpringBean,scope是prototype的。

  • (4) 将每个实现类都加入到List<T> strategies中,最后返回。

现在,我们梳理一下List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface)方法具体的含义:就是根据接口名,到DispatcherServlet.properties中找到它的实现类,然后动态地去创建为SpringBean。而Spring之所以将这些实现类配置在properties文件中放到jar包里,是不希望开发者去修改它。

initThemeResolver(ApplicationContext context)

initLocaleResolver(ApplicationContext context)类似

initHandlerMappings(ApplicationContext context)

看下源码

private void initHandlerMappings(ApplicationContext context) {
    this.handlerMappings = null;

    if (this.detectAllHandlerMappings) { ---- (1)
        // Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
        Map<String, HandlerMapping> matchingBeans =
                BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false); ---- (2)
        if (!matchingBeans.isEmpty()) {
            this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
            // We keep HandlerMappings in sorted order.
            OrderComparator.sort(this.handlerMappings); ---- (3)
        }
    }
    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); ---- (4)
        if (logger.isDebugEnabled()) {
            logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
        }
    }
}
  • (1) this.detectAllHandlerMappings默认为true
  • (2) BeanFactoryUtils.beansOfTypeIncludingAncestors(ListableBeanFactory lbf, Class<T> type, boolean includeNonSingletons, boolean allowEagerInit) 实现了在当前容器和其父容器中查找类型为type的所有bean。
  • (3) 对这些bean进行排序。之前我们分析过AnnotationAwareOrderComparator的用法,这里看下OrderComparator的核心逻辑:
@Override
public int compare(Object o1, Object o2) {
    boolean p1 = (o1 instanceof PriorityOrdered);
    boolean p2 = (o2 instanceof PriorityOrdered);
    if (p1 && !p2) {
        return -1;
    }
    else if (p2 && !p1) {
        return 1;
    }

    // Direct evaluation instead of Integer.compareTo to avoid unnecessary object creation.
    int i1 = getOrder(o1);
    int i2 = getOrder(o2);
    return (i1 < i2) ? -1 : (i1 > i2) ? 1 : 0;
}

protected int getOrder(Object obj) {
    return (obj instanceof Ordered ? ((Ordered) obj).getOrder() : Ordered.LOWEST_PRECEDENCE);
}

compare()方法中,先看两个对象是否有实现了PriorityOrdered接口,实现的比没实现的靠前。如果都实现了,或者都没实现,就根据getOrder()方法获取order值,再根据数值大小判断,数值小的排前面。
getOrder()方法中,判断是否实现了Ordered接口,如果实现了就通过Ordered接口的getOrder()方法获取排序值,如果没实现就排在最后。 看来,对所有HandlerMapping接口的子类,如果是用注解@Order指定顺序是无用的,自己实现的时候要注意。囧~

  • (4) 如果前面都没有获取handlerMapping,那么就从默认策略中加载。
initHandlerAdapters(ApplicationContext context)

initHandlerMappings(ApplicationContext context)类似

initHandlerExceptionResolvers(ApplicationContext context)

initHandlerMappings(ApplicationContext context)类似

initRequestToViewNameTranslator(ApplicationContext context)

initLocaleResolver(ApplicationContext context)类似

initViewResolvers(ApplicationContext context)

initHandlerMappings(ApplicationContext context)类似

initFlashMapManager(ApplicationContext context)

initLocaleResolver(ApplicationContext context)类似

至此,SpringMVC容器也初始化完毕了,事件也分发了,该初始化的也都初始化了。但是initWebApplicationContext()还没有完全执行完呢。再回顾一下代码:

protected WebApplicationContext initWebApplicationContext() {
    WebApplicationContext rootContext =
            WebApplicationContextUtils.getWebApplicationContext(getServletContext()); ---- (1)
    WebApplicationContext wac = null;

    if (this.webApplicationContext != null) { ---- (2)
        // 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(); ---- (3)
    }
    if (wac == null) {
        // No context instance is defined for this servlet -> create a local one
        wac = createWebApplicationContext(rootContext); ---- (4)
    }

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

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

    return wac;
}

现在,(4)已经执行完了。接下来,根据this.refreshEventReceived变量的值来判断要不要再执行onRefresh()。如果先收到事件,会在FrameworkServlet.onApplicationEvent(ContextRefreshedEvent event)中将this.refreshEventReceived设置为true,那么(5)也就不会执行了。
然后,还会判断this.publishContext的值,默认是true,那么(6)就是把当前的SpringMVC容器放入到ServletContext中,主要看一下key是谁呢?看下getServletContextAttributeName()方法的源码:

public static final String SERVLET_CONTEXT_PREFIX = FrameworkServlet.class.getName() + ".CONTEXT.";

public String getServletContextAttributeName() {
    return SERVLET_CONTEXT_PREFIX + getServletName();
}

@Override
public final String getServletName() {
    return (getServletConfig() != null ? getServletConfig().getServletName() : null);
}

可以看出SpringMVC容器放入ServletContext中的key为"org.springframework.web.servlet.FrameworkServlet.CONTEXT.[servletName]"。例如,按我们最初给出的配置文件来看,key为"org.springframework.web.servlet.FrameworkServlet.CONTEXT.springServlet"。

最后的最后,在FrameworkServlet.initServletBean()中,执行完this.webApplicationContext = initWebApplicationContext();后,还调用了initFrameworkServlet(),但这是个空方法,子类也没有去重写。

至此,DispatcherServlet的init()方法就全部执行完成了。大体上做了3件事:

  1. 初始化参数
  2. 创建一个SpringMVC自己的容器,并与父容器之间建立引用关系
  3. 利用事件机制,初始化SpringMVC自己的一些组件

几个小问题

  1. 我们最初给出的web.xml的配置中,在配置DispatcherServlet时,还配置了这么一行代码<load-on-startup>1</load-on-startup>,这是为了在Web容器启动时就调用Servletinit()方法。
  2. 解答之前的一个问题:之前我们写了一个ApplicationListener的小例子,然后提了一个问题:如果项目中有SpringMVC会打印出两句;如果没有,则只会打印出第一句;如果将此myApplicationListener配置到SpringMVC的配置文件中,则只会打印出第二句。这是为什么呢?
    现在解答一下:现在我们知道了,SpringMVC容器和Spring容器是子与父的关系。
    如果myApplicationListener配置在父容器里,父容器初始化完成后肯定会打印日志,那么子容器呢?子容器的事件也会向父容器中分发的。为什么这么说呢?我们看这个方法AbstractApplicationContext.publishEvent(ApplicationEvent event)
@Override
public void publishEvent(ApplicationEvent event) {
	Assert.notNull(event, "Event must not be null");
	if (logger.isTraceEnabled()) {
		logger.trace("Publishing event in " + getDisplayName() + ": " + event);
	}
	getApplicationEventMulticaster().multicastEvent(event);
	if (this.parent != null) {
		this.parent.publishEvent(event);
	}
}

在最后,会判断如果容器有父容器,也会向父容器分发事件的,而myApplicationListener又是配置在父容器中的。所以,SpringMVC加载完毕后,还会打印出一条日志。
当然,如果没有SpringMVC,就只会打印出一条日志了。
如果myApplicationListener配置在SpringMVC中,首先Spring容器中就并没有注册这个ApplicationListener,所以Spring容器初始化完毕时,是不会打印日志的。打印的那条日志,只是在SpringMVC容器初始化完毕时打印的。
3. 前面我们说了这么多initXxx()类的方法,我们重点看一个方法initHandlerMappings(ApplicationContext context),这个方法前面提到了,是初始化HandlerMappring的,再总结一下它的逻辑:

  • 先从SpringMVC容器和它的父容器Spring容器中找
  • 找不到就找名字是HANDLER_MAPPING_BEAN_NAME的SpringBean
  • 如果还没有,就使用默认策略。
    好,我们这里重点看第一步,当我们尝试从SpringMVC容器和Spring容器中找的时候,到底能不能找到呢?能找到几个呢?通过debug跟踪,我们发现运行到Map<String, HandlerMapping> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);这一行时,是能够找到的,而且还是3个bean。分别是RequestMappingHandlerMappingBeanNameUrlHandlerMappingSimpleUrlHandlerMapping。那这3个bean是哪里来的呢?
    要解释清楚这个问题,还得先说明一个Spring的概念,叫命名空间(Namespace)。Spring通过提供命名空间来使开发者可以通过定义命名空间的方式,来扩展Spring容器在解析自定义xml配置时的方法。
    什么是命名空间呢?凡是在Spring的xml配置文件,以<xxx:xxx xxx="xxx"></xxx:xxx>形式存在的都是Spring的自定义命名空间。回忆之前spring-mvc.xml中的内容。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p" xmlns:c="http://www.springframework.org/schema/c" xmlns:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 自动扫描且只扫描@Controller -->
    <context:component-scan base-package="personal.wanghui.quickstart" use-default-filters="false">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" />
        <context:include-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice" />
    </context:component-scan>

    <mvc:annotation-driven>
        <mvc:message-converters register-defaults="true">
            <bean class="org.springframework.http.converter.StringHttpMessageConverter" c:defaultCharset="UTF-8" />
        </mvc:message-converters>
        <mvc:argument-resolvers>
            <bean class="personal.wanghui.quickstart.web.bind.method.SubjectMethodArgumentResolver" />
        </mvc:argument-resolvers>
    </mvc:annotation-driven>
    
    <!-- 定义JSP文件的位置 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" 
        p:viewClass="org.springframework.web.servlet.view.JstlView"
        p:prefix="/WEB-INF/views/" 
        p:suffix=".jsp" />
        
    <!-- 文件上传 -->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">  
        <property name="defaultEncoding" value="UTF-8" />
    </bean>

    <!-- 容器默认的DefaultServletHandler处理 所有静态内容与无RequestMapping处理的URL -->
    <mvc:default-servlet-handler />

</beans>

<context:component-scan /><mvc:annotation-driven /><mvc:default-servlet-handler />都是自定义命名空间。
那么实现一个自定义命名空间都有什么要素呢?以<mvc:annotation-driven />举例吧。

  • 得有一个xml语法文件xsd,例如:spring-mvc.xsd;
  • 得有命名空间的url,例如:xmlns:mvc="http://www.springframework.org/schema/mvc
  • 得有命名空间url和xsd文件的对应关系,例如http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
  • 为了可以不通过网络去加载这个xsd文件,还得有一个的xsd的url形式与本地xsd文件路径的映射配置,看spring-webmvc-[version].jar下的META-INF文件夹下的spring.schemas。内容是:
http\://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd=org/springframework/web/servlet/config/spring-mvc-3.0.xsd
http\://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd=org/springframework/web/servlet/config/spring-mvc-3.1.xsd
http\://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd=org/springframework/web/servlet/config/spring-mvc-3.2.xsd
http\://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd=org/springframework/web/servlet/config/spring-mvc-4.0.xsd
http\://www.springframework.org/schema/mvc/spring-mvc.xsd=org/springframework/web/servlet/config/spring-mvc-4.0.xsd
  • 语法校验层面解决了,自定义命名空间怎么解析呢?看spring-webmvc-[version].jar下的META-INF文件夹下的spring.handlers。内容是:
http\://www.springframework.org/schema/mvc=org.springframework.web.servlet.config.MvcNamespaceHandler

说明要使用MvcNamespaceHandler去解析自定义命名空间了。打开MvcNamespaceHandler类的源码:

public class MvcNamespaceHandler extends NamespaceHandlerSupport {

    @Override
    public void init() {
        registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser());
        registerBeanDefinitionParser("default-servlet-handler", new DefaultServletHandlerBeanDefinitionParser());
        registerBeanDefinitionParser("interceptors", new InterceptorsBeanDefinitionParser());
        registerBeanDefinitionParser("resources", new ResourcesBeanDefinitionParser());
        registerBeanDefinitionParser("view-controller", new ViewControllerBeanDefinitionParser());
    }

}

看到了annotation-drivendefault-servlet-handler等等,这就是mvc命名空间下的标签。那么annotation-driven对应的就是使用AnnotationDrivenBeanDefinitionParser去解析的。再看AnnotationDrivenBeanDefinitionParser

class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {

    @Override
    public BeanDefinition parse(Element element, ParserContext parserContext) {
        Object source = parserContext.extractSource(element);

        CompositeComponentDefinition compDefinition = new CompositeComponentDefinition(element.getTagName(), source);
        parserContext.pushContainingComponent(compDefinition);

        RuntimeBeanReference contentNegotiationManager = getContentNegotiationManager(element, source, parserContext);

        RootBeanDefinition handlerMappingDef = new RootBeanDefinition(RequestMappingHandlerMapping.class);
        handlerMappingDef.setSource(source);
        handlerMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        handlerMappingDef.getPropertyValues().add("order", 0);
        handlerMappingDef.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
        String methodMappingName = parserContext.getReaderContext().registerWithGeneratedName(handlerMappingDef);

        if (element.hasAttribute("enable-matrix-variables")) {
            Boolean enableMatrixVariables = Boolean.valueOf(element.getAttribute("enable-matrix-variables"));
            handlerMappingDef.getPropertyValues().add("removeSemicolonContent", !enableMatrixVariables);
        }
        else if (element.hasAttribute("enableMatrixVariables")) {
            Boolean enableMatrixVariables = Boolean.valueOf(element.getAttribute("enableMatrixVariables"));
            handlerMappingDef.getPropertyValues().add("removeSemicolonContent", !enableMatrixVariables);
        }

        configurePathMatchingProperties(handlerMappingDef, element);
        
        ......
        
        parserContext.registerComponent(new BeanComponentDefinition(handlerMappingDef, methodMappingName));
        
        ......
        
        // Ensure BeanNameUrlHandlerMapping (SPR-8289) and default HandlerAdapters are not "turned off"
        MvcNamespaceUtils.registerDefaultComponents(parserContext, source);

        parserContext.popAndRegisterContainingComponent();

        return null;
    }
}

核心逻辑梳理

  • 它实现了BeanDefinitionParser接口,重写了parse(Element element, ParserContext parserContext)方法
  • 动态创建了一个RequestMappingHandlerMapping类型的RootBeanDefinition,并通过BeanWrapper的形式配置属性,注意它的order是0。
  • 然后调用ParserContext.registerComponent(ComponentDefinition component),向容器注册组件。
  • parserContext.popAndRegisterContainingComponent();,源码就不贴了。向容器注册了BeanNameUrlHandlerMappingorder是2。

以上,我们就把RequestMappingHandlerMappingBeanNameUrlHandlerMapping找到了。使用相同的方法可以在<mvc:default-servlet-handler />中找到SimpleUrlHandlerMapping,它的order没有设置,那么order值就是最大值。 同理,可以找到3个HandlerAdapterRequestMappingHandlerAdapterHttpRequestHandlerAdapterSimpleControllerHandlerAdapter

重写destroy()

看下源码

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

代码很简单,就是直接从自己的成员变量里中取到SpringMVC容器,然后调用它的close()方法。

重写各种doXxx()

FrameworkServlet中,我们找到了好几个重写的doXxx()方法,其中doGet()doPost()doPut()doDelete()的内容都一样,都只有一句话processRequest(request, response);,只有doOptions()doTrace()有一些特殊处理。看下源码

@Override
protected void doOptions(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {

    if (this.dispatchOptionsRequest) {
        processRequest(request, response);
        if (response.containsHeader("Allow")) {
            // Proper OPTIONS response coming from a handler - we're done.
            return;
        }
    }

    // Use response wrapper for Servlet 2.5 compatibility where
    // the getHeader() method does not exist
    super.doOptions(request, new HttpServletResponseWrapper(response) {
        @Override
        public void setHeader(String name, String value) {
            if ("Allow".equals(name)) {
                value = (StringUtils.hasLength(value) ? value + ", " : "") + RequestMethod.PATCH.name();
            }
            super.setHeader(name, value);
        }
    });
}

this.dispatchOptionsRequest表明是否分发doOptions的请求,默认值是false。那么,接下来就直接调用父类中的doOptions()方法,只不过这里使用了一个匿名类HttpServletResponseWrapperresponse做了包装,重写了一些逻辑,具体就不再赘述了。

@Override
protected void doTrace(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {

    if (this.dispatchTraceRequest) {
        processRequest(request, response);
        if ("message/http".equals(response.getContentType())) {
            // Proper TRACE response coming from a handler - we're done.
            return;
        }
    }
    super.doTrace(request, response);
}

this.dispatchTraceRequest表明是否分发doTrace的请求,默认值是false。所以,直接调用父类的doTrance()方法进行处理。

好了,那么现在所有的核心逻辑都指向processRequest(HttpServletRequest request, HttpServletResponse response)方法,我们就来看看这个方法的源码。

processRequest(HttpServletRequest request, HttpServletResponse response)
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); ---- (1)

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

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

    initContextHolders(request, localeContext, requestAttributes); ---- (4)

    try {
        doService(request, response); ---- (5)
    }
    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); ---- (6)
        if (requestAttributes != null) {
            requestAttributes.requestCompleted(); ---- (7)
        }

        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, startTime, failureCause); ---- (7)
    }
}

核心逻辑梳理

  • (1) 构建LocaleContext
  • (2) 构建RequestAttributes。看下getRequestAttributes()方法的代码
public static RequestAttributes getRequestAttributes() {
    RequestAttributes attributes = requestAttributesHolder.get();
    if (attributes == null) {
        attributes = inheritableRequestAttributesHolder.get();
    }
    return attributes;
}

requestAttributesHolder的类型是ThreadLocal<RequestAttributes>,试图从当前线程中获取RequestAttributesRequestAttributes做了什么呢?主要是对HttpServletRequest对象或HttpSession的一个包装。再

protected ServletRequestAttributes buildRequestAttributes(
        HttpServletRequest request, HttpServletResponse response, RequestAttributes previousAttributes) {

    if (previousAttributes == null || previousAttributes instanceof ServletRequestAttributes) {
        return new ServletRequestAttributes(request);
    }
    else {
        return null;  // preserve the pre-bound RequestAttributes instance
    }
}

再看下ServletRequestAttributes

public ServletRequestAttributes(HttpServletRequest request) {
    Assert.notNull(request, "Request must not be null");
    this.request = request;
}

@Override
public Object getAttribute(String name, int scope) {
    if (scope == SCOPE_REQUEST) {
        if (!isRequestActive()) {
            throw new IllegalStateException(
                    "Cannot ask for request attribute - request is not active anymore!");
        }
        return this.request.getAttribute(name);
    }
    else {
        HttpSession session = getSession(false);
        if (session != null) {
            try {
                Object value = session.getAttribute(name);
                if (value != null) {
                    this.sessionAttributesToUpdate.put(name, value);
                }
                return value;
            }
            catch (IllegalStateException ex) {
                // Session invalidated - shouldn't usually happen.
            }
        }
        return null;
    }
}

构造方法只是对HttpServletRequest的一个包装,getAttribute()的时候,需要指定scopescope的取值定义在其实现的接口RequestAttributes中,自己看代码就可以了,不再详述。

  • (3) 注册可调用的拦截器。
  • (4) 初始化上下文Holder。
  • (5) 核心逻辑,但交给子类处理。一会详述。
  • (6) 重置一些上下文中的变量。
  • (7) 发布请求完成的事件,自己看源码吧,不难。目前Spring没有提供监听ServletRequestHandledEvent事件的监听器,如果开发者需要可以自己实现一个ApplicationListener
doService(HttpServletRequest request, HttpServletResponse response)

接下来重点说这个方法,在FrameworkServlet中是一个抽象方法,真正的实现是在子类DispatcherServlet中。看下源码:

@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()); ---- (1)
    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); ---- (2)
    }
    finally {
        if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
            return;
        }
        // Restore the original attribute snapshot, in case of an include.
        if (attributesSnapshot != null) {
            restoreAttributesAfterInclude(request, attributesSnapshot);
        }
    }
}

这个方法也不每行看了,就看2行。··

  • (1) 中表明每次请求,都会把SpringMVC容器的引用放到请求里,key是WEB_APPLICATION_CONTEXT_ATTRIBUTE。也就是说可以通过request.getAttribute(DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE)获取到SpringMVC容器。
  • (2) 再调用doDispatch(HttpServletRequest request, HttpServletResponse response)方法,这里才是真正处理请求的地方。
doDispatch(HttpServletRequest request, HttpServletResponse response)

直接看源码

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); ---- (1)
            multipartRequestParsed = (processedRequest != request);

            // Determine handler for the current request.
            mappedHandler = getHandler(processedRequest); ---- (2)
            if (mappedHandler == null || mappedHandler.getHandler() == null) {
                noHandlerFound(processedRequest, response);
                return;
            }

            // Determine handler adapter for the current request.
            HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); ---- (3)

            // 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)) { ---- (4)
                return;
            }

            try {
                // Actually invoke the handler.
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); ---- (5)
            }
            finally {
                if (asyncManager.isConcurrentHandlingStarted()) {
                    return;
                }
            }

            applyDefaultViewName(request, mv); ---- (6)
            mappedHandler.applyPostHandle(processedRequest, response, mv); ----- (7)
        }
        catch (Exception ex) {
            dispatchException = ex;
        }
        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); ---- (8)
    }
    catch (Exception ex) {
        triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
    }
    catch (Error err) {
        triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
    }
    finally {
        if (asyncManager.isConcurrentHandlingStarted()) {
            // Instead of postHandle and afterCompletion
            mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
            return;
        }
        // Clean up any resources used by a multipart request.
        if (multipartRequestParsed) {
            cleanupMultipart(processedRequest);
        }
    }
}

我们先大致的看一下逻辑,然后再逐一分析核心方法

  1. 先检查了一下上传请求
  2. 根据请求获得了一个HandlerExecutionChain
  3. HandlerExecutionChain中获取了一个handler,然后获得了一个HandlerAdapter
  4. 对请求做前处理
  5. 处理请求,获得ModelAndView
  6. 设置默认的视图
  7. 对请求做后处理
  8. 处理请求分发结果
HttpServletRequest processedRequest = checkMultipart(request)

检查是否是上传文件的请求

HandlerExecutionChain mappedHandler = getHandler(processedRequest)

获取Handler,返回值是一个HandlerExecutionChain。先不急去看方法实现,先看下HandlerExecutionChain的类定义。

public class HandlerExecutionChain {

    private final Object handler;

    private HandlerInterceptor[] interceptors;

    private List<HandlerInterceptor> interceptorList;

    /**
     * 只包含handler的构造方法
     */
    public HandlerExecutionChain(Object handler) {
        this(handler, (HandlerInterceptor[]) null);
    }

    /**
     * 包含handler和拦截器的构造方法
     */
    public HandlerExecutionChain(Object handler, HandlerInterceptor... interceptors) {
        if (handler instanceof HandlerExecutionChain) {
            HandlerExecutionChain originalChain = (HandlerExecutionChain) handler;
            this.handler = originalChain.getHandler();
            this.interceptorList = new ArrayList<HandlerInterceptor>();
            CollectionUtils.mergeArrayIntoCollection(originalChain.getInterceptors(), this.interceptorList);
            CollectionUtils.mergeArrayIntoCollection(interceptors, this.interceptorList);
        }
        else {
            this.handler = handler;
            this.interceptors = interceptors;
        }
    }

    /**
     * 获取handler
     */
    public Object getHandler() {
        return this.handler;
    }

    /**
     * 获取所有拦截器
     */
    public HandlerInterceptor[] getInterceptors() {
        if (this.interceptors == null && this.interceptorList != null) {
            this.interceptors = this.interceptorList.toArray(new HandlerInterceptor[this.interceptorList.size()]);
        }
        return this.interceptors;
	}

    /**
     * 循环调用拦截器的preHandle()方法,如果返回false,则执行拦截器的afterCompletion()方法
     */
    boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HandlerInterceptor[] interceptors = getInterceptors();
        if (!ObjectUtils.isEmpty(interceptors)) {
            for (int i = 0; i < interceptors.length; i++) {
                HandlerInterceptor interceptor = interceptors[i];
                if (!interceptor.preHandle(request, response, this.handler)) {
                    triggerAfterCompletion(request, response, null);
                    return false;
                }
                this.interceptorIndex = i;
            }
        }
        return true;
    }

    /**
     * 循环调用拦截器的postHandle()方法
     */
    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);
            }
        }
    }

    /**
     * 循环调用拦截的afterCompletion()方法
     */
    void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex)
            throws Exception {
        HandlerInterceptor[] interceptors = getInterceptors();
        if (!ObjectUtils.isEmpty(interceptors)) {
            for (int i = this.interceptorIndex; i >= 0; i--) {
                HandlerInterceptor interceptor = interceptors[i];
                try {
                    interceptor.afterCompletion(request, response, this.handler, ex);
                }
                catch (Throwable ex2) {
                    logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
                }
            }
        }
    }

}

它主要就是对handler和其一堆拦截器interceptors的封装,并提供了方法来获取handler,获取拦截器和执行拦截器中的方法。

好了,接下来我们来看看getHandler(HttpServletRequest request)方法

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

代码很简单,遍历之前初始化好的handlerMappings,哪个HandlerMappinggetHandler(HttpServletRequest request)方法的返回值不为空,则返回谁。当然这里就与这些HandlerMapping的顺序有关了,排在前面的肯定先遍历到。
根据我们之前的分析,这里先遍历的是RequestMappingHandlerMapping,那我们就先来看看它的getHandler(HttpServletRequest request)方法。但发现这个方法的实现并不在RequestMappingHandlerMapping中,而在它的父类AbstractHandlerMapping中。所以,我们先看看RequestMappingHandlerMapping的继承结构。 image
核心类是AbstractHandlerMapping,通过继承WebApplicationContextObjectSupport获得了Spring容器和ServletContext,又实现了HandlerMapping接口,重写了接口中唯一的方法getHandler(HttpServletRequest request),还实现了Ordered接口,使子类支持排序。接下来就看一下AbstractHandlerMapping.getHandler(HttpServletRequest request)方法的源码

@Override
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    Object handler = getHandlerInternal(request);
    if (handler == null) {
        handler = getDefaultHandler();
    }
    if (handler == null) {
        return null;
    }
    // Bean name or resolved handler?
    if (handler instanceof String) {
        String handlerName = (String) handler;
        handler = getApplicationContext().getBean(handlerName);
    }
    return getHandlerExecutionChain(handler, request);
}

方法被设置为final,显然不希望子类重写,说明它是一个实现的足够好的模板方法。但也给子类留下2个可以重写的方法protected abstract Object getHandlerInternal(HttpServletRequest request)getHandlerExecutionChain(Object handler, HttpServletRequest request)getHandlerInternal()是抽象方法,完全由子类实现;getHandlerExecution()protected方法,并提供了默认实现。看下源码

protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
    HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
            (HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
    chain.addInterceptors(getAdaptedInterceptors());

    String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
    for (MappedInterceptor mappedInterceptor : this.mappedInterceptors) {
        if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
            chain.addInterceptor(mappedInterceptor.getInterceptor());
        }
    }

    return chain;
}

读一下逻辑,先把handler转换成HandlerExecutionChain,然后给HandlerExecutionChain添加拦截器,拦截器分2种,一种是普通的都要执行的,一种是Mapped拦截器,需要有路径匹配的。最后构造出一个包含handler和拦截器的给HandlerExecutionChain,这和我们之前分析的HandlerExecutionChain的作用相匹配。

那接下来就看看,该怎样获取handler呢?代码位置:AbstractHandlerMethodMapping.getHandlerInternal(HttpServletRequest request)

@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
    String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
    if (logger.isDebugEnabled()) {
        logger.debug("Looking up handler method for path " + lookupPath);
    }
    HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
    if (logger.isDebugEnabled()) {
        if (handlerMethod != null) {
            logger.debug("Returning handler method [" + handlerMethod + "]");
        }
        else {
            logger.debug("Did not find handler method for [" + lookupPath + "]");
        }
    }
    return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
}

又调用了AbstractHandlerMethodMapping.lookupHandlerMethod(String lookupPath, HttpServletRequest request)

protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
    List<Match> matches = new ArrayList<Match>();
    List<T> directPathMatches = this.urlMap.get(lookupPath);
    if (directPathMatches != null) {
        addMatchingMappings(directPathMatches, matches, request);
    }
    if (matches.isEmpty()) {
        // No choice but to go through all mappings...
        addMatchingMappings(this.handlerMethods.keySet(), matches, request);
    }

    if (!matches.isEmpty()) {
        Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
        Collections.sort(matches, comparator);
        if (logger.isTraceEnabled()) {
            logger.trace("Found " + matches.size() + " matching mapping(s) for [" + lookupPath + "] : " + matches);
        }
        Match bestMatch = matches.get(0);
        if (matches.size() > 1) {
            Match secondBestMatch = matches.get(1);
            if (comparator.compare(bestMatch, secondBestMatch) == 0) {
                Method m1 = bestMatch.handlerMethod.getMethod();
                Method m2 = secondBestMatch.handlerMethod.getMethod();
                throw new IllegalStateException(
                        "Ambiguous handler methods mapped for HTTP path '" + request.getRequestURL() + "': {" +
                        m1 + ", " + m2 + "}");
            }
        }
        handleMatch(bestMatch.mapping, lookupPath, request);
        return bestMatch.handlerMethod;
    }
    else {
        return handleNoMatch(handlerMethods.keySet(), lookupPath, request);
    }
}

代码的大致逻辑是,先精确匹配,如果没匹配到,就遍历所有方法。如果找到好几个匹配的,就找那个最匹配的,如果有2个都是最匹配的,就抛异常。
这里有一处逻辑还没分析清晰,就是如果没匹配到,就遍历所有方法所有方法是从取到的呢?
我们注意到,AbstractHandlerMethodMapping实现了InitializingBean接口,那么就得重写afterPropertiesSet()方法。我们看

@Override
public void afterPropertiesSet() {
    initHandlerMethods();
}

protected void initHandlerMethods() {
    if (logger.isDebugEnabled()) {
        logger.debug("Looking for request mappings in application context: " + getApplicationContext());
    }

    String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
            BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
            getApplicationContext().getBeanNamesForType(Object.class));

    for (String beanName : beanNames) {
        if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX) &&
                isHandler(getApplicationContext().getType(beanName))){
            detectHandlerMethods(beanName);
        }
    }
    handlerMethodsInitialized(getHandlerMethods());
}

afterPropertiesSet()方法直接调用了initHandlerMethods()方法,在这个方法里,detectHandlerMethodsInAncestorContexts默认是false,那就不找父容器了,直接从子容器里取出所有的bean,然后遍历,要对那边beanName不是以SCOPED_TARGET_NAME_PREFIX开头的,其通过isHandler(Class<?> beanType)方法判断返回true的bean,做进一步处理,也就是调用detectHandlerMethods(final Object handler)。 接下来先看isHandler(Class<?> beanType),它是由子类实现的RequestMappingHandlerMapping.isHandler(Class<?> beanType)

@Override
protected boolean isHandler(Class<?> beanType) {
    return ((AnnotationUtils.findAnnotation(beanType, Controller.class) != null) ||
            (AnnotationUtils.findAnnotation(beanType, RequestMapping.class) != null));
}

就是以@Controller@RequestMapping注解修饰的类,也就是我们业务代码中的Controller类。
然后看下detectHandlerMethods(final Object handler)

protected void detectHandlerMethods(final Object handler) {
    Class<?> handlerType =
            (handler instanceof String ? getApplicationContext().getType((String) handler) : handler.getClass());

    // Avoid repeated calls to getMappingForMethod which would rebuild RequestMappingInfo instances
    final Map<Method, T> mappings = new IdentityHashMap<Method, T>();
    final Class<?> userType = ClassUtils.getUserClass(handlerType);

    Set<Method> methods = HandlerMethodSelector.selectMethods(userType, new MethodFilter() {
        @Override
        public boolean matches(Method method) {
            T mapping = getMappingForMethod(method, userType);
            if (mapping != null) {
                mappings.put(method, mapping);
                return true;
            }
            else {
                return false;
            }
        }
    });

    for (Method method : methods) {
        registerHandlerMethod(handler, method, mappings.get(method));
    }
}

从所有的Controller类中,找到匹配的方法,哪些方法是匹配的呢?看getMappingForMethod(Method method, Class<?> handlerType),是由子类实现的RequestMappingHandlerMapping.getMappingForMethod(Method method, Class<?> handlerType)

@Override
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
    RequestMappingInfo info = null;
    RequestMapping methodAnnotation = AnnotationUtils.findAnnotation(method, RequestMapping.class);
    if (methodAnnotation != null) {
        RequestCondition<?> methodCondition = getCustomMethodCondition(method);
        info = createRequestMappingInfo(methodAnnotation, methodCondition);
        RequestMapping typeAnnotation = AnnotationUtils.findAnnotation(handlerType, RequestMapping.class);
        if (typeAnnotation != null) {
            RequestCondition<?> typeCondition = getCustomTypeCondition(handlerType);
            info = createRequestMappingInfo(typeAnnotation, typeCondition).combine(info);
        }
    }
    return info;
}

带有@RequestMappring注解的方法就是我们要找的方法,找到后取注解中的属性,然后封装成RequestMappingInfo对象。

找到这些方法后调用registerHandlerMethod(Object handler, Method method, T mapping)

protected void registerHandlerMethod(Object handler, Method method, T mapping) {
    HandlerMethod newHandlerMethod = createHandlerMethod(handler, method);
    HandlerMethod oldHandlerMethod = this.handlerMethods.get(mapping);
    if (oldHandlerMethod != null && !oldHandlerMethod.equals(newHandlerMethod)) {
        throw new IllegalStateException("Ambiguous mapping found. Cannot map '" + newHandlerMethod.getBean() +
                "' bean method \n" + newHandlerMethod + "\nto " + mapping + ": There is already '" +
                oldHandlerMethod.getBean() + "' bean method\n" + oldHandlerMethod + " mapped.");
    }

    this.handlerMethods.put(mapping, newHandlerMethod);
    if (logger.isInfoEnabled()) {
        logger.info("Mapped \"" + mapping + "\" onto " + newHandlerMethod);
    }

    Set<String> patterns = getMappingPathPatterns(mapping);
    for (String pattern : patterns) {
        if (!getPathMatcher().isPattern(pattern)) {
            this.urlMap.add(pattern, mapping);
        }
    }
}

将这些方法再放到this.handlerMethods中。

好了,也就是说,我们在处理请求匹配的方法前,其实已经准备好了所有Controller中的方法了。接下来要做的就是在addMatchingMappings()方法中,将请求和方法做一个匹配。

private void addMatchingMappings(Collection<T> mappings, List<Match> matches, HttpServletRequest request) {
    for (T mapping : mappings) {
        T match = getMatchingMapping(mapping, request);
        if (match != null) {
            matches.add(new Match(match, this.handlerMethods.get(mapping)));
        }
    }
}

具体要看RequestMappingInfoHandlerMapping.getMatchingMapping(RequestMappingInfo info, HttpServletRequest request)

@Override
protected RequestMappingInfo getMatchingMapping(RequestMappingInfo info, HttpServletRequest request) {
    return info.getMatchingCondition(request);
}

最后RequestMappingInfo.getMatchingCondition(HttpServletRequest request)

@Override
public RequestMappingInfo getMatchingCondition(HttpServletRequest request) {
    RequestMethodsRequestCondition methods = this.methodsCondition.getMatchingCondition(request);
    ParamsRequestCondition params = this.paramsCondition.getMatchingCondition(request);
    HeadersRequestCondition headers = this.headersCondition.getMatchingCondition(request);
    ConsumesRequestCondition consumes = this.consumesCondition.getMatchingCondition(request);
    ProducesRequestCondition produces = this.producesCondition.getMatchingCondition(request);

    if (methods == null || params == null || headers == null || consumes == null || produces == null) {
        return null;
    }

    PatternsRequestCondition patterns = this.patternsCondition.getMatchingCondition(request);
    if (patterns == null) {
        return null;
    }

    RequestConditionHolder custom = this.customConditionHolder.getMatchingCondition(request);
    if (custom == null) {
        return null;
    }

    return new RequestMappingInfo(patterns, methods, params, headers, consumes, produces, custom.getCondition());
}

不再具体读了,知道思路就好了。

好了,那我们最终知道了,返回的HandlerExecutionChain中包含的handler是一个HandlerMethod对象。

HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler())

HandlerAdapter

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

逻辑很简单,就是遍历所有HandlerAdapter,如果其supports()方法返回true,则返回此HandlerAdapter。如果没找到,就抛异常。
那么,我们之前分析了,现在this.handlerAdapters中有RequestMappingHandlerAdapter, HttpRequestHandlerAdapter, SimpleControllerHandlerAdapter。就逐一看看谁的supports()方法返回true吧。
既然HandlerAdapter是一个接口,就先来看看它的继承结构:
image
WebApplicationObjectSupport刚才我们分析过了,就是实现了获取容器和ServletContext的功能,WebContentGenerator添加了更多与请求相关的内容。AbstractHandlerMethodAdapter还实现了Ordered接口,用于排序。注意RequestMappingHandlerAdapter实现了InitializingBean接口,会初始化一些东西。
首先看下RequestMappingHandlerAdapter.supports()方法,这个方法定义在它的父类AbstractHandlerMethodAdapter

@Override
public final boolean supports(Object handler) {
    return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
}

protected abstract boolean supportsInternal(HandlerMethod handlerMethod);

显然handler instanceof HandlerMethodtrue,后面还留一个抽象方法,给子类一个机会来改变这个结果。在RequestMappingHandlerAdapter中的supportsInternal()方法中只是简单的返回了true。所以,我们要找的HandlerAdapter就是RequestMappingHandlerAdapter了。

boolean mappedHandler.applyPreHandle(processedRequest, response)

执行预处理。就是调用HandlerExecutionChainapplyPreHandle(),前面已经分析过了,就是循环调用拦截器的preHandle()方法。

ModelAndView mv = ha.handle(processedRequest, response, mappedHandler.getHandler())

要处理具体的请求了。直接调用了AbstractHandlerMethodAdapter.handle(HttpServletRequest request, HttpServletResponse response, Object handler)。看源码

@Override
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
		throws Exception {
	return handleInternal(request, response, (HandlerMethod) handler);
}
protected abstract ModelAndView handleInternal(HttpServletRequest request,
    HttpServletResponse response, HandlerMethod handlerMethod) throws Exception;

什么都没做,只是对handler做了强制类型转换,因为此时已经知道了handler一定是HandlerMethod。(为什么?看AbstractHandlerMethodAdapter.supports(Object handler))。然后看RequestMappingHandlerAdapter实现的handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod)

@Override
protected ModelAndView handleInternal(HttpServletRequest request,
        HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

    if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
        // Always prevent caching in case of session attribute management.
        checkAndPrepare(request, response, this.cacheSecondsForSessionAttributeHandlers, true);
    }
    else {
        // Uses configured default cacheSeconds setting.
        checkAndPrepare(request, response, true);
    }

    // Execute invokeHandlerMethod in synchronized block if required.
    if (this.synchronizeOnSession) {
        HttpSession session = request.getSession(false);
        if (session != null) {
            Object mutex = WebUtils.getSessionMutex(session);
            synchronized (mutex) {
                return invokeHandleMethod(request, response, handlerMethod);
            }
        }
    }

    return invokeHandleMethod(request, response, handlerMethod);
}

我们忽略前面处理缓存的逻辑,this.synchronizeOnSession默认值是false,继续看RequestMappingHandlerAdapter.invokeHandleMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod)

private ModelAndView invokeHandleMethod(HttpServletRequest request,
        HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

    ServletWebRequest webRequest = new ServletWebRequest(request, response); ---- (1)

    WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod); ---- (2)
    ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory); ---- (3)
    ServletInvocableHandlerMethod requestMappingMethod = createRequestMappingMethod(handlerMethod, binderFactory); ---- (4)

    ModelAndViewContainer mavContainer = new ModelAndViewContainer(); ---- (5)
    mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request)); ---- (6)
    modelFactory.initModel(webRequest, mavContainer, requestMappingMethod); ---- (7)
    mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect); ---- (8)

    AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
    asyncWebRequest.setTimeout(this.asyncRequestTimeout);

    final WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    asyncManager.setTaskExecutor(this.taskExecutor);
    asyncManager.setAsyncWebRequest(asyncWebRequest);
    asyncManager.registerCallableInterceptors(this.callableInterceptors);
    asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);

    if (asyncManager.hasConcurrentResult()) {
        Object result = asyncManager.getConcurrentResult();
        mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
        asyncManager.clearConcurrentResult();

        if (logger.isDebugEnabled()) {
            logger.debug("Found concurrent result value [" + result + "]");
        }
        requestMappingMethod = requestMappingMethod.wrapConcurrentResult(result);
    }

    requestMappingMethod.invokeAndHandle(webRequest, mavContainer); ---- (9)

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

    return getModelAndView(mavContainer, modelFactory, webRequest); ---- (10)
}

在分析这个方法之前,回忆一下之前提到RequestMappingHandlerAdapter实现了InitializingBean接口,我们先看看它都初始化什么了。

@Override
public void afterPropertiesSet() {
    if (this.argumentResolvers == null) {
        List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
        this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
    }
    if (this.initBinderArgumentResolvers == null) {
        List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
        this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
    }
    if (this.returnValueHandlers == null) {
        List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
        this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
    }
    initControllerAdviceCache();
}

初始化了参数解析器、初始绑定参数解析器、返回值处理器,还有Controller切面。具体就先不看了。
另外这个类还在无参构造方法中初始化了几个messageConverters,这里也要注意一下。源码就不贴了。

核心代码逻辑分析

  • (1) 将请求和响应包装到ServletWebRequest中。看源码
public ServletWebRequest(HttpServletRequest request, HttpServletResponse response) {
    this(request);
    this.response = response;
}

ServletWebRequestServletRequestAttributes的子类,前面我们分析了ServletRequestAttributes,就是对request的一个包装,那么在ServletWebRequest中加入了对response的包装。

  • (2) 获得数据绑定工厂。看源码
private WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod) throws Exception {
    Class<?> handlerType = handlerMethod.getBeanType();
    Set<Method> methods = this.initBinderCache.get(handlerType);
    if (methods == null) {
        methods = HandlerMethodSelector.selectMethods(handlerType, INIT_BINDER_METHODS);
        this.initBinderCache.put(handlerType, methods);
    }
    List<InvocableHandlerMethod> initBinderMethods = new ArrayList<InvocableHandlerMethod>();
    // Global methods first
    for (Entry<ControllerAdviceBean, Set<Method>> entry : this.initBinderAdviceCache.entrySet()) {
        if (entry.getKey().isApplicableToBeanType(handlerType)) {
            Object bean = entry.getKey().resolveBean();
            for (Method method : entry.getValue()) {
                initBinderMethods.add(createInitBinderMethod(bean, method));
            }
        }
    }
    for (Method method : methods) {
        Object bean = handlerMethod.getBean();
        initBinderMethods.add(createInitBinderMethod(bean, method));
    }
    return createDataBinderFactory(initBinderMethods);
}

简单看下逻辑

  1. 先获得handlerType,也就是Controller的Class<?>
  2. 看缓存里有没有Controller中关于初始绑定(initBinder)的方法,如果没有,就去找一下,找到放到缓存里。其中INIT_BINDER_METHODS的源码是这样的
public static final MethodFilter INIT_BINDER_METHODS = new MethodFilter() {

    @Override
    public boolean matches(Method method) {
        return AnnotationUtils.findAnnotation(method, InitBinder.class) != null;
    }
};

也就是那些带@InitBinder的方法。 3. initBinderAdviceCache是一个Map,key是ControllerAdviceBean,value是对应的方法。接下来遍历所有的initBinderAdviceCache,判断是否能应用到这个handlerType(也就是Controller)上。如果可以的话,解析到ControllerAdviceBean,再遍历它对应的方法,然后创建初始绑定的方法,添加到initBinderMethods中。看下createInitBinderMethod(Object bean, Method method)的源码

private InvocableHandlerMethod createInitBinderMethod(Object bean, Method method) {
    InvocableHandlerMethod binderMethod = new InvocableHandlerMethod(bean, method);
    binderMethod.setHandlerMethodArgumentResolvers(this.initBinderArgumentResolvers);
    binderMethod.setDataBinderFactory(new DefaultDataBinderFactory(this.webBindingInitializer));
    binderMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
    return binderMethod;
}
  1. 遍历Controller中的初始绑定方法,也都加入到initBinderMethods中。
  2. 最后创建了一个通过createDataBinderFactory(List<InvocableHandlerMethod> binderMethods)创建了一个InitBinderDataBinderFactory。看下源码
protected InitBinderDataBinderFactory createDataBinderFactory(List<InvocableHandlerMethod> binderMethods)
        throws Exception {
    return new ServletRequestDataBinderFactory(binderMethods, getWebBindingInitializer());
}

这里又有一个方法getWebBindingInitializer(),我们前面分析过一些Initializer的接口,是为程序提供扩展点用的,这里也是,就不细说了。

  • (3) 获取模型工厂。看源码
private ModelFactory getModelFactory(HandlerMethod handlerMethod, WebDataBinderFactory binderFactory) {
    SessionAttributesHandler sessionAttrHandler = getSessionAttributesHandler(handlerMethod);
    Class<?> handlerType = handlerMethod.getBeanType();
    Set<Method> methods = this.modelAttributeCache.get(handlerType);
    if (methods == null) {
        methods = HandlerMethodSelector.selectMethods(handlerType, MODEL_ATTRIBUTE_METHODS);
        this.modelAttributeCache.put(handlerType, methods);
    }
    List<InvocableHandlerMethod> attrMethods = new ArrayList<InvocableHandlerMethod>();
    // Global methods first
    for (Entry<ControllerAdviceBean, Set<Method>> entry : this.modelAttributeAdviceCache.entrySet()) {
        if (entry.getKey().isApplicableToBeanType(handlerType)) {
            Object bean = entry.getKey().resolveBean();
            for (Method method : entry.getValue()) {
                attrMethods.add(createModelAttributeMethod(binderFactory, bean, method));
            }
        }
    }
    for (Method method : methods) {
        Object bean = handlerMethod.getBean();
        attrMethods.add(createModelAttributeMethod(binderFactory, bean, method));
    }
    return new ModelFactory(attrMethods, binderFactory, sessionAttrHandler);
}

不再详细分析了,和getDataBinderFactory()类似,是对@ModelAttribute的一些处理。

  • (4) 创建请求映射的方法。看源码
private ServletInvocableHandlerMethod createRequestMappingMethod(
        HandlerMethod handlerMethod, WebDataBinderFactory binderFactory) {
    ServletInvocableHandlerMethod requestMethod;
    requestMethod = new ServletInvocableHandlerMethod(handlerMethod);
    requestMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
    requestMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
    requestMethod.setDataBinderFactory(binderFactory);
    requestMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
    return requestMethod;
}

代码很简单,就是把handlerMethod封装到ServletInvocableHandlerMethod中,然后又设置了了参数解析器this.argumentResolvers,返回值处理器this.returnValueHandlers,数据绑定工厂binderFactory,参数名发现者this.parameterNameDiscoverer。如此,我们看到,对于一个方法的从参数解析、方法定义、到返回值处理都封装在了ServletInvocableHandlerMethod中了,接下来肯定就要调用这个类中的方法来执行方法了。ServletInvocableHandlerMethod的继承结构很简单,就不画图了。ServletInvocableHandlerMethod -> InvocableHandlerMethod -> HandlerMethod
在继续之前,我们先看下ServletInvocableHandlerMethod的源码。先看它的构造方法

public ServletInvocableHandlerMethod(HandlerMethod handlerMethod) {
    super(handlerMethod);
    initResponseStatus();
}

private void initResponseStatus() { // 当方法上写了@ResponseStatus时,返回的时候就使用注解中的属性值
    ResponseStatus annotation = getMethodAnnotation(ResponseStatus.class);
    if (annotation != null) {
        this.responseStatus = annotation.value();
        this.responseReason = annotation.reason();
    }
}

HandlerMethod主要是对Controller的方法的封装,InvocableHandlerMethodHandlerMethod的基础上添加了解析参数的功能,ServletInvocableHandlerMethodInvocableHandlerMethod的基础上添加了解析返回值的功能。

  • (5) 创建一个空的ModelAndViewContainer,之后代表返回值的视图和模型都会放到这个容器里。看下ModelAndViewContainer的源码
public class ModelAndViewContainer {

    private Object view;
    
    private boolean requestHandled = false;

    private final ModelMap defaultModel = new BindingAwareModelMap();
    
    ......
}

就是对处理中的model和view的一个记录,为什么说处理中呢?因为这里还有一个requestHandled表明是否请求处理完了,所以这个对象是对方法处理中的model和view的记录,这点要和后面要讲到ModelAndView类区分一下,ModelAndView就是d对ModelAndViewContainer中最后的结果做一个交接,得到方法处理完成后model和view的终态。
view我们是没法控制类型的,因为不知道业务代码最终的返回值是什么。但model是可以控制的,就是一个键值对。那看一下ModelMap相关的类继承关系
image
其实就是一个LinkedHashMap,不再细致分析了。

  • (6) 从FlashMap中获取属性,添加到ModelAndViewContainer中。这个比较简单,就不看源码了。
  • (7) 初始化模型。会调用那些@ModelAttribute的方法,将其返回值加入到Model中。源码不贴了。不过,从这里可以看到,每次调用每个HandlerMethod之前,都会先调用@ModelAttribute修饰的方法。
  • (8) 是否忽略重定向中默认的模型,this.ignoreDefaultModelOnRedirect的默认值是false
  • (9) 调用和处理请求。果然是调用ServletInvocableHandlerMethod中某个方法执行的。看下源码
public void invokeAndHandle(ServletWebRequest webRequest,
        ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {

    Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
    setResponseStatus(webRequest);

    if (returnValue == null) {
        if (isRequestNotModified(webRequest) || hasResponseStatus() || mavContainer.isRequestHandled()) {
            mavContainer.setRequestHandled(true);
            return;
        }
    }
    else if (StringUtils.hasText(this.responseReason)) {
        mavContainer.setRequestHandled(true);
        return;
    }

    mavContainer.setRequestHandled(false);
    try {
        this.returnValueHandlers.handleReturnValue(
                returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
    }
    catch (Exception ex) {
        if (logger.isTraceEnabled()) {
            logger.trace(getReturnValueHandlingErrorMessage("Error handling return value", returnValue), ex);
        }
        throw ex;
    }
}

先大致看下逻辑

  1. 还是没有自己去调用,委托给invokeForRequest()方法去调用了,得到了返回值。
  2. ServletWebRequest中设置响应状态,之前分析了,ServletWebRequest中封装了请求和响应。
  3. 如果返回值是null,做一系列判断,并且标记请求已处理完成。结束方法。
  4. 如有responseReason,就标记请求已处理完成。结束方法。
  5. 如果没有被3、4结束掉,那么标记请求还没处理完。
  6. 处理返回值。 逻辑很清楚,看几个重要的方法。

invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer, Object... providedArgs)
这个方法在ServletInvocableHandlerMethod的父类InvocableHandlerMethod中,看源码

public Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer,
        Object... providedArgs) throws Exception {
    Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
    if (logger.isTraceEnabled()) {
        StringBuilder sb = new StringBuilder("Invoking [");
        sb.append(getBeanType().getSimpleName()).append(".");
        sb.append(getMethod().getName()).append("] method with arguments ");
        sb.append(Arrays.asList(args));
        logger.trace(sb.toString());
    }
    Object returnValue = doInvoke(args);
    if (logger.isTraceEnabled()) {
        logger.trace("Method [" + getMethod().getName() + "] returned [" + returnValue + "]");
    }
    return returnValue;
}

只有2个核心逻辑:

  1. 解析参数
  2. 传入解析后的参数,执行方法调用,得到返回值 先看是如何解析参数的
private Object[] getMethodArgumentValues(NativeWebRequest request, ModelAndViewContainer mavContainer,
        Object... providedArgs) throws Exception {

    MethodParameter[] parameters = getMethodParameters();
    Object[] args = new Object[parameters.length];
    for (int i = 0; i < parameters.length; i++) {
        MethodParameter parameter = parameters[i];
        parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
        GenericTypeResolver.resolveParameterType(parameter, getBean().getClass());
        args[i] = resolveProvidedArgument(parameter, providedArgs);
        if (args[i] != null) {
            continue;
        }
        if (this.argumentResolvers.supportsParameter(parameter)) {
            try {
                args[i] = this.argumentResolvers.resolveArgument(
                        parameter, mavContainer, request, this.dataBinderFactory);
                continue;
            }
            catch (Exception ex) {
                if (logger.isTraceEnabled()) {
                    logger.trace(getArgumentResolutionErrorMessage("Error resolving argument", i), ex);
                }
                throw ex;
            }
        }
        if (args[i] == null) {
            String msg = getArgumentResolutionErrorMessage("No suitable resolver for argument", i);
            throw new IllegalStateException(msg);
        }
    }
    return args;
}

代码逻辑:先获取到这个方法的所有参数,然后遍历。尝试使用parameterNameDiscoverer解析,如果解析的值不为空就解析成功了,继续下一个;如果没成功,则使用参数解析器argumentResolvers去解析,原则是谁支持supportsParameter()谁解析resolveArgument(),解析失败抛异常。如果最终都没能解析,抛异常。
SpringMVC默认注册的参数解析器在RequestMappingHandlerAdapter.getDefaultArgumentResolvers()方法中可以查看到

private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
    List<HandlerMethodArgumentResolver> resolvers = new ArrayList<HandlerMethodArgumentResolver>();

    // Annotation-based argument resolution
    resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
    resolvers.add(new RequestParamMapMethodArgumentResolver());
    resolvers.add(new PathVariableMethodArgumentResolver());
    resolvers.add(new PathVariableMapMethodArgumentResolver());
    resolvers.add(new MatrixVariableMethodArgumentResolver());
    resolvers.add(new MatrixVariableMapMethodArgumentResolver());
    resolvers.add(new ServletModelAttributeMethodProcessor(false));
    resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters()));
    resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters()));
    resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));
    resolvers.add(new RequestHeaderMapMethodArgumentResolver());
    resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));
    resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));

    // Type-based argument resolution
    resolvers.add(new ServletRequestMethodArgumentResolver());
    resolvers.add(new ServletResponseMethodArgumentResolver());
    resolvers.add(new HttpEntityMethodProcessor(getMessageConverters()));
    resolvers.add(new RedirectAttributesMethodArgumentResolver());
    resolvers.add(new ModelMethodProcessor());
    resolvers.add(new MapMethodProcessor());
    resolvers.add(new ErrorsMethodArgumentResolver());
    resolvers.add(new SessionStatusMethodArgumentResolver());
    resolvers.add(new UriComponentsBuilderMethodArgumentResolver());

    // Custom arguments
    if (getCustomArgumentResolvers() != null) {
        resolvers.addAll(getCustomArgumentResolvers());
    }

    // Catch-all
    resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
    resolvers.add(new ServletModelAttributeMethodProcessor(true));

    return resolvers;
}

很多啊,我们这里看几个常用的参数解析器。

  1. 注解注入Model接口的 -- ModelMethodProcessor
  2. 根据@RequestParam注解解析的 -- RequestParamMethodArgumentResolver
  3. 从请求中直接根据key直接注入value的 -- RequestParamMethodArgumentResolver
  4. 把请求参数封装成一个对象的 -- ServletModelAttributeMethodProcessor
  5. 根据@PathVariable注解解析的 -- PathVariableMethodArgumentResolver
  6. 直接注入HttpServletRequest对象的 -- ServletRequestMethodArgumentResolver
  7. 直接注入HttpServletResponse对象的 -- ServletResponseMethodArgumentResolver
  8. 根据@RequestBody注解解析的 -- RequestResponseBodyMethodProcessor

代码就不再细致读了。

另外,细心的你可能发现了,RequestParamMethodArgumentResolverServletModelAttributeMethodProcessor分别加入了两次,只是构造方法带的参数一个是true,一个是false。那么truefalse是控制什么的呢?

  • RequestParamMethodArgumentResolver构造方法中的第2个参数是useDefaultResolution,翻译过来是“使用默认的解析方式”,其实是说是否处理简单类型,false表示不处理,true表示处理。
    也就是说,如果useDefaultResolutionfalse,就只处理那些参数前面带@RequestParam注解的;
    如果是useDefaultResolutiontrue,也可以处理那些简单类型。简单类型都包括什么呢?RequestParamMethodArgumentResolver.supportsParameter(MethodParameter parameter)中有一行return BeanUtils.isSimpleProperty(paramType);,明白了吧?
  • ServletModelAttributeMethodProcessor构造方法中的参数是annotationNotRequired,翻译过来是“是否必须有注解”。是true,则只解析带@ModelAttribute的参数;是false,则可以解析那种POJO类型的参数。
    其实这么说也不是太准确,由于new ServletModelAttributeMethodProcessor(true)是最后一个参数解析器,如果前面的解析器都不支持,就只能由它来处理了,通常情况下,由它去解析一个POJO参数。

这里多说一句。我们在spring-mvc.xml中还注册了一个参数解析器,直接贴源码了

public class SubjectMethodArgumentResolver implements HandlerMethodArgumentResolver {

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.hasParameterAnnotation(Subject.class);
    }

    @Override
    public Object resolveArgument(MethodParameter parameter,
            ModelAndViewContainer mavContainer, NativeWebRequest webRequest,
            WebDataBinderFactory binderFactory) throws Exception {
        return SubjectUtils.getSubject();
    }

}

是干什么的,就不用解释了吧?它在resolvers.addAll(getCustomArgumentResolvers());中被加入到参数解析器List中去。
这里有个问题要思考一下,getCustomArgumentResolvers()也就是直接返回this.customArgumentResolvers。那么设置this.customArgumentResolvers值的地方只有一处,那就是setCustomArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers)方法。在哪里调用了这个方法呢?我们使用Eclipse的"Open Call Hierarchy"功能,看到它被org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport.requestMappingHandlerAdapter()调用了,但显然这是使用Java Config配置时才会执行的代码。那么,如果不是编译时指定了调用关系,就一定是通过反射调用的。这个思路,对于分析代码间的调用关系至关重要。那么,接下来的问题是,如果是反射调用的,这也就不好找了。还记得我们之前讲过命名空间吗?SubjectMethodArgumentResolver就是通过<mvc:argument-resolvers />配置的,自然就去相对应的解析类中找啊。具体我就不一步步去找了,调用是在这里AnnotationDrivenBeanDefinitionParser.getArgumentResolvers(Element element, ParserContext parserContext)

private ManagedList<?> getArgumentResolvers(Element element, ParserContext parserContext) {
    Element resolversElement = DomUtils.getChildElementByTagName(element, "argument-resolvers");
    if (resolversElement != null) {
        ManagedList<BeanDefinitionHolder> argumentResolvers = extractBeanSubElements(resolversElement, parserContext);
        return wrapWebArgumentResolverBeanDefs(argumentResolvers, parserContext);
    }
    return null;
}

然后这个方法的返回值,通过BeanWrapper的方式注入到customArgumentResolvers属性中,也就是通过反射,调用了RequestMappingHandlerAdapter.setCustomArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers),代码如下

RootBeanDefinition handlerAdapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class);

ManagedList<?> argumentResolvers = getArgumentResolvers(element, parserContext);

if (argumentResolvers != null) {
    handlerAdapterDef.getPropertyValues().add("customArgumentResolvers", argumentResolvers);
}

好,接着看InvocableHandlerMethod.doInvoke(Object... args)

protected Object doInvoke(Object... args) throws Exception {
    ReflectionUtils.makeAccessible(getBridgedMethod());
    try {
        return getBridgedMethod().invoke(getBean(), args);
    }
    catch (IllegalArgumentException ex) {
        assertTargetBean(getBridgedMethod(), getBean(), args);
        throw new IllegalStateException(getInvocationErrorMessage(ex.getMessage(), args), ex);
    }
    catch (InvocationTargetException ex) {
        // Unwrap for HandlerExceptionResolvers ...
        Throwable targetException = ex.getTargetException();
        if (targetException instanceof RuntimeException) {
            throw (RuntimeException) targetException;
        }
        else if (targetException instanceof Error) {
            throw (Error) targetException;
        }
        else if (targetException instanceof Exception) {
            throw (Exception) targetException;
        }
        else {
            String msg = getInvocationErrorMessage("Failed to invoke controller method", args);
            throw new IllegalStateException(msg, targetException);
        }
    }
}

这才是最终方法调用的核心中的核心,其实超级简单,就是通过反射调用方法。好了,这时我们获得了方法调用完成的返回值了。

setResponseStatus(ServletWebRequest webRequest)
直接上源码

private void setResponseStatus(ServletWebRequest webRequest) throws IOException {
    if (this.responseStatus == null) {
        return;
    }
    if (StringUtils.hasText(this.responseReason)) {
        webRequest.getResponse().sendError(this.responseStatus.value(), this.responseReason);
    }
    else {
        webRequest.getResponse().setStatus(this.responseStatus.value());
    }
    // to be picked up by the RedirectView
    webRequest.getRequest().setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, this.responseStatus);
}

this.responseStatusthis.responseReason的值,我们之前在initResponseStatus()中分析了,如果方法上有@ResponseStatus注解,这两个变量才有值。

this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest)
实际调用的是HandlerMethodReturnValueHandlerComposite.handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest)。看源码

@Override
public void handleReturnValue(Object returnValue, MethodParameter returnType,
        ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
    HandlerMethodReturnValueHandler handler = getReturnValueHandler(returnType);
    Assert.notNull(handler, "Unknown return value type [" + returnType.getParameterType().getName() + "]");
    handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}

private HandlerMethodReturnValueHandler getReturnValueHandler(MethodParameter returnType) {
    for (HandlerMethodReturnValueHandler returnValueHandler : returnValueHandlers) {
        if (logger.isTraceEnabled()) {
            logger.trace("Testing if return value handler [" + returnValueHandler + "] supports [" +
                    returnType.getGenericParameterType() + "]");
        }
        if (returnValueHandler.supportsReturnType(returnType)) {
            return returnValueHandler;
        }
    }
    return null;
}

就是先找到那个能处理返回值的HandlerMethodReturnValueHandler,再调用它的handleReturnValue()方法。分析到这就没法往下看了,因为要根据返回值的具体情况具体分析了。
SpringMVC默认的返回值解析器是在RequestMappingHandlerAdapter.getDefaultReturnValueHandlers()方法中可以查看到

private List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() {
    List<HandlerMethodReturnValueHandler> handlers = new ArrayList<HandlerMethodReturnValueHandler>();

    // Single-purpose return value types
    handlers.add(new ModelAndViewMethodReturnValueHandler());
    handlers.add(new ModelMethodProcessor());
    handlers.add(new ViewMethodReturnValueHandler());
    handlers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.contentNegotiationManager));
    handlers.add(new HttpHeadersReturnValueHandler());
    handlers.add(new CallableMethodReturnValueHandler());
    handlers.add(new DeferredResultMethodReturnValueHandler());
    handlers.add(new AsyncTaskMethodReturnValueHandler(this.beanFactory));

    // Annotation-based return value types
    handlers.add(new ModelAttributeMethodProcessor(false));
    handlers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.contentNegotiationManager));

    // Multi-purpose return value types
    handlers.add(new ViewNameMethodReturnValueHandler());
    handlers.add(new MapMethodProcessor());

    // Custom return value types
    if (getCustomReturnValueHandlers() != null) {
        handlers.addAll(getCustomReturnValueHandlers());
    }

    // Catch-all
    if (!CollectionUtils.isEmpty(getModelAndViewResolvers())) {
        handlers.add(new ModelAndViewResolverMethodReturnValueHandler(getModelAndViewResolvers()));
    }
    else {
        handlers.add(new ModelAttributeMethodProcessor(true));
    }

    return handlers;
}

那我们就看2个常用返回值处理。一种是返回视图名,然后返回一个JSP。一种是方法加了@ResponseBody注解,返回Json的。

  1. ViewNameMethodReturnValueHandler
public class ViewNameMethodReturnValueHandler implements HandlerMethodReturnValueHandler {

    @Override
    public boolean supportsReturnType(MethodParameter returnType) {
        Class<?> paramType = returnType.getParameterType();
        return (void.class.equals(paramType) || String.class.equals(paramType));
    }

    @Override
    public void handleReturnValue(Object returnValue, MethodParameter returnType,
            ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {

        if (returnValue == null) {
            return;
        }
        else if (returnValue instanceof String) {
            String viewName = (String) returnValue;
            mavContainer.setViewName(viewName);
            if (isRedirectViewName(viewName)) {
                mavContainer.setRedirectModelScenario(true);
            }
        }
        else {
            // should not happen
            throw new UnsupportedOperationException("Unexpected return type: " +
                    returnType.getParameterType().getName() + " in method: " + returnType.getMethod());
        }
    }

    /**
     * Whether the given view name is a redirect view reference.
     * @param viewName the view name to check, never {@code null}
     * @return "true" if the given view name is recognized as a redirect view
     * reference; "false" otherwise.
     */
    protected boolean isRedirectViewName(String viewName) {
        return viewName.startsWith("redirect:");
    }

}

代码很简单,直接实现了HandlerMethodReturnValueHandler接口

  • 判断是否支持。支持void和String的返回值
  • 处理。返回值即是视图名。
  1. RequestResponseBodyMethodProcessor 嗯?它怎么又出现了,在解析@RequestBody参数时就有它。RequestResponseBodyMethodProcessor就是带有2个功能,解析@RequestBody的参数和@ResponseBody的返回值。从它的继承结构中也可以证实这一点。
    image
    这里我们重点关注返回值的部分。
@Override
public boolean supportsReturnType(MethodParameter returnType) {
    return (AnnotationUtils.findAnnotation(returnType.getContainingClass(), ResponseBody.class) != null ||
            returnType.getMethodAnnotation(ResponseBody.class) != null);
}

@Override
public void handleReturnValue(Object returnValue, MethodParameter returnType,
        ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
        throws IOException, HttpMediaTypeNotAcceptableException {
    mavContainer.setRequestHandled(true);
    if (returnValue != null) {
        writeWithMessageConverters(returnValue, returnType, webRequest);
    }
}

代码很简单,只说handleReturnValue(),首先把ModelAndViewContainer中的请求处理完成标识设置为true。然后调用了writeWithMessageConverters()方法,这个方法在其父类AbstractMessageConverterMethodProcessor

protected <T> void writeWithMessageConverters(T returnValue, MethodParameter returnType, NativeWebRequest 
    webRequest) throws IOException, HttpMediaTypeNotAcceptableException {
    ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
    ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
    writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}

接下来看AbstractMessageConverterMethodArgumentResolver.createInputMessage(NativeWebRequest webRequest)

protected ServletServerHttpRequest createInputMessage(NativeWebRequest webRequest) {
    HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
    return new ServletServerHttpRequest(servletRequest);
}

将原始的HttpServletRequest封装到ServletServerHttpRequest中。

然后看AbstractMessageConverterMethodProcessor.createOutputMessage(NativeWebRequest webRequest)

protected ServletServerHttpResponse createOutputMessage(NativeWebRequest webRequest) {
    HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
    return new ServletServerHttpResponse(response);
}

将原始的HttpServletResponse封装到ServletServerHttpResponse中。

最后看AbstractMessageConverterMethodProcessor.writeWithMessageConverters(T returnValue, MethodParameter returnType, ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)

protected <T> void writeWithMessageConverters(T returnValue, MethodParameter returnType,
        ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
        throws IOException, HttpMediaTypeNotAcceptableException {

    Class<?> returnValueClass = returnValue.getClass();
    HttpServletRequest servletRequest = inputMessage.getServletRequest();
    List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(servletRequest);
    List<MediaType> producibleMediaTypes = getProducibleMediaTypes(servletRequest, returnValueClass);

    Set<MediaType> compatibleMediaTypes = new LinkedHashSet<MediaType>();
    for (MediaType requestedType : requestedMediaTypes) {
        for (MediaType producibleType : producibleMediaTypes) {
            if (requestedType.isCompatibleWith(producibleType)) {
                compatibleMediaTypes.add(getMostSpecificMediaType(requestedType, producibleType));
            }
        }
    }
    if (compatibleMediaTypes.isEmpty()) {
        throw new HttpMediaTypeNotAcceptableException(producibleMediaTypes);
    }

    List<MediaType> mediaTypes = new ArrayList<MediaType>(compatibleMediaTypes);
    MediaType.sortBySpecificityAndQuality(mediaTypes);

    MediaType selectedMediaType = null;
    for (MediaType mediaType : mediaTypes) {
        if (mediaType.isConcrete()) {
            selectedMediaType = mediaType;
            break;
        }
        else if (mediaType.equals(MediaType.ALL) || mediaType.equals(MEDIA_TYPE_APPLICATION)) {
            selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
            break;
        }
    }

    if (selectedMediaType != null) {
        selectedMediaType = selectedMediaType.removeQualityValue();
        for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
            if (messageConverter.canWrite(returnValueClass, selectedMediaType)) {
                ((HttpMessageConverter<T>) messageConverter).write(returnValue, selectedMediaType, outputMessage);
                if (logger.isDebugEnabled()) {
                    logger.debug("Written [" + returnValue + "] as \"" + selectedMediaType + "\" using [" +
                            messageConverter + "]");
                }
                return;
            }
        }
    }
    throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes);
}

经过一系列的处理,最后是选择某个messageConverter,可以先通过canWrite()方法,看行不行,行的话,调用这个messageConverterwrite()。关于HttpMessageConverter的问题,前面我们分析了,是在RequestMappingHandlerAdapter的无参构造方法里初始化的。

public RequestMappingHandlerAdapter() {
    StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
    stringHttpMessageConverter.setWriteAcceptCharset(false);  // see SPR-7316

    this.messageConverters = new ArrayList<HttpMessageConverter<?>>(4);
    this.messageConverters.add(new ByteArrayHttpMessageConverter());
    this.messageConverters.add(stringHttpMessageConverter);
    this.messageConverters.add(new SourceHttpMessageConverter<Source>());
    this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());
}

但是!这就证明,到了AbstractMessageConverterMethodProcessor.writeWithMessageConverters()中,还是这4个吗?还是命名空间,想到了吧?加载<mvc:annotation-driven />可能会调用RequestMappingHandlerAdapter.setMessageConverters(List<HttpMessageConverter<?>> messageConverters)方法的。好,我们去看看。

@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {

    // 只保留核心代码
    ManagedList<?> messageConverters = getMessageConverters(element, source, parserContext);

	RootBeanDefinition handlerAdapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class);
    
	handlerAdapterDef.getPropertyValues().add("messageConverters", messageConverters);
	
	parserContext.registerComponent(new BeanComponentDefinition(handlerMappingDef, methodMappingName));
	return null;
}

private ManagedList<?> getMessageConverters(Element element, Object source, ParserContext parserContext) {
	Element convertersElement = DomUtils.getChildElementByTagName(element, "message-converters");
	ManagedList<? super Object> messageConverters = new ManagedList<Object>();
	if (convertersElement != null) {
		messageConverters.setSource(source);
		for (Element beanElement : DomUtils.getChildElementsByTagName(convertersElement, "bean", "ref")) {
			Object object = parserContext.getDelegate().parsePropertySubElement(beanElement, null);
			messageConverters.add(object);
		}
	}

	if (convertersElement == null || Boolean.valueOf(convertersElement.getAttribute("register-defaults"))) {
		messageConverters.setSource(source);
		messageConverters.add(createConverterDefinition(ByteArrayHttpMessageConverter.class, source));

		RootBeanDefinition stringConverterDef = createConverterDefinition(StringHttpMessageConverter.class, source);
		stringConverterDef.getPropertyValues().add("writeAcceptCharset", false);
		messageConverters.add(stringConverterDef);

		messageConverters.add(createConverterDefinition(ResourceHttpMessageConverter.class, source));
		messageConverters.add(createConverterDefinition(SourceHttpMessageConverter.class, source));
		messageConverters.add(createConverterDefinition(AllEncompassingFormHttpMessageConverter.class, source));

		if (romePresent) {
			messageConverters.add(createConverterDefinition(AtomFeedHttpMessageConverter.class, source));
			messageConverters.add(createConverterDefinition(RssChannelHttpMessageConverter.class, source));
		}
		if (jaxb2Present) {
			messageConverters.add(createConverterDefinition(Jaxb2RootElementHttpMessageConverter.class, source));
		}
		if (jackson2Present) {
			messageConverters.add(createConverterDefinition(MappingJackson2HttpMessageConverter.class, source));
		}
		else if (jacksonPresent) {
			messageConverters.add(createConverterDefinition(
					org.springframework.http.converter.json.MappingJacksonHttpMessageConverter.class, source));
		}
	}
	return messageConverters;
}

果然.. 其实RequestMappingHandlerAdapter中很多HttpMessageConverter呢。后续就不再去分析了,处理ResponseBody消息转换的类应该是MappingJackson2HttpMessageConverter(如果你加入了Jackson2的jar的话)。

  • (10) 返回ModelAndView对象
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
        ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
    modelFactory.updateModel(webRequest, mavContainer);
    if (mavContainer.isRequestHandled()) {
        return null;
    }
    ModelMap model = mavContainer.getModel();
    ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model);
    if (!mavContainer.isViewReference()) {
        mav.setView((View) mavContainer.getView());
    }
    if (model instanceof RedirectAttributes) {
        Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
        HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
        RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
    }
    return mav;
}

就是把ModelAndViewContainer中一些信息转移到ModelAndView中。如果请求已经处理完了(比如返回json),就返回null。如果是重定向的,就把flashMap中的属性,放到请求的属性里。

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

如果没视图就设置一个默认的。什么时候会没视图呢?前面分析了,当方法返回值是void时,就没有视图。见public class ViewNameMethodReturnValueHandler.supportsReturnType(MethodParameter returnType)。那么,看看怎么获取默认的视图吧。

protected String getDefaultViewName(HttpServletRequest request) throws Exception {
    return this.viewNameTranslator.getViewName(request);
}

DispatcherServletinitRequestToViewNameTranslator(ApplicationContext context)中初始化了viewNameTranslator了,实现类是DefaultRequestToViewNameTranslator,细节就不再看了。

mappedHandler.applyPostHandle(processedRequest, response, mv)

调用所有拦截器的postHandle()方法

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);
    }
}
  1. 如果有异常,就处理异常。
  2. 如果ModelAndView不为空,调用render()方法。
  3. 最后通过mappedHandler.triggerAfterCompletion(request, response, null);调用所有拦截器的afterCompletion()方法。

重点看下render()方法

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()) { ---- [1]
        // We need to resolve the view name.
        view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request); ---- [2]
        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 {
        view.render(mv.getModelInternal(), request, response); ---- [3]
    }
    catch (Exception ex) {
        if (logger.isDebugEnabled()) {
            logger.debug("Error rendering view [" + view + "] in DispatcherServlet with name '" +
                    getServletName() + "'", ex);
        }
        throw ex;
    }
}

核心代码逻辑分析

  • [1] 判断视图是否是引用的(isReference,这个含义不好翻..)?看下源码
public boolean isReference() {
    return (this.view instanceof String);
}

显然,这里是返回true。即表明接下来要通过视图解析器解析。

  • [2] 解析视图名
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;
}

也是从一堆viewResolvers中找一个能解析出的视图不为空的,就返回它。那我们现在有几个viewResolvers呢?其实,就一个,就是我们在spring-mvc.xml文件中配置的。

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" 
    p:viewClass="org.springframework.web.servlet.view.JstlView"
    p:prefix="/WEB-INF/views/" 
    p:suffix=".jsp" />

那我们就去InternalResourceViewResolver中看看resolveViewName()。看方法实现之前,先看下继承结构。
image
继承了WebApplicationContextSupport,获得了Spring容器和ServletContext,实现Ordered接口,可以排序。看下ViewResolver接口的定义

public interface ViewResolver {
    View resolveViewName(String viewName, Locale locale) throws Exception;
}

只有一个方法,根据视图名,解析出一个View对象。在spring-mvc.xml,我们配置了一个JstlView,看下它的继承结构:
image
注意InitializingBean接口,会初始化一些东西。

好,看下InternalResourceViewResolver.resolveViewName()

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

跳过那些跟缓存相关的逻辑,重点关注view = createView(viewName, locale);。具体由子类实现。 看UrlBasedViewResolver.createView(String viewName, Locale locale)

@Override
protected View createView(String viewName, Locale locale) throws Exception {
    // If this resolver is not supposed to handle the given view,
    // return null to pass on to the next resolver in the chain.
    if (!canHandle(viewName, locale)) {
        return null;
    }
    // Check for special "redirect:" prefix.
    if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
        String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
        RedirectView view = new RedirectView(redirectUrl, isRedirectContextRelative(), isRedirectHttp10Compatible());
        return applyLifecycleMethods(viewName, view);
    }
    // Check for special "forward:" prefix.
    if (viewName.startsWith(FORWARD_URL_PREFIX)) {
        String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());
        return new InternalResourceView(forwardUrl);
    }
    // Else fall back to superclass implementation: calling loadView.
    return super.createView(viewName, locale);
}

明确了两种处理方式:

  1. redirect:开头的,使用RedirectView
  2. forward:开头的,使用InternalResourceView 如果都不是,就调用父类的createView()方法。那么就是AbstractCachingViewResolver.createView(String viewName, Locale locale)
protected View createView(String viewName, Locale locale) throws Exception {
    return loadView(viewName, locale);
}
protected abstract View loadView(String viewName, Locale locale) throws Exception;

呃.. 什么都没做,又交给子类了。看UrlBasedViewResolver.loadView(String viewName, Locale locale)

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

private View applyLifecycleMethods(String viewName, AbstractView view) {
    return (View) getApplicationContext().getAutowireCapableBeanFactory().initializeBean(view, viewName);
}

重点看buildView(String viewName)

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

viewClass肯定就是我们配置的org.springframework.web.servlet.view.JstlView了,通过反射实例化。
url就是根据前缀+试图名+后缀拼接起来的。
contentType没指定,肯定就是null了。
下面就是设置一些属性了。好了,终于得到View了。

  • [3] 渲染视图。看源码AbstractView.render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
@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<String, Object> mergedModel = createMergedOutputModel(model, request, response);
    prepareResponse(request, response);
    renderMergedOutputModel(mergedModel, request, response);
}

只有3行核心代码,胜利在望了~~

  1. 创建合并的输出模型
protected Map<String, Object> createMergedOutputModel(Map<String, ?> model, HttpServletRequest request,
        HttpServletResponse response) {

    @SuppressWarnings("unchecked")
    Map<String, Object> pathVars = (this.exposePathVariables ?
            (Map<String, Object>) request.getAttribute(View.PATH_VARIABLES) : null);

    // Consolidate static and dynamic model attributes.
    int size = this.staticAttributes.size();
    size += (model != null ? model.size() : 0);
    size += (pathVars != null ? pathVars.size() : 0);

    Map<String, Object> mergedModel = new LinkedHashMap<String, Object>(size);
    mergedModel.putAll(this.staticAttributes);
    if (pathVars != null) {
        mergedModel.putAll(pathVars);
    }
    if (model != null) {
        mergedModel.putAll(model);
    }

    // Expose RequestContext?
    if (this.requestContextAttribute != null) {
        mergedModel.put(this.requestContextAttribute, createRequestContext(request, response, mergedModel));
    }

    return mergedModel;
}

逻辑比较清楚,就是把staticAttributespathVars和业务代码中的Model(Map形式)合并到一个LinkedHashMap中。

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

protected boolean generatesDownloadContent() {
    return false;
}

判断是不是需要下载,默认是false。而子类JstlView也没有重写此方法。

  1. 渲染合并的输出模型 在AbstractView中,这是一个抽象方法,留给了子类实现。看``
@Override
protected void renderMergedOutputModel(
        Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {

    // Determine which request handle to expose to the RequestDispatcher.
    HttpServletRequest requestToExpose = getRequestToExpose(request); ---- (1)

    // Expose the model object as request attributes.
    exposeModelAsRequestAttributes(model, requestToExpose); ---- (2)

    // Expose helpers as request attributes, if any.
    exposeHelpers(requestToExpose); ---- (3)

    // Determine the path for the request dispatcher.
    String dispatcherPath = prepareForRendering(requestToExpose, response); ---- (4)

    // Obtain a RequestDispatcher for the target resource (typically a JSP).
    RequestDispatcher rd = getRequestDispatcher(requestToExpose, dispatcherPath);  ---- (5)
    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(requestToExpose, response)) {  ---- (6)
        response.setContentType(getContentType());
        if (logger.isDebugEnabled()) {
            logger.debug("Including resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
        }
        rd.include(requestToExpose, 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(requestToExpose, response); ---- (7)
    }
}
  • (1) 直接看源码
protected HttpServletRequest getRequestToExpose(HttpServletRequest originalRequest) {
    if (this.exposeContextBeansAsAttributes || this.exposedContextBeanNames != null) {
        return new ContextExposingHttpServletRequest(
                originalRequest, getWebApplicationContext(), this.exposedContextBeanNames);
    }
    return originalRequest;
}

this.exposeContextBeansAsAttributes默认值是false,而且this.exposedContextBeanNames也是null,所以直接返回原始的request对象。

  • (2) 看源码
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的key-value,都通过request.setAttribute(key, value)放到request中。

  • (3) 看源码 在InternelResourceView中,这是一个空方法,但JstlView重写了此方法。
@Override
protected void exposeHelpers(HttpServletRequest request) throws Exception {
	if (this.messageSource != null) {
		JstlUtils.exposeLocalizationContext(request, this.messageSource);
	}
	else {
		JstlUtils.exposeLocalizationContext(new RequestContext(request, getServletContext()));
	}
}

给请求绑定了处理国际化资源的对象。

  • (4) 准备渲染。艾玛,终于要渲染了..
protected String prepareForRendering(HttpServletRequest request, HttpServletResponse response)
        throws Exception {
    String path = getUrl();
    if (this.preventDispatchLoop) {
        String uri = request.getRequestURI();
        if (path.startsWith("/") ? uri.equals(path) : uri.equals(StringUtils.applyRelativePath(uri, path))) {
            throw new ServletException("Circular view path [" + path + "]: would dispatch back " +
                    "to the current handler URL [" + uri + "] again. Check your ViewResolver setup! " +
                    "(Hint: This may be the result of an unspecified view, due to default view name generation.)");
        }
    }
    return path;
}

获取path,path之前计算过了,就是前缀+视图名+后缀。这里说下preventDispatchLoop,默认值是true,变量名翻译过来叫"避免循环分发",意思是,比如你请求的url是/test,但返回的path的结果还是/test,那SpringMVC肯定还要再去转发,这就形成循环分发了。

  • (5) 获取请求分发器。
protected RequestDispatcher getRequestDispatcher(HttpServletRequest request, String path) {
    return request.getRequestDispatcher(path);
}

这就是纯Servlet的基础知识了。

  • (6) 看源码
protected boolean useInclude(HttpServletRequest request, HttpServletResponse response) {
	return (this.alwaysInclude || WebUtils.isIncludeRequest(request) || response.isCommitted());
}

不多说了,3个都是false

  • (7) 直接是Servlet的基础API了,请求转发。

至此,SpringMVC的代码就全部分析完了。


转载于:https://my.oschina.net/ojeta/blog/716550

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值