上一篇:Spring MVC之DispatchServlet Spring MVC之Web请求处理流程** 下一篇:本系列完

29436 2020-04-02 2020-09-12

前言:Spring MVC系列收尾篇,本篇将详细讨论Spring MVC的启动/加载、处理请求的具体流程。我们先从一个简单的Spring MVC日志开始分析(这里假设读者已经仔细阅读完了前面的文章,且对Spring源码有一定深度的了解,否则会看得一脸闷逼)。

一、配置

1、log4j配置

先附上日志配置,如下

log4j.rootLogger=trace, stdout
log4j.logger.org.springframework=trace, stdout
# 去掉和rootLogger重合的部分
log4j.additivity.org.springframework=false

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
# 日期 线程名(保留后3位) 日志级别 类路径(只保留最后2个父包) 行号 输出信息 换行
log4j.appender.stdout.layout.conversionPattern=%d{HH:mm:ss.SSS} [%.3t][%p] %C{3} %L: %m%n

2、web.xml

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--        <servlet-class>xiaokui.test.TestServlet</servlet-class>-->
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>dispatcher</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

3、applicationContext.xml

<mvc:annotation-driven/>
<aop:aspectj-autoproxy proxy-target-class="true" expose-proxy="true"/>
<context:component-scan base-package="xiaokui.*"/>

4、dispatcher-servlet.xml

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

其他bean我们并不关心,大致有个Controller、Service、Dao结构就行了。

下面将一行行、一段段进行解读一个真实Spring MVC项目的日志输出(中间没有任何断层,且为了行文方便,故意拆分为多个逻辑)。

二、applicationContext启动流程概览

我们先来看下由applicationContext.xml搞出来的事情。

# 如果有部分术语看不懂,请先查看之前的文章,这里假设读者已经对Spring MVC内部已经有了一个大概的了解了
# 通过ContextLoaderListener监听,开始刷新WebApplicationContext
# 注释采取python格式,因为# 比 // 好打

# 监听器成功引导了Spring IoC容器
10:34:58.340 [0.1][INFO] web.context.ContextLoader 271: Root WebApplicationContext: initialization started
10:34:58.413 [0.1][TRACE] context.support.AbstractApplicationContext 592: Refreshing Root WebApplicationContext, started on Thu May 07 10:34:58 CST 2020

# 把注意了转移到了对applicationContext的解析,里面定义个整个项目中对WebApplicationContext的配置
10:34:58.475 [0.1][TRACE] factory.xml.XmlBeanDefinitionReader 318: Loading XML bean definitions from ServletContext resource [/WEB-INF/applicationContext.xml]
10:34:58.477 [0.1][TRACE] factory.xml.DefaultDocumentLoader 74: Using JAXP provider [com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl]
10:34:58.511 [0.1][TRACE] factory.xml.PluggableSchemaResolver 111: Trying to resolve XML entity with public id [null] and system id [http://www.springframework.org/schema/beans/spring-beans.xsd]
10:34:58.512 [0.1][TRACE] factory.xml.PluggableSchemaResolver 154: Loading schema mappings from [META-INF/spring.schemas]
10:34:58.516 [0.1][TRACE] factory.xml.PluggableSchemaResolver 160: Loaded schema mappings: {https://www.springframework.org/schema/jdbc/spring-jdbc.xsd=org/springframework/jdbc/config/spring-jdbc.xsd, https://www.springframework.org/schema/aop/spring-aop.xsd=org/springframework/aop/config/spring-aop.xsd, https://www.springframework.org/schema/tx/spring-tx-4.3.xsd=org/springframework/transaction/config/spring-tx.xsd.....以下省略50行

10:34:58.520 [0.1][TRACE] factory.xml.PluggableSchemaResolver 128: Found XML schema [http://www.springframework.org/schema/beans/spring-beans.xsd] in classpath: org/springframework/beans/factory/xml/spring-beans.xsd.....以下省略50行
10:34:58.623 [0.1][TRACE] factory.xml.DefaultNamespaceHandlerResolver 160: Loading NamespaceHandler mappings from [META-INF/spring.handlers]
                                                                                       
# 已经找到了对于的自定义标签的NamespaceHandler,它们都继承自NamespaceHandlerSupport(相关知识请参考xml自定义标签解析一文)
10:34:58.625 [0.1][TRACE] factory.xml.DefaultNamespaceHandlerResolver 166: Loaded NamespaceHandler mappings: {http://www.springframework.org/schema/p=org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler, http://www.springframework.org/schema/mvc=org.springframework.web.servlet.config.MvcNamespaceHandler, http://www.springframework.org/schema/util=org.springframework.beans.factory.xml.UtilNamespaceHandler, http://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler, http://www.springframework.org/schema/aop=org.springframework.aop.config.AopNamespaceHandler, http://www.springframework.org/schema/oxm=org.springframework.oxm.config.OxmNamespaceHandler, http://www.springframework.org/schema/jdbc=org.springframework.jdbc.config.JdbcNamespaceHandler, http://www.springframework.org/schema/cache=org.springframework.cache.config.CacheNamespaceHandler, http://www.springframework.org/schema/c=org.springframework.beans.factory.xml.SimpleConstructorNamespaceHandler, http://www.springframework.org/schema/tx=org.springframework.transaction.config.TxNamespaceHandler, http://www.springframework.org/schema/jms=org.springframework.jms.config.JmsNamespaceHandler, http://www.springframework.org/schema/task=org.springframework.scheduling.config.TaskNamespaceHandler, http://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler, http://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler}

通过debug可以发现(在其公共父类上NamespaceHandlerSupport打上断点即可),Spring解析xml是从上往下依次解析的,在这里第一个被解析的是aop标签,虽然这里的解析顺序并不会影响解析的正确结果。

1、引导

这里我们简单概述一下,好对整个流程有个大概的了解,更多细节请参考前面的相关文章:

  1. Tomcat容器启动,web.xml配置项入口。
  2. ContextLoaderListener作为ServeletContext初始化的监听器,在Servlet环境初始化后会进入到这个类,这个类会创建Spring中的IoC容器,并根据context-param加载applicationContext.xml。
  3. 在applicationContext.xml中的RootContext初始化后,然后再由Servlet的配置去初始化DispatcherServlet,这里会按照相同的流程初始化dispatcher-servlet.xml,这里是ChildContext。
  4. ServletContext、DispatcherServlet初始化完成,Tomcat成功启动。

每次Context的初始化,都是以wac.refresh结尾的,这里面包含了Spring的核心逻辑,我们选取几个比较典型的关注点:

  1. 什么时候真正加载xml配置文件 - obtainFreshBeanFactory方法。
  2. 什么时候开始自定义标签的解析,如aop、context、mvc等自定义标签的解析 - obtainFreshBeanFactory。
  3. @Configuration注解类何时应用 - invokeBeanFactoryPostProcessors方法中的ConfigurationClassPostProcessor类负责解析。
  4. @Component注解类何时应用 - finishBeanFactoryInitialization方法。
  5. @Autowired、@Value注解何时应用 - registerBeanPostProcessors方法中的AutowiredAnnotationBeanPostProcessor负责解析。
  6. 动态代理何时应用 - finishBeanFactoryInitialization方法中AnnotationAwareAspectJAutoProxyCreator负责筛选。

2、wac.refresh**

refresh方法算是Context中的发动机了,重中之重。如果把Spring IoC当做人体中的骨架 + 躯干,各个子Bean比作血液 + 各个器官,那么Context中的refresh方法同大脑 + 心脏,统筹全局,精打细算,其重要性不言而喻(比喻可能不是很恰当,读者知有这么个概念即可,详情读者可参考前面的相关文章)。

// 第一次RootContext的刷新
// 来自父类AbstractApplicationContext
// 注意,这里分析就不再以ClassPathXmlApplicationContext,而是以XmlWebApplicationContext,两者大致流程是一样的,区别后者专门是为Web服务的
public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        // Prepare this context for refreshing.
        // 准备刷新的上下文环境,初始化前的准备工作,例如对系统属性或者环境变量进行准备及验证。
        prepareRefresh();

        // Tell the subclass to refresh the internal bean factory.
        // 告诉子类刷新内部bean工厂,初始化DefaultListableBeanFactory,并进行xml文件读取
        // 复用BeanFactory中的配置文件读取解析及其他功能,这一步之后,ClassPathXMLApplicationContext实际上已经包含了BeanFactory所提供的功能,也就是可以进行bean的提取等基础操作了
        // 这一步之后,实际上已经读取了aop标签内置的bean、mvc标签内置的bean以及context标签所扫描的需要bean
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

        // Prepare the bean factory for use in this context.
        // 对BeanFactory进行各种功能填充,例如增加对SPEL语言的支持、增加对属性编辑器的支持、设置了依赖功能可忽略的接口、注册一些固定依赖的属性、将相关环境变量及属性注册以单例模式注册
        prepareBeanFactory(beanFactory);

        try {
            // Allows post-processing of the bean factory in context subclasses.
            // 子类覆盖方法做前置处理
            // web重写:Register request/session scopes, a {@link ServletContextAwareProcessor}, etc.
            postProcessBeanFactory(beanFactory);

            // Invoke factory processors registered as beans in the context.
            // 激活各种BeanFactory处理器,针对BeanFactory做定制化,主要是BeanFactoryPostProcessor接口及其的子接口类的特殊处理
            // 区别于BeanFactoryPostProcessors,而BeanPostProcessors针对于bean的
            invokeBeanFactoryPostProcessors(beanFactory);

            // Register bean processors that intercept bean creation.
            // 注册拦截bean创建的bean处理器,这里只是注册,真正的调用是在getBean的时候
            registerBeanPostProcessors(beanFactory);

            // Initialize message source for this context.
            // 初始化应用消息广播器,并放入applicationEventMulticaster bean中
            initMessageSource();

            // Initialize event multicaster for this context.
            // 留个子类来初始化其他的bean
            initApplicationEventMulticaster();

            // Initialize other special beans in specific context subclasses.
            // 留个子类来初始化其他的bean
            onRefresh();

            // Check for listener beans and register them.
            // 在所有注册的bean种查找listener bean,注册到消息广播中
            registerListeners();

            // Instantiate all remaining (non-lazy-init) singletons.
            // 初始化剩下的单实例(非惰性)
            finishBeanFactoryInitialization(beanFactory);

            // Last step: publish corresponding event.
            // 完成刷新过程,通知生命周期处理器lifecycleProcessor刷新过程,同时发出ContextRefreshEvent通知别人
            finishRefresh();
        }
        catch (BeansException ex) {
            if (logger.isWarnEnabled()) {
                logger.warn("Exception encountered during context initialization - " +
                            "cancelling refresh attempt: " + ex);
            }

            // Destroy already created singletons to avoid dangling resources.
            destroyBeans();

            // Reset 'active' flag.
            cancelRefresh(ex);

            // Propagate exception to caller.
            throw ex;
        }
        finally {
				// Reset common introspection caches in Spring's core, since we
				// might not ever need metadata for singleton beans anymore...
				resetCommonCaches();
		}
    }
}

三、obtainFreshBeanFactory与applicationContext.xml

1、概述

在Tomcat容器成功引导了Spring IoC容器,即成功初始化了一个DefaultListableBeanFactory对象,后续就是如何找到Bean对象,如何将Bean对象放置进容器,如何从容器取出的过程了。

从前文得知,在Spring MVC中的ServletContext刷新的最后一步就是调用wac.refresh方法,而其中wac.refresh方法第一步就是加载applicationContext.xml,获取所有系统内置bean、用户bean,于是开始了自定义标签的解析过程了。

2、aop

1、关键代码

/**
 * {@code NamespaceHandler} for the {@code aop} namespace.
 *
 * <p>Provides a {@link org.springframework.beans.factory.xml.BeanDefinitionParser} for the
 * {@code <aop:config>} tag. A {@code config} tag can include nested
 * {@code pointcut}, {@code advisor} and {@code aspect} tags.
 *
 * <p>The {@code pointcut} tag allows for creation of named
 * {@link AspectJExpressionPointcut} beans using a simple syntax:
 * <pre class="code">
 * &lt;aop:pointcut id=&quot;getNameCalls&quot; expression=&quot;execution(* *..ITestBean.getName(..))&quot;/&gt;
 * </pre>
 *
 * <p>Using the {@code advisor} tag you can configure an {@link org.springframework.aop.Advisor}
 * and have it applied to all relevant beans in you {@link org.springframework.beans.factory.BeanFactory}
 * automatically. The {@code advisor} tag supports both in-line and referenced
 * {@link org.springframework.aop.Pointcut Pointcuts}:
 *
 * <pre class="code">
 * &lt;aop:advisor id=&quot;getAgeAdvisor&quot;
 *     pointcut=&quot;execution(* *..ITestBean.getAge(..))&quot;
 *     advice-ref=&quot;getAgeCounter&quot;/&gt;
 *
 * &lt;aop:advisor id=&quot;getNameAdvisor&quot;
 *     pointcut-ref=&quot;getNameCalls&quot;
 *     advice-ref=&quot;getNameCounter&quot;/&gt;</pre>
 *
 */
public class AopNamespaceHandler extends NamespaceHandlerSupport {

	/**
	 * Register the {@link BeanDefinitionParser BeanDefinitionParsers} for the
	 * '{@code config}', '{@code spring-configured}', '{@code aspectj-autoproxy}'
	 * and '{@code scoped-proxy}' tags.
	 */
	@Override
	public void init() {
		// In 2.0 XSD as well as in 2.1 XSD.
		registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
		registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
		registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());

		// Only in 2.0 XSD: moved to context namespace as of 2.1
		registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
	}

}

/**
 * {@link BeanDefinitionParser} for the {@code aspectj-autoproxy} tag,
 * enabling the automatic application of @AspectJ-style aspects found in
 * the {@link org.springframework.beans.factory.BeanFactory}.
 *
 */
class AspectJAutoProxyBeanDefinitionParser implements BeanDefinitionParser {

	@Override
	public BeanDefinition parse(Element element, ParserContext parserContext) {
        // 这里会注入一个AnnotationAwareAspectJAutoProxyCreator,用于支持AspectJ语法的动态代理
		AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext, element);
		extendBeanDefinition(element, parserContext);
		return null;
	}
}

@Nullable
public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(
        BeanDefinitionRegistry registry, @Nullable Object source) {

    return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
}

public static void registerAspectJAnnotationAutoProxyCreatorIfNecessary(
        ParserContext parserContext, Element sourceElement) {
	// 注册BeanDefinition
    BeanDefinition beanDefinition = AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(
            parserContext.getRegistry(), parserContext.extractSource(sourceElement));
    // 设置BeanDefinition中的相关设置属性
    useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);
    // 注册xml组件
    registerComponentIfNecessary(beanDefinition, parserContext);
}

2、小结

更为详细的讨论请参考Spring AOP一节,这里简单提一下:AnnotationAwareAspectJAutoProxyCreator实现了SmartInstantiationAwareBeanPostProcessor接口,并重写对应方法以使得当发现一个bean需要代理时,会返回创建的代理对象,而不是创建一个新的原Class类型的对象。关键代码如下

// 来源于AnnotationAwareAspectJAutoProxyCreator的父类 AbstractAutoProxyCreator
/**
 * Create a proxy with the configured interceptors if the bean is
 * identified as one to proxy by the subclass.
 * @see #getAdvicesAndAdvisorsForBean
 */
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
    if (bean != null) {
        Object cacheKey = getCacheKey(bean.getClass(), beanName);
        if (this.earlyProxyReferences.remove(cacheKey) != bean) {
            // 该bean是否符合Aspect或自定义的代理规则,以决定是否需要代理
            return wrapIfNecessary(bean, beanName, cacheKey);
        }
    }
    return bean;
}

这一行代码<aop:aspectj-autoproxy proxy-target-class="true" expose-proxy="true"/>,注册了一个AnnotationAwareAspectJAutoProxyCreator,让Spring支持动态代理,例如对@AspectJ、@After、@Before、@Around的支持。这里隐式注册的bean只有1个,如下

org.springframework.aop.config.internalAutoProxyCreator -> {RootBeanDefinition@3705} "Root bean: class [org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator]; scope=; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null"

3、mvc

1、关键代码

// 通过前面日志我们定位到了MvcNamespaceHandler
/**
 * {@link NamespaceHandler} for Spring MVC configuration namespace.
 */
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());
		registerBeanDefinitionParser("redirect-view-controller", new ViewControllerBeanDefinitionParser());
		registerBeanDefinitionParser("status-controller", new ViewControllerBeanDefinitionParser());
		registerBeanDefinitionParser("view-resolvers", new ViewResolversBeanDefinitionParser());
		registerBeanDefinitionParser("tiles-configurer", new TilesConfigurerBeanDefinitionParser());
		registerBeanDefinitionParser("freemarker-configurer", new FreeMarkerConfigurerBeanDefinitionParser());
		registerBeanDefinitionParser("groovy-configurer", new GroovyMarkupConfigurerBeanDefinitionParser());
		registerBeanDefinitionParser("script-template-configurer", new ScriptTemplateConfigurerBeanDefinitionParser());
		registerBeanDefinitionParser("cors", new CorsBeanDefinitionParser());
	}
}

/**
 * A {@link BeanDefinitionParser} that provides the configuration for the
 * {@code <annotation-driven/>} MVC namespace element.
 *
 * <p>This class registers the following {@link HandlerMapping HandlerMappings}:</p>
 * <ul>
 * <li>{@link RequestMappingHandlerMapping}
 * ordered at 0 for mapping requests to annotated controller methods.
 * <li>{@link BeanNameUrlHandlerMapping}
 * ordered at 2 to map URL paths to controller bean names.
 * </ul>
 *
 * <p><strong>Note:</strong> Additional HandlerMappings may be registered
 * as a result of using the {@code <view-controller>} or the
 * {@code <resources>} MVC namespace elements.
 *
 * <p>This class registers the following {@link HandlerAdapter HandlerAdapters}:
 * <ul>
 * <li>{@link RequestMappingHandlerAdapter}
 * for processing requests with annotated controller methods.
 * <li>{@link HttpRequestHandlerAdapter}
 * for processing requests with {@link HttpRequestHandler HttpRequestHandlers}.
 * <li>{@link SimpleControllerHandlerAdapter}
 * for processing requests with interface-based {@link Controller Controllers}.
 * </ul>
 *
 * <p>This class registers the following {@link HandlerExceptionResolver HandlerExceptionResolvers}:
 * <ul>
 * <li>{@link ExceptionHandlerExceptionResolver} for handling exceptions through
 * {@link org.springframework.web.bind.annotation.ExceptionHandler} methods.
 * <li>{@link ResponseStatusExceptionResolver} for exceptions annotated
 * with {@link org.springframework.web.bind.annotation.ResponseStatus}.
 * <li>{@link DefaultHandlerExceptionResolver} for resolving known Spring
 * exception types
 * </ul>
 *
 * <p>This class registers an {@link org.springframework.util.AntPathMatcher}
 * and a {@link org.springframework.web.util.UrlPathHelper} to be used by:
 * <ul>
 * <li>the {@link RequestMappingHandlerMapping},
 * <li>the {@link HandlerMapping} for ViewControllers
 * <li>and the {@link HandlerMapping} for serving resources
 * </ul>
 * Note that those beans can be configured by using the {@code path-matching}
 * MVC namespace element.
 *
 * <p>Both the {@link RequestMappingHandlerAdapter} and the
 * {@link ExceptionHandlerExceptionResolver} are configured with instances of
 * the following by default:
 * <ul>
 * <li>A {@link ContentNegotiationManager}
 * <li>A {@link DefaultFormattingConversionService}
 * <li>A {@link org.springframework.validation.beanvalidation.LocalValidatorFactoryBean}
 * if a JSR-303 implementation is available on the classpath
 * <li>A range of {@link HttpMessageConverter HttpMessageConverters} depending on which third-party
 * libraries are available on the classpath.
 * </ul>
 */
// 通过以上注释我们知道Spring帮我们内置了一大堆web组件
class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {

	public static final String HANDLER_MAPPING_BEAN_NAME = RequestMappingHandlerMapping.class.getName();

	public static final String HANDLER_ADAPTER_BEAN_NAME = RequestMappingHandlerAdapter.class.getName();

	public static final String CONTENT_NEGOTIATION_MANAGER_BEAN_NAME = "mvcContentNegotiationManager";


	private static final boolean javaxValidationPresent;

	private static boolean romePresent;

	private static final boolean jaxb2Present;

	private static final boolean jackson2Present;

	private static final boolean jackson2XmlPresent;

	private static final boolean jackson2SmilePresent;

	private static final boolean jackson2CborPresent;

	private static final boolean gsonPresent;

	static {
        // 判断某个类是否存在,类boot思想,约定大于配置
		ClassLoader classLoader = AnnotationDrivenBeanDefinitionParser.class.getClassLoader();
		javaxValidationPresent = ClassUtils.isPresent("javax.validation.Validator", classLoader);
		romePresent = ClassUtils.isPresent("com.rometools.rome.feed.WireFeed", classLoader);
		jaxb2Present = ClassUtils.isPresent("javax.xml.bind.Binder", classLoader);
		jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) &&
						ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
		jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader);
		jackson2SmilePresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.smile.SmileFactory", classLoader);
		jackson2CborPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.cbor.CBORFactory", classLoader);
		gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader);
	}

	@Override
	@Nullable
    // 我们稍稍过下这个方法,尝试去寻找我们需要关注的点
	public BeanDefinition parse(Element element, ParserContext context) {
		Object source = context.extractSource(element);
		XmlReaderContext readerContext = context.getReaderContext();

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

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

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

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

		configurePathMatchingProperties(handlerMappingDef, element, context);
		readerContext.getRegistry().registerBeanDefinition(HANDLER_MAPPING_BEAN_NAME, handlerMappingDef);

		RuntimeBeanReference corsRef = MvcNamespaceUtils.registerCorsConfigurations(null, context, source);
		handlerMappingDef.getPropertyValues().add("corsConfigurations", corsRef);

		RuntimeBeanReference conversionService = getConversionService(element, source, context);
		RuntimeBeanReference validator = getValidator(element, source, context);
		RuntimeBeanReference messageCodesResolver = getMessageCodesResolver(element);

		RootBeanDefinition bindingDef = new RootBeanDefinition(ConfigurableWebBindingInitializer.class);
		bindingDef.setSource(source);
		bindingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
		bindingDef.getPropertyValues().add("conversionService", conversionService);
		bindingDef.getPropertyValues().add("validator", validator);
		bindingDef.getPropertyValues().add("messageCodesResolver", messageCodesResolver);

		ManagedList<?> messageConverters = getMessageConverters(element, source, context);
		ManagedList<?> argumentResolvers = getArgumentResolvers(element, context);
		ManagedList<?> returnValueHandlers = getReturnValueHandlers(element, context);
		String asyncTimeout = getAsyncTimeout(element);
		RuntimeBeanReference asyncExecutor = getAsyncExecutor(element);
		ManagedList<?> callableInterceptors = getInterceptors(element, source, context, "callable-interceptors");
		ManagedList<?> deferredResultInterceptors = getInterceptors(element, source, context, "deferred-result-interceptors");

		RootBeanDefinition handlerAdapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class);
		handlerAdapterDef.setSource(source);
		handlerAdapterDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
		handlerAdapterDef.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
		handlerAdapterDef.getPropertyValues().add("webBindingInitializer", bindingDef);
		handlerAdapterDef.getPropertyValues().add("messageConverters", messageConverters);
		addRequestBodyAdvice(handlerAdapterDef);
		addResponseBodyAdvice(handlerAdapterDef);

		if (element.hasAttribute("ignore-default-model-on-redirect")) {
			Boolean ignoreDefaultModel = Boolean.valueOf(element.getAttribute("ignore-default-model-on-redirect"));
			handlerAdapterDef.getPropertyValues().add("ignoreDefaultModelOnRedirect", ignoreDefaultModel);
		}
		if (argumentResolvers != null) {
			handlerAdapterDef.getPropertyValues().add("customArgumentResolvers", argumentResolvers);
		}
		if (returnValueHandlers != null) {
			handlerAdapterDef.getPropertyValues().add("customReturnValueHandlers", returnValueHandlers);
		}
		if (asyncTimeout != null) {
			handlerAdapterDef.getPropertyValues().add("asyncRequestTimeout", asyncTimeout);
		}
		if (asyncExecutor != null) {
			handlerAdapterDef.getPropertyValues().add("taskExecutor", asyncExecutor);
		}

		handlerAdapterDef.getPropertyValues().add("callableInterceptors", callableInterceptors);
		handlerAdapterDef.getPropertyValues().add("deferredResultInterceptors", deferredResultInterceptors);
		readerContext.getRegistry().registerBeanDefinition(HANDLER_ADAPTER_BEAN_NAME, handlerAdapterDef);

		RootBeanDefinition uriContributorDef =
				new RootBeanDefinition(CompositeUriComponentsContributorFactoryBean.class);
		uriContributorDef.setSource(source);
		uriContributorDef.getPropertyValues().addPropertyValue("handlerAdapter", handlerAdapterDef);
		uriContributorDef.getPropertyValues().addPropertyValue("conversionService", conversionService);
		String uriContributorName = MvcUriComponentsBuilder.MVC_URI_COMPONENTS_CONTRIBUTOR_BEAN_NAME;
		readerContext.getRegistry().registerBeanDefinition(uriContributorName, uriContributorDef);

		RootBeanDefinition csInterceptorDef = new RootBeanDefinition(ConversionServiceExposingInterceptor.class);
		csInterceptorDef.setSource(source);
		csInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(0, conversionService);
		RootBeanDefinition mappedInterceptorDef = new RootBeanDefinition(MappedInterceptor.class);
		mappedInterceptorDef.setSource(source);
		mappedInterceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
		mappedInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(0, (Object) null);
		mappedInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(1, csInterceptorDef);
		String mappedInterceptorName = readerContext.registerWithGeneratedName(mappedInterceptorDef);

		RootBeanDefinition methodExceptionResolver = new RootBeanDefinition(ExceptionHandlerExceptionResolver.class);
		methodExceptionResolver.setSource(source);
		methodExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
		methodExceptionResolver.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
		methodExceptionResolver.getPropertyValues().add("messageConverters", messageConverters);
		methodExceptionResolver.getPropertyValues().add("order", 0);
		addResponseBodyAdvice(methodExceptionResolver);
		if (argumentResolvers != null) {
			methodExceptionResolver.getPropertyValues().add("customArgumentResolvers", argumentResolvers);
		}
		if (returnValueHandlers != null) {
			methodExceptionResolver.getPropertyValues().add("customReturnValueHandlers", returnValueHandlers);
		}
		String methodExResolverName = readerContext.registerWithGeneratedName(methodExceptionResolver);

		RootBeanDefinition statusExceptionResolver = new RootBeanDefinition(ResponseStatusExceptionResolver.class);
		statusExceptionResolver.setSource(source);
		statusExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
		statusExceptionResolver.getPropertyValues().add("order", 1);
		String statusExResolverName = readerContext.registerWithGeneratedName(statusExceptionResolver);

		RootBeanDefinition defaultExceptionResolver = new RootBeanDefinition(DefaultHandlerExceptionResolver.class);
		defaultExceptionResolver.setSource(source);
		defaultExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
		defaultExceptionResolver.getPropertyValues().add("order", 2);
		String defaultExResolverName = readerContext.registerWithGeneratedName(defaultExceptionResolver);

        // 前面更多的是一些web组件的初始化和组装
        // 这里统一注册,我们直接看这里就行了
        // 所以之前的默认策略并不一定准,因为这里Spring帮我们注入了,所以不会走默认web组件
		context.registerComponent(new BeanComponentDefinition(handlerMappingDef, HANDLER_MAPPING_BEAN_NAME));
		context.registerComponent(new BeanComponentDefinition(handlerAdapterDef, HANDLER_ADAPTER_BEAN_NAME));
		context.registerComponent(new BeanComponentDefinition(uriContributorDef, uriContributorName));
		context.registerComponent(new BeanComponentDefinition(mappedInterceptorDef, mappedInterceptorName));
		context.registerComponent(new BeanComponentDefinition(methodExceptionResolver, methodExResolverName));
		context.registerComponent(new BeanComponentDefinition(statusExceptionResolver, statusExResolverName));
		context.registerComponent(new BeanComponentDefinition(defaultExceptionResolver, defaultExResolverName));

		// Ensure BeanNameUrlHandlerMapping (SPR-8289) and default HandlerAdapters are not "turned off"
		MvcNamespaceUtils.registerDefaultComponents(context, source);

		context.popAndRegisterContainingComponent();

		return null;
	}
    // 省略其他方法
}


// 之前提到过的26个bean,有相当一部分就是来自这里,我们等下再过

贴上日志,如下

# 上面那个方法又臭又长,为什么要过呢,是因为上面有些web组件bean实现了特定的接口,提前进行了一些不为人知的操作
# 一共找到了26个bean,包含aop:a个、context:b个、mvc:c个、自定义bean:d个
10:34:59.039 [0.1][DEBUG] factory.xml.XmlBeanDefinitionReader 396: Loaded 26 bean definitions from ServletContext resource [/WEB-INF/applicationContext.xml]
10:34:59.039 [0.1][TRACE] factory.support.AbstractBeanDefinitionReader 229: Loaded 26 bean definitions from location pattern [/WEB-INF/applicationContext.xml]

2、小结

mvc标签和aop标签类似,都是向Spring容器注册一些运行的必要bean组件,主要是用于支持web环境,这里隐式注册的bean比较多,共14个,如下

"org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping" -> {RootBeanDefinition@3283} "Root bean: class [org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping]; scope=; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null"

"org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#0" -> {RootBeanDefinition@3324} "Root bean: class [org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver]; scope=; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null"

"org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver#0" -> {RootBeanDefinition@3326} "Root bean: class [org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver]; scope=; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null"

"mvcCorsConfigurations" -> {RootBeanDefinition@3474} "Root bean: class [java.util.LinkedHashMap]; scope=; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null"

"mvcHandlerMappingIntrospector" -> {RootBeanDefinition@3476} "Root bean: class [org.springframework.web.servlet.handler.HandlerMappingIntrospector]; scope=; abstract=false; lazyInit=true; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null"

"org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver#0" -> {RootBeanDefinition@3328} "Root bean: class [org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver]; scope=; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null"

"org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter" -> {RootBeanDefinition@3478} "Root bean: class [org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter]; scope=; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null"

"mvcContentNegotiationManager" -> {RootBeanDefinition@3480} "Root bean: class [org.springframework.web.accept.ContentNegotiationManagerFactoryBean]; scope=; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null"

"org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping" -> {RootBeanDefinition@3482} "Root bean: class [org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping]; scope=; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null"

"org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter" -> {RootBeanDefinition@3318} "Root bean: class [org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter]; scope=; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null"

"org.springframework.web.servlet.handler.MappedInterceptor#0" -> {RootBeanDefinition@3322} "Root bean: class [org.springframework.web.servlet.handler.MappedInterceptor]; scope=; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null"

"org.springframework.format.support.FormattingConversionServiceFactoryBean#0" -> {RootBeanDefinition@3484} "Root bean: class [org.springframework.format.support.FormattingConversionServiceFactoryBean]; scope=; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null"

"org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter" -> {RootBeanDefinition@3486} "Root bean: class [org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter]; scope=; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null"

"mvcUriComponentsContributor" -> {RootBeanDefinition@3319} "Root bean: class [org.springframework.web.servlet.config.AnnotationDrivenBeanDefinitionParser$CompositeUriComponentsContributorFactoryBean]; scope=; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null"

4、context

在正式进入日志分析之前,我们先来大致过一下这个自定义标签的逻辑(虽然可以简单的猜出来)。

1、关键代码

// 首先定位到 ContextNamespaceHandler
public class ContextNamespaceHandler extends NamespaceHandlerSupport {
	@Override
	public void init() {
		registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser());
		registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser());
		registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser());
        // 再定位到这一行
		registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
		registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser());
		registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
		registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser());
		registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser());
	}
}

@Override
@Nullable
public BeanDefinition parse(Element element, ParserContext parserContext) {
    String basePackage = element.getAttribute(BASE_PACKAGE_ATTRIBUTE);
    basePackage = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(basePackage);
    String[] basePackages = StringUtils.tokenizeToStringArray(basePackage,
            ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);

    // Actually scan for bean definitions and register them.
    ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);
    // 扫描用户包下面的所有类,寻找Spring需要的bean
    Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);
    // 注册所需要的bean,注意这里,为什么呢,因为有些bean不是简单的bean,而是系统需要的bean
    // 例如被@Configuration、@Component注解标记的bean
    registerComponents(parserContext.getReaderContext(), beanDefinitions, element);
    return null;
}

protected void registerComponents(
        XmlReaderContext readerContext, Set<BeanDefinitionHolder> beanDefinitions, Element element) {

    Object source = readerContext.extractSource(element);
    CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), source);

    for (BeanDefinitionHolder beanDefHolder : beanDefinitions) {
        compositeDef.addNestedComponent(new BeanComponentDefinition(beanDefHolder));
    }

    // Register annotation config processors, if necessary.
    boolean annotationConfig = true;
    if (element.hasAttribute(ANNOTATION_CONFIG_ATTRIBUTE)) {
        annotationConfig = Boolean.parseBoolean(element.getAttribute(ANNOTATION_CONFIG_ATTRIBUTE));
    }
    if (annotationConfig) {
        // 注意这里注册了大量的BeanPostProcessor用于支持Spring注解、标准注解
        // 如@Bean、@Service、@Component、@Configuration、@Autowired、@Resource等
        // 但都不是Spring MVC的注解,如@Controller、@RequestMapping、@ResponseBody等
        // Register all relevant annotation post processors
        Set<BeanDefinitionHolder> processorDefinitions =                 AnnotationConfigUtils.registerAnnotationConfigProcessors(readerContext.getRegistry(), source);
        for (BeanDefinitionHolder processorDefinition : processorDefinitions) {
            compositeDef.addNestedComponent(new BeanComponentDefinition(processorDefinition));
        }
    }

    readerContext.fireComponentRegistered(compositeDef);
}

/**
 * Register all relevant annotation post processors in the given registry.
 * @param registry the registry to operate on
 * @param source the configuration source element (already extracted)
 * that this registration was triggered from. May be {@code null}.
 * @return a Set of BeanDefinitionHolders, containing all bean definitions
 * that have actually been registered by this call
 */
public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
        BeanDefinitionRegistry registry, @Nullable Object source) {

    DefaultListableBeanFactory beanFactory = unwrapDefaultListableBeanFactory(registry);
    if (beanFactory != null) {
        // 用于支持@Order,以定义注入bean的优先级
        if (!(beanFactory.getDependencyComparator() instanceof AnnotationAwareOrderComparator)) {
            beanFactory.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE);
        }
        // 用于支持@Lazy,以让bean初始化延迟到第一次被用户使用
        if (!(beanFactory.getAutowireCandidateResolver() instanceof ContextAnnotationAutowireCandidateResolver)) {
            beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
        }
    }

    Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<>(8);
	// 支持@Configuration
    // 注意和上面的区别,上面不是以BeanPostProcessor的形式注入,而是以扩展的形式set进DefaultListableBeanFactory
    if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {
        RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
        def.setSource(source);
        beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
    }
	// 支持@Autowired
    if (!registry.containsBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) {
        RootBeanDefinition def = new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class);
        def.setSource(source);
        beanDefs.add(registerPostProcessor(registry, def, AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
    }

    // Check for JSR-250 support, and if present add the CommonAnnotationBeanPostProcessor.
    // 支持@PostConstruct、@PreDestroy、@Resource
    if (jsr250Present && !registry.containsBeanDefinition(COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)) {
        RootBeanDefinition def = new RootBeanDefinition(CommonAnnotationBeanPostProcessor.class);
        def.setSource(source);
        beanDefs.add(registerPostProcessor(registry, def, COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));
    }

    // Check for JPA support, and if present add the PersistenceAnnotationBeanPostProcessor.
    if (jpaPresent && !registry.containsBeanDefinition(PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME)) {
        RootBeanDefinition def = new RootBeanDefinition();
        try {
            def.setBeanClass(ClassUtils.forName(PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME,
                    AnnotationConfigUtils.class.getClassLoader()));
        }
        catch (ClassNotFoundException ex) {
            throw new IllegalStateException(
                    "Cannot load optional framework class: " + PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME, ex);
        }
        def.setSource(source);
        beanDefs.add(registerPostProcessor(registry, def, PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME));
    }
	// 支持@EventListener
    if (!registry.containsBeanDefinition(EVENT_LISTENER_PROCESSOR_BEAN_NAME)) {
        RootBeanDefinition def = new RootBeanDefinition(EventListenerMethodProcessor.class);
        def.setSource(source);
        beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_PROCESSOR_BEAN_NAME));
    }
    if (!registry.containsBeanDefinition(EVENT_LISTENER_FACTORY_BEAN_NAME)) {
        RootBeanDefinition def = new RootBeanDefinition(DefaultEventListenerFactory.class);
        def.setSource(source);
        beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_FACTORY_BEAN_NAME));
    }
    return beanDefs;
}

里面的具体实现逻辑我们就不细看了,贴上日志

# 到这里为止,Spring开始下狠手了,但是这里为什么都不给个明确的提示呢?
10:34:58.873 [0.1][TRACE] context.annotation.ClassPathScanningCandidateComponentProvider 212: JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning
10:34:58.882 [0.1][TRACE] io.support.PathMatchingResourcePatternResolver 323: Resolved classpath location [xiaokui/] to resources [URL [file:/home/hk/JavaSpace/spring-web-demo/out/artifacts/spring_web_demo_war_exploded/WEB-INF/classes/xiaokui/]]
# ......省略50行输出......

# 到这里已经完成了对所有用户类的扫描
10:34:58.900 [0.1][TRACE] io.support.PathMatchingResourcePatternResolver 517: Resolved location pattern [classpath*:xiaokui/*/**/*.class] to resources [file [/home/hk/JavaSpace/spring-web-demo/out/artifacts/spring_web_demo_war_exploded/WEB-INF/classes/xiaokui/config/AfterRun.class], file [/home/hk/JavaSpace/spring-web-demo/out/artifacts/spring_web_demo_war_exploded/WEB-INF/classes/xiaokui/config/SpringConfig.class], file [/home/hk/JavaSpace/spring-web-demo/out/artifacts/spring_web_demo_war_exploded/WEB-INF/classes/xiaokui/controller/IndexController.class], file [/home/hk/JavaSpace/spring-web-demo/out/artifacts/spring_web_demo_war_exploded/WEB-INF/classes/xiaokui/controller/LastModifiedCacheController.class], file [/home/hk/JavaSpace/spring-web-demo/out/artifacts/spring_web_demo_war_exploded/WEB-INF/classes/xiaokui/dao/UserDao.class], file [/home/hk/JavaSpace/spring-web-demo/out/artifacts/spring_web_demo_war_exploded/WEB-INF/classes/xiaokui/entity/Apple.class], file [/home/hk/JavaSpace/spring-web-demo/out/artifacts/spring_web_demo_war_exploded/WEB-INF/classes/xiaokui/entity/User.class], file [/home/hk/JavaSpace/spring-web-demo/out/artifacts/spring_web_demo_war_exploded/WEB-INF/classes/xiaokui/service/UserService.class], file [/home/hk/JavaSpace/spring-web-demo/out/artifacts/spring_web_demo_war_exploded/WEB-INF/classes/xiaokui/test/TestLambda.class], file [/home/hk/JavaSpace/spring-web-demo/out/artifacts/spring_web_demo_war_exploded/WEB-INF/classes/xiaokui/test/TestLambda1$A.class], file [/home/hk/JavaSpace/spring-web-demo/out/artifacts/spring_web_demo_war_exploded/WEB-INF/classes/xiaokui/test/TestLambda1$B.class], file [/home/hk/JavaSpace/spring-web-demo/out/artifacts/spring_web_demo_war_exploded/WEB-INF/classes/xiaokui/test/TestLambda1.class]]

# 注意,扫描类已经从 PathMatchingResourcePatternResolver 变成了 ClassPathScanningCandidateComponentProvider,到这里Spring已经在寻找可以注入的Bean了
10:34:58.900 [0.1][TRACE] context.annotation.ClassPathScanningCandidateComponentProvider 426: Scanning file [/home/hk/JavaSpace/spring-web-demo/out/artifacts/spring_web_demo_war_exploded/WEB-INF/classes/xiaokui/config/AfterRun.class]
10:34:58.958 [0.1][DEBUG] context.annotation.ClassPathScanningCandidateComponentProvider 437: Identified candidate component class: file [/home/hk/JavaSpace/spring-web-demo/out/artifacts/spring_web_demo_war_exploded/WEB-INF/classes/xiaokui/config/AfterRun.class]
# ......省略50行输出......
# 继续往下

2、小结

aop和mvc标签解析时,两者都会提前向Spring容器放置一些内置bean,其中有比较关键的bean,例如AnnotationAwareAspectJAutoProxyCreator,也有一些不是那么显眼的bean,例如mvcUriComponentsContributor,但他们两者都有一个特点,就是不会输出日志,只是单纯的把一些必要的BeanDefinition放进IoC容器,然后等待流程往下走。

而context标签的不同点在于,context标签会扫描指定包下的所有类,然后扫描所有@Component注解的类,但为什么@Configuration、@Service、@Controller、@RestController、@Repository、@ControllerAdvice、@RestControllerAdvice注解的类也可以被扫描呢?

你是不是在骗我,欺负我读书读得少?不是的,这是因为上面的那些注解同时都是被@Component注解的,本质上都是@Component注解,是隐式的继承关系。context除了扫描用户自定的bean,还会隐式注入以下5个bean,主要是用户对@Configuration的解析(用于提前配置Spring环境)、@EventListener的解析(用户监听Spring内部事件通知)、@Autowired/@Value/@Resource的解析(用于bean的自动装配)。

"org.springframework.context.annotation.internalConfigurationAnnotationProcessor" -> {RootBeanDefinition@4059} "Root bean: class [org.springframework.context.annotation.ConfigurationClassPostProcessor]; scope=; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null"

"org.springframework.context.event.internalEventListenerFactory" -> {RootBeanDefinition@4062} "Root bean: class [org.springframework.context.event.DefaultEventListenerFactory]; scope=; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null"

"org.springframework.context.event.internalEventListenerProcessor" -> {RootBeanDefinition@4064} "Root bean: class [org.springframework.context.event.EventListenerMethodProcessor]; scope=; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null"

"org.springframework.context.annotation.internalAutowiredAnnotationProcessor" -> {RootBeanDefinition@4069} "Root bean: class [org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor]; scope=; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null"

"org.springframework.context.annotation.internalCommonAnnotationProcessor" -> {RootBeanDefinition@4072} "Root bean: class [org.springframework.context.annotation.CommonAnnotationBeanPostProcessor]; scope=; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null"

四、invokeBeanFactoryPostProcessors与@Configuration

1、postProcessBeanFactory

日志如下

# 注册web中特点的域,并为特定web Aware接口赋予特定资源
10:34:59.057 [0.1][TRACE] factory.support.AbstractBeanFactory 980: Registering scope 'request' with implementation [org.springframework.web.context.request.RequestScope@1eea14f9]
10:34:59.058 [0.1][TRACE] factory.support.AbstractBeanFactory 980: Registering scope 'session' with implementation [org.springframework.web.context.request.SessionScope@29022255]
10:34:59.059 [0.1][TRACE] factory.support.AbstractBeanFactory 980: Registering scope 'application' with implementation [org.springframework.web.context.support.ServletContextScope@4913a969]

2、invokeBeanFactoryPostProcessors

先上日志,如下

# 激活各BeanDefinitionRegistryPostProcessor、BeanFactoryPostProcessor,已配置BeanFactory
# 这里特指ConfigurationClassPostProcessor,来自context标签
# 主要是通过实现BeanFactoryPostProcessor接口的postProcessBeanFactory方法以定制化BeanFactory
10:34:59.098 [0.1][DEBUG] factory.support.DefaultSingletonBeanRegistry 213: Creating shared instance of singleton bean 'org.springframework.context.annotation.internalConfigurationAnnotationProcessor'
10:34:59.099 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean 'org.springframework.context.annotation.internalConfigurationAnnotationProcessor'
10:34:59.124 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 585: Eagerly caching bean 'org.springframework.context.annotation.internalConfigurationAnnotationProcessor' to allow for resolving potential circular references
10:34:59.127 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean 'org.springframework.context.annotation.internalConfigurationAnnotationProcessor'
# ConfigurationClassPostProcessor里面会触发对@Configuration、@Bean的扫描,并将该Bean以BeanDefinition的形式注册进Spring IoC容器
# 所以@Configuration是先于@Component加载,注意,这里只是注册,并不会实例化,且在这里面可以替换系统默认内置的bean
10:34:59.228 [0.1][TRACE] context.annotation.ConfigurationClassBeanDefinitionReader 284: Registering bean definition for @Bean method xiaokui.config.SpringConfig.userBean()
            
# 这里触发了增强, ConfigurationClassPostProcessor类同时实现了BeanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry和BeanFactoryPostProcessor的postProcessBeanFactory方法
# postProcessBeanDefinitionRegistry先于postProcessBeanFactory执行
# postProcessBeanDefinitionRegistry方法用于扫描所有@Configuration中的@Bean,postProcessBeanFactory用于增强所有@Configuration所注解的类
# 为什么需要增强呢?我们后面再来回顾
10:34:59.333 [0.1][TRACE] context.annotation.ConfigurationClassEnhancer 111: Successfully enhanced xiaokui.config.SpringConfig; enhanced class name is: xiaokui.config.SpringConfig$$EnhancerBySpringCGLIB$$91072d89
10:34:59.333 [0.1][TRACE] context.annotation.ConfigurationClassPostProcessor 426: Replacing bean definition 'springConfig' existing class 'xiaokui.config.SpringConfig' with enhanced class 'xiaokui.config.SpringConfig$$EnhancerBySpringCGLIB$$91072d89'
            
# 另外一个BeanFactoryPostProcessor,来自context标签
# 这里为internalEventListenerProcessor
10:34:59.335 [0.1][DEBUG] factory.support.DefaultSingletonBeanRegistry 213: Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerProcessor'
10:34:59.336 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean 'org.springframework.context.event.internalEventListenerProcessor'
10:34:59.338 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 585: Eagerly caching bean 'org.springframework.context.event.internalEventListenerProcessor' to allow for resolving potential circular references
10:34:59.338 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean 'org.springframework.context.event.internalEventListenerProcessor'

# 另外一个BeanFactoryPostProcessor,来自context标签
# 这里为internalEventListenerFactory
10:34:59.339 [0.1][DEBUG] factory.support.DefaultSingletonBeanRegistry 213: Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerFactory'
10:34:59.340 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean 'org.springframework.context.event.internalEventListenerFactory'
10:34:59.340 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 585: Eagerly caching bean 'org.springframework.context.event.internalEventListenerFactory' to allow for resolving potential circular references
10:34:59.341 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean 'org.springframework.context.event.internalEventListenerFactory'   

3、为什么需要增强@Configuration类

暂时放下我们的日志追踪,来看一看这一个有趣的问题:为什么需要增强@Configuration注解标记的类

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {

	/**
	 * Explicitly specify the name of the Spring bean definition associated with the
	 * {@code @Configuration} class. If left unspecified (the common case), a bean
	 * name will be automatically generated.
	 * <p>The custom name applies only if the {@code @Configuration} class is picked
	 * up via component scanning or supplied directly to an
	 * {@link AnnotationConfigApplicationContext}. If the {@code @Configuration} class
	 * is registered as a traditional XML bean definition, the name/id of the bean
	 * element will take precedence.
	 * @return the explicit component name, if any (or empty String otherwise)
	 * @see AnnotationBeanNameGenerator
	 */
	@AliasFor(annotation = Component.class)
	String value() default "";

	/**
	 * Specify whether {@code @Bean} methods should get proxied in order to enforce
	 * bean lifecycle behavior, e.g. to return shared singleton bean instances even
	 * in case of direct {@code @Bean} method calls in user code. This feature
	 * requires method interception, implemented through a runtime-generated CGLIB
	 * subclass which comes with limitations such as the configuration class and
	 * its methods not being allowed to declare {@code final}.
	 * <p>The default is {@code true}, allowing for 'inter-bean references' within
	 * the configuration class as well as for external calls to this configuration's
	 * {@code @Bean} methods, e.g. from another configuration class. If this is not
	 * needed since each of this particular configuration's {@code @Bean} methods
	 * is self-contained and designed as a plain factory method for container use,
	 * switch this flag to {@code false} in order to avoid CGLIB subclass processing.
	 * <p>Turning off bean method interception effectively processes {@code @Bean}
	 * methods individually like when declared on non-{@code @Configuration} classes,
	 * a.k.a. "@Bean Lite Mode" (see {@link Bean @Bean's javadoc}). It is therefore
	 * behaviorally equivalent to removing the {@code @Configuration} stereotype.
	 * @since 5.2
	 */
	boolean proxyBeanMethods() default true;

}

通过注释,我们大致知道默认代理bean方法的左右是为了内部bean的引用,即“inter-bean references”。具体见下面这个例子

@Component    // true false
//@Configuration  // true true
public class TestConfiguration {

    static class A1 {
    }

    static class A2 {
    }

    static class A3 {
        A1 a1;
        A2 a2;
        A3(A1 a1, A2 a2) {
            this.a1 = a1;
            this.a2 = a2;
        }
    }

    @Bean
    public A1 a1() {
        return new A1();
    }

    @Bean A2 a2() {
        return new A2();
    }

    @Bean A3 a3(A1 a1) {
        // 如果启用了代理模式,那么会拦截a2,返回a2所对应的bean,否则返回的是一个新对象
        // 这一步并不需要显示指定AOP模块,而是隐式代理
        return new A3(a1, a2());
    }

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TestConfiguration.class);
        A1 a1 = (A1) context.getBean("a1");
        A2 a2 = (A2) context.getBean("a2");
        A3 a3 = (A3) context.getBean("a3");
        System.out.print(a1 == a3.a1 );
        System.out.println(" " + (a2 == a3.a2));
    }
}

4、registerBeanPostProcessors

继续分析日志,如下

# 开始注册BeanPostProcessor
# 对 AutowiredAnnotationBeanPostProcessor 的初始化注册,来自context标签    
10:34:59.344 [0.1][DEBUG] factory.support.DefaultSingletonBeanRegistry 213: Creating shared instance of singleton bean 'org.springframework.context.annotation.internalAutowiredAnnotationProcessor'
10:34:59.345 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean 'org.springframework.context.annotation.internalAutowiredAnnotationProcessor'
10:34:59.348 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 585: Eagerly caching bean 'org.springframework.context.annotation.internalAutowiredAnnotationProcessor' to allow for resolving potential circular references
10:34:59.348 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean 'org.springframework.context.annotation.internalAutowiredAnnotationProcessor'
            
# 对 CommonAnnotationBeanPostProcessor 的初始化注册,来自context标签            
10:34:59.349 [0.1][DEBUG] factory.support.DefaultSingletonBeanRegistry 213: Creating shared instance of singleton bean 'org.springframework.context.annotation.internalCommonAnnotationProcessor'
10:34:59.349 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean 'org.springframework.context.annotation.internalCommonAnnotationProcessor'
10:34:59.354 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 585: Eagerly caching bean 'org.springframework.context.annotation.internalCommonAnnotationProcessor' to allow for resolving potential circular references
10:34:59.355 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean 'org.springframework.context.annotation.internalCommonAnnotationProcessor'
            
# 对 AnnotationAwareAspectJAutoProxyCreator 的初始化,来自aop标签            
10:34:59.355 [0.1][DEBUG] factory.support.DefaultSingletonBeanRegistry 213: Creating shared instance of singleton bean 'org.springframework.aop.config.internalAutoProxyCreator'
10:34:59.355 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean 'org.springframework.aop.config.internalAutoProxyCreator'
10:34:59.379 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 585: Eagerly caching bean 'org.springframework.aop.config.internalAutoProxyCreator' to allow for resolving potential circular references
            
# 对于bean属性的填充,populateBean方法中触发applyPropertyValues
# 还记得Spring AOP章节里提到过的3个合并属性?exposeProxy、proxyTargetClass、order属性,由于显示指定了BeanDefinition中的PropertyValues,所以这里需要有相应处理逻辑以日志,已经改动了原有BeanDefinition
10:34:59.392 [0.1][TRACE] io.support.SpringFactoriesLoader 100: Loaded [org.springframework.beans.BeanInfoFactory] names: [org.springframework.beans.ExtendedBeanInfoFactory]
10:34:59.393 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 270: Getting BeanInfo for class [org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator]
10:34:59.411 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 275: Caching PropertyDescriptors for class [org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator]
10:34:59.411 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'advisorAdapterRegistry' of type [org.springframework.aop.framework.adapter.AdvisorAdapterRegistry]
10:34:59.412 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'applyCommonInterceptorsFirst' of type [boolean]
10:34:59.413 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'aspectJAdvisorFactory' of type [org.springframework.aop.aspectj.annotation.AspectJAdvisorFactory]
10:34:59.413 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'beanClassLoader' of type [java.lang.ClassLoader]
10:34:59.414 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'beanFactory' of type [org.springframework.beans.factory.BeanFactory]
10:34:59.414 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'class' of type [java.lang.Class]
10:34:59.418 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'customTargetSourceCreators' of type [[Lorg.springframework.aop.framework.autoproxy.TargetSourceCreator;]
10:34:59.419 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'exposeProxy' of type [boolean]
10:34:59.419 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'frozen' of type [boolean]
10:34:59.419 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'includePatterns' of type [java.util.List]
10:34:59.420 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'interceptorNames' of type [[Ljava.lang.String;]
10:34:59.420 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'opaque' of type [boolean]
10:34:59.420 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'optimize' of type [boolean]
10:34:59.421 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'order' of type [int]
10:34:59.422 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'proxyClassLoader' of type [java.lang.ClassLoader]
10:34:59.422 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'proxyTargetClass' of type [boolean]
10:34:59.452 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean 'org.springframework.aop.config.internalAutoProxyCreator'
# 至此所有BeanPostProcessor已初始化完成
                                                                                       # 对于wac.refresh方法中的initMessageSource、initApplicationEventMulticaster、onRefresh方法
10:34:59.453 [0.1][TRACE] context.support.AbstractApplicationContext 753: No 'messageSource' bean, using [Empty MessageSource]
10:34:59.456 [0.1][TRACE] context.support.AbstractApplicationContext 776: No 'applicationEventMulticaster' bean, using [SimpleApplicationEventMulticaster]
10:34:59.459 [0.1][DEBUG] context.support.UiApplicationContextUtils 85: Unable to locate ThemeSource with name 'themeSource': using default [org.springframework.ui.context.support.ResourceBundleThemeSource@305226e7]

五、finishBeanFactoryInitialization与Web组件的初始化

紧跟日志,如下

# 对应wac.refresh方法中的 finishBeanFactoryInitialization 方法
# 这一步主要是用于提前初始化所有非lazy-init的bean,后续的getBean直接走缓存即可
# 注意,这里的bean区别之前的初始化的系统bean,之前的bean是用于配置BeanFactory和配置Bean的bean
# 若无特属于说明,均指普通的@Component指定的bean
10:34:59.462 [0.1][TRACE] factory.support.DefaultListableBeanFactory 848: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@a2ea04d:
        
defining beans [
    mvcContentNegotiationManager,
    org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,
    mvcCorsConfigurations,
    org.springframework.format.support.FormattingConversionServiceFactoryBean#0,
    org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,
    mvcUriComponentsContributor,
    org.springframework.web.servlet.handler.MappedInterceptor#0,
    org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#0,
    org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver#0,
    org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver#0,
    org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,
    org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,
    org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,
    mvcHandlerMappingIntrospector,
    org.springframework.aop.config.internalAutoProxyCreator,
    afterRun,
    mvcConfig,
    springConfig,
    indexController,
    /last,
    testController,
    testConfiguration,
    userDao,
    userService,
    org.springframework.context.annotation.internalConfigurationAnnotationProcessor,
    org.springframework.context.annotation.internalAutowiredAnnotationProcessor,
    org.springframework.context.annotation.internalCommonAnnotationProcessor,
    org.springframework.context.event.internalEventListenerProcessor,
    org.springframework.context.event.internalEventListenerFactory,
    org.springframework.web.servlet.handler.MappedInterceptor#1,
    userBean,
    a]; 
    root of factory hierarchy

我们大致过一下吧。

1、mvcContentNegotiationManager

MVC内容协商管理器。

// 以下如不作特殊说明,则均来自类AnnotationDrivenBeanDefinitionParser,来自mvc标签
// 值得一提的是,ContentNegotiationManagerFactoryBean实现了FactoryBean、ServletContextAware、InitializingBean
// 所以这就有点意思了,且这个对象大量被其他web组件对象引用
private RuntimeBeanReference getContentNegotiationManager(
        Element element, @Nullable Object source, ParserContext context) {

    RuntimeBeanReference beanRef;
    if (element.hasAttribute("content-negotiation-manager")) {
        String name = element.getAttribute("content-negotiation-manager");
        beanRef = new RuntimeBeanReference(name);
    }
    else {
        RootBeanDefinition factoryBeanDef = new RootBeanDefinition(ContentNegotiationManagerFactoryBean.class);
        factoryBeanDef.setSource(source);
        factoryBeanDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        // 注意这一行
        factoryBeanDef.getPropertyValues().add("mediaTypes", getDefaultMediaTypes());
        String name = CONTENT_NEGOTIATION_MANAGER_BEAN_NAME;
        context.getReaderContext().getRegistry().registerBeanDefinition(name, factoryBeanDef);
        context.registerComponent(new BeanComponentDefinition(factoryBeanDef, name));
        beanRef = new RuntimeBeanReference(name);
    }
    return beanRef;
}

/**
 * Invoked by Spring to inject the ServletContext.
 */
// 以下3个方法均来自类ContentNegotiationManagerFactoryBean
@Override
public void setServletContext(ServletContext servletContext) {
    this.servletContext = servletContext;
}

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

/**
 * Actually build the {@link ContentNegotiationManager}.
 * @since 5.0
 */
public ContentNegotiationManager build() {
    List<ContentNegotiationStrategy> strategies = new ArrayList<>();

    if (this.strategies != null) {
        strategies.addAll(this.strategies);
    }
    else {
        if (this.favorPathExtension) {
            PathExtensionContentNegotiationStrategy strategy;
            if (this.servletContext != null && !useRegisteredExtensionsOnly()) {
                strategy = new ServletPathExtensionContentNegotiationStrategy(this.servletContext, this.mediaTypes);
            }
            else {
                strategy = new PathExtensionContentNegotiationStrategy(this.mediaTypes);
            }
            strategy.setIgnoreUnknownExtensions(this.ignoreUnknownPathExtensions);
            if (this.useRegisteredExtensionsOnly != null) {
                strategy.setUseRegisteredExtensionsOnly(this.useRegisteredExtensionsOnly);
            }
            strategies.add(strategy);
        }

        if (this.favorParameter) {
            ParameterContentNegotiationStrategy strategy = new ParameterContentNegotiationStrategy(this.mediaTypes);
            strategy.setParameterName(this.parameterName);
            if (this.useRegisteredExtensionsOnly != null) {
                strategy.setUseRegisteredExtensionsOnly(this.useRegisteredExtensionsOnly);
            }
            else {
                strategy.setUseRegisteredExtensionsOnly(true);  // backwards compatibility
            }
            strategies.add(strategy);
        }

        if (!this.ignoreAcceptHeader) {
            strategies.add(new HeaderContentNegotiationStrategy());
        }

        if (this.defaultNegotiationStrategy != null) {
            strategies.add(this.defaultNegotiationStrategy);
        }
    }

    this.contentNegotiationManager = new ContentNegotiationManager(strategies);
    return this.contentNegotiationManager;
}

@Override
@Nullable
public ContentNegotiationManager getObject() {
    return this.contentNegotiationManager;
}

/**
 * Central class to determine requested {@linkplain MediaType media types}
 * for a request. This is done by delegating to a list of configured
 * {@code ContentNegotiationStrategy} instances.
 *
 * <p>Also provides methods to look up file extensions for a media type.
 * This is done by delegating to the list of configured
 * {@code MediaTypeFileExtensionResolver} instances.
 *
 */
public class ContentNegotiationManager implements ContentNegotiationStrategy, MediaTypeFileExtensionResolver {
	// 省略类属性和方法
}

贴上日志,如下

# 来自类AnnotationDrivenBeanDefinitionParser,来自mvc标签
10:34:59.463 [0.1][DEBUG] factory.support.DefaultSingletonBeanRegistry 213: Creating shared instance of singleton bean 'mvcContentNegotiationManager'
10:34:59.463 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean 'mvcContentNegotiationManager'
10:34:59.486 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 585: Eagerly caching bean 'mvcContentNegotiationManager' to allow for resolving potential circular references
10:34:59.487 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 270: Getting BeanInfo for class [org.springframework.web.accept.ContentNegotiationManagerFactoryBean]
10:34:59.492 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 275: Caching PropertyDescriptors for class [org.springframework.web.accept.ContentNegotiationManagerFactoryBean]
# 代码中加入自定义属性
10:34:59.492 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'class' of type [java.lang.Class]
10:34:59.492 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'defaultContentType' of type [org.springframework.http.MediaType]
10:34:59.493 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'defaultContentTypeStrategy' of type [org.springframework.web.accept.ContentNegotiationStrategy]
10:34:59.493 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'defaultContentTypes' of type [java.util.List]
10:34:59.493 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'favorParameter' of type [boolean]
10:34:59.494 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'favorPathExtension' of type [boolean]
10:34:59.494 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'ignoreAcceptHeader' of type [boolean]
10:34:59.494 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'ignoreUnknownPathExtensions' of type [boolean]
10:34:59.494 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'mediaTypes' of type [java.util.Properties]
10:34:59.495 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'object' of type [org.springframework.web.accept.ContentNegotiationManager]
10:34:59.495 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'objectType' of type [java.lang.Class]
10:34:59.496 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'parameterName' of type [java.lang.String]
10:34:59.496 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'servletContext' of type [javax.servlet.ServletContext]
10:34:59.496 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'singleton' of type [boolean]
10:34:59.497 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'strategies' of type [java.util.List]
10:34:59.497 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'useJaf' of type [boolean]
10:34:59.498 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'useRegisteredExtensionsOnly' of type [boolean]
10:34:59.521 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 1841: Invoking afterPropertiesSet() on bean with name 'mvcContentNegotiationManager'
10:34:59.528 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean 'mvcContentNegotiationManager'

10:34:59.529 [0.1][DEBUG] factory.support.DefaultSingletonBeanRegistry 213: Creating shared instance of singleton bean 'org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping'
fsdfs

2、RequestMappingHandlerMapping

RequestMappingHandlerMapping主要负责处理@Controller、@RequestMapping。我们先定位下它的注入代码,如下

// 截取自类AnnotationDrivenBeanDefinitionParser的parse方法
RuntimeBeanReference contentNegotiationManager = getContentNegotiationManager(element, source, context);

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

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

configurePathMatchingProperties(handlerMappingDef, element, context);
readerContext.getRegistry().registerBeanDefinition(HANDLER_MAPPING_BEAN_NAME, handlerMappingDef);

RuntimeBeanReference corsRef = MvcNamespaceUtils.registerCorsConfigurations(null, context, source);
// 对应下文中的 mvcCorsConfigurations 处理跨域问题
handlerMappingDef.getPropertyValues().add("corsConfigurations", corsRef);

再上日志

# 开始初始化 RequestMappingHandlerMapping
10:34:59.529 [0.1][DEBUG] factory.support.DefaultSingletonBeanRegistry 213: Creating shared instance of singleton bean 'org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping'
10:34:59.529 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean 'org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping'
10:34:59.560 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 585: Eagerly caching bean 'org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping' to allow for resolving potential circular references
10:34:59.562 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 270: Getting BeanInfo for class [org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping]
10:34:59.581 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 275: Caching PropertyDescriptors for class [org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping]

# 开始applyPropertyValues
10:34:59.582 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'alwaysUseFullPath' of type [boolean]
10:34:59.582 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'applicationContext' of type [org.springframework.context.ApplicationContext]
10:34:59.582 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'beanName' of type [java.lang.String]
10:34:59.583 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'class' of type [java.lang.Class]
10:34:59.584 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'contentNegotiationManager' of type [org.springframework.web.accept.ContentNegotiationManager]
10:34:59.584 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'corsConfigurationSource' of type [org.springframework.web.cors.CorsConfigurationSource]
10:34:59.585 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'corsConfigurations' of type [java.util.Map]
10:34:59.585 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'corsProcessor' of type [org.springframework.web.cors.CorsProcessor]
10:34:59.586 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'defaultHandler' of type [java.lang.Object]
10:34:59.586 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'detectHandlerMethodsInAncestorContexts' of type [boolean]
10:34:59.587 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'embeddedValueResolver' of type [org.springframework.util.StringValueResolver]
10:34:59.587 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'fileExtensions' of type [java.util.List]
10:34:59.588 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'handlerMethodMappingNamingStrategy' of type [org.springframework.web.servlet.handler.HandlerMethodMappingNamingStrategy]
10:34:59.588 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'handlerMethods' of type [java.util.Map]
10:34:59.589 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'interceptors' of type [[Ljava.lang.Object;]
10:34:59.590 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'namingStrategy' of type [org.springframework.web.servlet.handler.HandlerMethodMappingNamingStrategy]
10:34:59.590 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'order' of type [int]
10:34:59.591 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'pathMatcher' of type [org.springframework.util.PathMatcher]
10:34:59.591 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'pathPrefixes' of type [java.util.Map]
10:34:59.592 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'removeSemicolonContent' of type [boolean]
10:34:59.592 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'servletContext' of type [javax.servlet.ServletContext]
10:34:59.593 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'urlDecode' of type [boolean]
10:34:59.593 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'urlPathHelper' of type [org.springframework.web.util.UrlPathHelper]
10:34:59.594 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'useRegisteredSuffixPatternMatch' of type [boolean]
10:34:59.594 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'useSuffixPatternMatch' of type [boolean]
10:34:59.594 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'useTrailingSlashMatch' of type [boolean]
                                                                                       
                                                                                             # 从这里开始,开始注入bean属性了
# 这里的注入并不是普通的注入,更多的是以下这种形式 
# 先定义一个RootBeanDefinition
# RootBeanDefinition factoryBeanDef = new RootBeanDefinition(ContentNegotiationManagerFactoryBean.class);

# 然后指定引用上面BeanDefinition的beanName                                               
# RuntimeBeanReference contentNegotiationManager = getContentNegotiationManager(element, source, context);                               
# 这里的contentNegotiationManager实际是一个beanName,所以当设置这个属性时,会触发之前BeanDefinition的初始化                                                                 
# handlerMappingDef.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);

# 之前已经创建了mvcContentNegotiationManager
10:34:59.605 [0.1][TRACE] factory.support.AbstractBeanFactory 257: Returning cached instance of singleton bean 'mvcContentNegotiationManager'

# 创建mvcCorsConfigurations,处理跨域问题,注意这里的mvcCorsConfigurations其实是一个LinkedHashMap
10:34:59.607 [0.1][DEBUG] factory.support.DefaultSingletonBeanRegistry 213: Creating shared instance of singleton bean 'mvcCorsConfigurations'
10:34:59.607 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean 'mvcCorsConfigurations'
10:34:59.608 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 585: Eagerly caching bean 'mvcCorsConfigurations' to allow for resolving potential circular references
10:34:59.608 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean 'mvcCorsConfigurations'

我们紧跟日志,逐个分析由RequestMappingHandlerMapping引发的各子组件bean的初始化,如下

1、mvcCorsConfigurations

这个bean是用来解决跨域问题的,默认为null,声明如下

// 来自类 AnnotationDrivenBeanDefinitionParser parse方法
// 这里默认参数为null
RuntimeBeanReference corsRef = MvcNamespaceUtils.registerCorsConfigurations(null, context, source);
handlerMappingDef.getPropertyValues().add("corsConfigurations", corsRef);

/**
 * Registers a {@code Map<String, CorsConfiguration>} (mapped {@code CorsConfiguration}s)
 * under a well-known name unless already registered. The bean definition may be updated
 * if a non-null CORS configuration is provided.
 * @return a RuntimeBeanReference to this {@code Map<String, CorsConfiguration>} instance
 */
public static RuntimeBeanReference registerCorsConfigurations(
        @Nullable Map<String, CorsConfiguration> corsConfigurations,
        ParserContext context, @Nullable Object source) {

    if (!context.getRegistry().containsBeanDefinition(CORS_CONFIGURATION_BEAN_NAME)) {
        // 注意这里用了一个LinkedHashMap作为载体
        RootBeanDefinition corsDef = new RootBeanDefinition(LinkedHashMap.class);
        corsDef.setSource(source);
        corsDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        if (corsConfigurations != null) {
            corsDef.getConstructorArgumentValues().addIndexedArgumentValue(0, corsConfigurations);
        }
        context.getReaderContext().getRegistry().registerBeanDefinition(CORS_CONFIGURATION_BEAN_NAME, corsDef);
        context.registerComponent(new BeanComponentDefinition(corsDef, CORS_CONFIGURATION_BEAN_NAME));
    }
    else if (corsConfigurations != null) {
        BeanDefinition corsDef = context.getRegistry().getBeanDefinition(CORS_CONFIGURATION_BEAN_NAME);
        corsDef.getConstructorArgumentValues().addIndexedArgumentValue(0, corsConfigurations);
    }
    return new RuntimeBeanReference(CORS_CONFIGURATION_BEAN_NAME);
}

我们通过一段示例代码来演示一下,如下

// 假设现在有这么一个需求,我程序里面的部分对外接口只对特定域开发,那么应该怎么办
// 例如我现在的域为www.xiaokui.site,但是对www.text1.com站内发来的post/get请求我是拒绝的,而对于www.test2.com站内的请求我是允许的
// @CrossOrigin就是比较优雅的解决这个问题的,其本质就是在处理请求时加入了一个CorsConfiguration
// 这里@CrossOrigin并不会影响另外的Controller,默认的Controlller是隐式可以跨域的,虽然浏览器层面可能会有所限制
@CrossOrigin(origins = "http://www.test2.com")
@Controller
@RequestMapping("/")
public class IndexController implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @RequestMapping("/testCors")
    @ResponseBody
    public String testCors() {
        Object obj = applicationContext.getBean("mvcCorsConfigurations");
        System.out.println(obj);
        System.out.println(obj.getClass());
        return obj.toString();
    }
}

/**
 * Annotation for permitting cross-origin requests on specific handler classes
 * and/or handler methods. Processed if an appropriate {@code HandlerMapping}
 * is configured.
 *
 * <p>Both Spring Web MVC and Spring WebFlux support this annotation through the
 * {@code RequestMappingHandlerMapping} in their respective modules. The values
 * from each type and method level pair of annotations are added to a
 * {@link CorsConfiguration} and then default values are applied via
 * {@link CorsConfiguration#applyPermitDefaultValues()}.
 *
 * <p>The rules for combining global and local configuration are generally
 * additive -- e.g. all global and all local origins. For those attributes
 * where only a single value can be accepted such as {@code allowCredentials}
 * and {@code maxAge}, the local overrides the global value.
 * See {@link CorsConfiguration#combine(CorsConfiguration)} for more details.
 */
// 可以在类或方法上声明
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CrossOrigin {
    // 省略其他属性
}

2、MappedInterceptor

先上日志,如下

# 日志输出紧跟上面,第二个初始化的bean为MappedInterceptor
10:34:59.610 [0.1][DEBUG] factory.support.DefaultSingletonBeanRegistry 213: Creating shared instance of singleton bean 'org.springframework.web.servlet.handler.MappedInterceptor#0'
10:34:59.611 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean 'org.springframework.web.servlet.handler.MappedInterceptor#0'

# 又开始了另一个bean的创建,这里有个依赖链式关系,读者可以自己去跟踪源代码
# 主要引发语句为 new RootBeanDefinition(FormattingConversionServiceFactoryBean.class);
# 这个bean的作用主要用于类型转换/格式化,例如完成将Date到String的转换,需要时再来重点看下
10:34:59.619 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean '(inner bean)#7e62a42d'
10:34:59.620 [0.1][DEBUG] factory.support.DefaultSingletonBeanRegistry 213: Creating shared instance of singleton bean 'org.springframework.format.support.FormattingConversionServiceFactoryBean#0'
10:34:59.621 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean 'org.springframework.format.support.FormattingConversionServiceFactoryBean#0'
10:34:59.622 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 585: Eagerly caching bean 'org.springframework.format.support.FormattingConversionServiceFactoryBean#0' to allow for resolving potential circular references
# 创建完毕,并调用afterPropertiesSet方法
10:34:59.623 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 1841: Invoking afterPropertiesSet() on bean with name 'org.springframework.format.support.FormattingConversionServiceFactoryBean#0'
10:34:59.725 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean 'org.springframework.format.support.FormattingConversionServiceFactoryBean#0'
10:34:59.733 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean '(inner bean)#7e62a42d'

# 可以看到这里出错了且必现,但这不是我们关注的点,跳过即可
10:34:59.747 [0.1][TRACE] factory.support.ConstructorResolver 233: Ignoring constructor [public org.springframework.web.servlet.handler.MappedInterceptor(java.lang.String[],java.lang.String[],org.springframework.web.servlet.HandlerInterceptor)] of bean 'org.springframework.web.servlet.handler.MappedInterceptor#0': org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'org.springframework.web.servlet.handler.MappedInterceptor#0': Unsatisfied dependency expressed through constructor parameter 1: Could not convert argument value of type [org.springframework.web.servlet.handler.ConversionServiceExposingInterceptor] to required type [[Ljava.lang.String;]: Failed to convert value of type 'org.springframework.web.servlet.handler.ConversionServiceExposingInterceptor' to required type 'java.lang.String[]'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'org.springframework.web.servlet.handler.ConversionServiceExposingInterceptor' to required type 'java.lang.String': no matching editors or conversion strategy found
10:34:59.748 [0.1][TRACE] factory.support.ConstructorResolver 233: Ignoring constructor [public org.springframework.web.servlet.handler.MappedInterceptor(java.lang.String[],java.lang.String[],org.springframework.web.context.request.WebRequestInterceptor)] of bean 'org.springframework.web.servlet.handler.MappedInterceptor#0': org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'org.springframework.web.servlet.handler.MappedInterceptor#0': Unsatisfied dependency expressed through constructor parameter 1: Could not convert argument value of type [org.springframework.web.servlet.handler.ConversionServiceExposingInterceptor] to required type [[Ljava.lang.String;]: Failed to convert value of type 'org.springframework.web.servlet.handler.ConversionServiceExposingInterceptor' to required type 'java.lang.String[]'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'org.springframework.web.servlet.handler.ConversionServiceExposingInterceptor' to required type 'java.lang.String': no matching editors or conversion strategy found
10:34:59.749 [0.1][TRACE] springframework.beans.BeanUtils 545: No property editor [org.springframework.web.context.request.WebRequestInterceptorEditor] found for type org.springframework.web.context.request.WebRequestInterceptor according to 'Editor' suffix convention
10:34:59.750 [0.1][TRACE] factory.support.ConstructorResolver 233: Ignoring constructor [public org.springframework.web.servlet.handler.MappedInterceptor(java.lang.String[],org.springframework.web.context.request.WebRequestInterceptor)] of bean 'org.springframework.web.servlet.handler.MappedInterceptor#0': org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'org.springframework.web.servlet.handler.MappedInterceptor#0': Unsatisfied dependency expressed through constructor parameter 1: Could not convert argument value of type [org.springframework.web.servlet.handler.ConversionServiceExposingInterceptor] to required type [org.springframework.web.context.request.WebRequestInterceptor]: Failed to convert value of type 'org.springframework.web.servlet.handler.ConversionServiceExposingInterceptor' to required type 'org.springframework.web.context.request.WebRequestInterceptor'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'org.springframework.web.servlet.handler.ConversionServiceExposingInterceptor' to required type 'org.springframework.web.context.request.WebRequestInterceptor': no matching editors or conversion strategy found
10:34:59.751 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 585: Eagerly caching bean 'org.springframework.web.servlet.handler.MappedInterceptor#0' to allow for resolving potential circular references
# 完成创建
10:34:59.752 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean 'org.springframework.web.servlet.handler.MappedInterceptor#0'

我们再来过一下它的声明,如下

// 还是来自parse方法
RootBeanDefinition mappedInterceptorDef = new RootBeanDefinition(MappedInterceptor.class);
mappedInterceptorDef.setSource(source);
mappedInterceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
mappedInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(0, (Object) null);
mappedInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(1, csInterceptorDef);
String mappedInterceptorName = readerContext.registerWithGeneratedName(mappedInterceptorDef);

context.registerComponent(new BeanComponentDefinition(mappedInterceptorDef, mappedInterceptorName));

/**
 * Call the bean name generator for the given bean definition
 * and register the bean definition under the generated name.
 * @see XmlBeanDefinitionReader#getBeanNameGenerator()
 * @see org.springframework.beans.factory.support.BeanNameGenerator#generateBeanName
 * @see BeanDefinitionRegistry#registerBeanDefinition
 */
public String registerWithGeneratedName(BeanDefinition beanDefinition) {
    String generatedName = generateBeanName(beanDefinition);
    getRegistry().registerBeanDefinition(generatedName, beanDefinition);
    return generatedName;
}

最后再来过一下MappedInterceptor的用法,如下

/**
 * Contains and delegates calls to a {@link HandlerInterceptor} along with
 * include (and optionally exclude) path patterns to which the interceptor should apply.
 * Also provides matching logic to test if the interceptor applies to a given request path.
 *
 * <p>A MappedInterceptor can be registered directly with any
 * {@link org.springframework.web.servlet.handler.AbstractHandlerMethodMapping}.
 * Furthermore, beans of type {@code MappedInterceptor} are automatically detected by
 * {@code AbstractHandlerMethodMapping} (including ancestor ApplicationContext's) which
 * effectively means the interceptor is registered "globally" with all handler mappings.
 */
// 实现了 HandlerInterceptor 接口
public final class MappedInterceptor implements HandlerInterceptor {
    // 省略类属性和方法
}

// 传统MVC还是需要使用这种方式声明注解
<mvc:interceptors>
    <mvc:interceptor>
        <mvc:mapping path="/**" />
        <bean class="xiaokui.config.TestInterceptor" />
    </mvc:interceptor>
</mvc:interceptors>

// 这种方式还是对于SpringBoot的支持,在Spring MVC不适用
@Configuration
public class MvcConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new TestInterceptor()).addPathPatterns("/**");
    }
}

public class TestInterceptor implements HandlerInterceptor {

    private Logger log = LogManager.getLogger(this.getClass());

    /**
     * 如果请求放行(调用Controller之前),则返回true,否则返回false
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.debug("放行请求:" + request.getRequestURI());
        sleep(100);
        return true;
    }

    /**
     * 请求已被处理完(调用Controller之后,返回逻辑视图之前),调用此方法
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        log.debug("执行请求完,返回" + response.getStatus());
        sleep(100);
    }

    /**
     * 在返回逻辑试图之后(通常用来释放资源),调用此方法
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        log.debug("关闭连接资源");
        sleep(100);
    }
}

// 对于<mvc:interceptors>标签的解析类
class InterceptorsBeanDefinitionParser implements BeanDefinitionParser {

	@Override
	@Nullable
	public BeanDefinition parse(Element element, ParserContext context) {
		context.pushContainingComponent(
				new CompositeComponentDefinition(element.getTagName(), context.extractSource(element)));

		RuntimeBeanReference pathMatcherRef = null;
		if (element.hasAttribute("path-matcher")) {
			pathMatcherRef = new RuntimeBeanReference(element.getAttribute("path-matcher"));
		}

		List<Element> interceptors = DomUtils.getChildElementsByTagName(element, "bean", "ref", "interceptor");
		for (Element interceptor : interceptors) {
			RootBeanDefinition mappedInterceptorDef = new RootBeanDefinition(MappedInterceptor.class);
			mappedInterceptorDef.setSource(context.extractSource(interceptor));
			mappedInterceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);

			ManagedList<String> includePatterns = null;
			ManagedList<String> excludePatterns = null;
			Object interceptorBean;
			if ("interceptor".equals(interceptor.getLocalName())) {
				includePatterns = getIncludePatterns(interceptor, "mapping");
				excludePatterns = getIncludePatterns(interceptor, "exclude-mapping");
				Element beanElem = DomUtils.getChildElementsByTagName(interceptor, "bean", "ref").get(0);
				interceptorBean = context.getDelegate().parsePropertySubElement(beanElem, null);
			}
			else {
				interceptorBean = context.getDelegate().parsePropertySubElement(interceptor, null);
			}
			mappedInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(0, includePatterns);
			mappedInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(1, excludePatterns);
			mappedInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(2, interceptorBean);

			if (pathMatcherRef != null) {
				mappedInterceptorDef.getPropertyValues().add("pathMatcher", pathMatcherRef);
			}
			// 注意这一行,值为org.springframework.web.servlet.handler.MappedInterceptor#1
			// 所以我们可以像下面那样做,不使用<mvc:interceptors>来完成拦截器的注入
			String beanName = context.getReaderContext().registerWithGeneratedName(mappedInterceptorDef);
			context.registerComponent(new BeanComponentDefinition(mappedInterceptorDef, beanName));
		}

		context.popAndRegisterContainingComponent();
		return null;
	}
}

// 用一个bean替换掉xml中的<mvc:interceptors>标签
// 这就是阅读源码的好处,能帮你从另一个维度去看问题,并解决问题
// 为什么后面有个#1呢?这其实是Spring自动对bean加的一个标志,标识特定类型bean的序号,一般由Spring自动生成
@Bean("org.springframework.web.servlet.handler.MappedInterceptor#1")
public MappedInterceptor testInterceptor() {
    return new MappedInterceptor(new String[]{"/**"}, new TestInterceptor());
}

/**
 * Call the bean name generator for the given bean definition
 * and register the bean definition under the generated name.
 * @see XmlBeanDefinitionReader#getBeanNameGenerator()
 * @see org.springframework.beans.factory.support.BeanNameGenerator#generateBeanName
 * @see BeanDefinitionRegistry#registerBeanDefinition
 */
public String registerWithGeneratedName(BeanDefinition beanDefinition) {
    String generatedName = generateBeanName(beanDefinition);
    getRegistry().registerBeanDefinition(generatedName, beanDefinition);
    return generatedName;
}

3、afterPropertiesSet**

先上日志,如下

10:34:59.753 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 1841: Invoking afterPropertiesSet() on bean with name 'org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping'
# 这一步我们可以大致猜出它的逻辑,扫描@Controller,将相应方法匹配到相应url,并缓存到Map
# 后续url匹配时,直接调用缓存中相应的方法就行了
10:34:59.792 [0.1][TRACE] servlet.handler.AbstractHandlerMethodMapping 284: 
	x.c.IndexController:
	{ /testPage}: testPage()
	{ /testStr}: testStr()
	{ /testLogin}: testLogin(String,String,HttpServletRequest)
	{ /redirect1}: redirect1(String,String,HttpServletRequest)
	{ /redirect2}: redirect2(String,String,RedirectAttributes)
	{ /redirectTarget}: redirectTarget(String,String,HttpServletRequest)
	{ /forwardTarget}: forwardTarget(String,String)
	{ /redirect}: redirect(String,String)
	{ /forward}: forward(String,String)
	{ [/index, /]}: index()
10:34:59.800 [0.1][DEBUG] servlet.handler.AbstractHandlerMethodMapping 351: 10 mappings in 'org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping'
# 完成RequestMappingHandlerMapping的初始化
10:34:59.801 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean 'org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping'

再来看下代码,如下

// 来自类 RequestMappingHandlerMapping
@Override
public void afterPropertiesSet() {
    // 主要是这一行,虽然默认config就是这样的,这里又再次new了一次
    this.config = new RequestMappingInfo.BuilderConfiguration();
    this.config.setUrlPathHelper(getUrlPathHelper());
    this.config.setPathMatcher(getPathMatcher());
    this.config.setSuffixPatternMatch(this.useSuffixPatternMatch);
    this.config.setTrailingSlashMatch(this.useTrailingSlashMatch);
    this.config.setRegisteredSuffixPatternMatch(this.useRegisteredSuffixPatternMatch);
    this.config.setContentNegotiationManager(getContentNegotiationManager());
    // 我们重点看下这一行
    super.afterPropertiesSet();
}

// 以下方法无特殊说明均来自父类 AbstractHandlerMethodMapping
/**
 * Detects handler methods at initialization.
 * @see #initHandlerMethods
 */
@Override
public void afterPropertiesSet() {
    initHandlerMethods();
}

/**
 * Scan beans in the ApplicationContext, detect and register handler methods.
 * @see #getCandidateBeanNames()
 * @see #processCandidateBean
 * @see #handlerMethodsInitialized
 */
protected void initHandlerMethods() {
     // 遍历所有bean,寻找@Controller
    for (String beanName : getCandidateBeanNames()) {
        if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
            processCandidateBean(beanName);
        }
    }
    handlerMethodsInitialized(getHandlerMethods());
}

protected void processCandidateBean(String beanName) {
    Class<?> beanType = null;
    try {
        beanType = obtainApplicationContext().getType(beanName);
    }
    catch (Throwable ex) {
        // An unresolvable bean type, probably from a lazy bean - let's ignore it.
        if (logger.isTraceEnabled()) {
            logger.trace("Could not resolve type for bean '" + beanName + "'", ex);
        }
    }
    // 注意这个判断,子类RequestMappingHandlerMapping进行是重写,默认空实现
    if (beanType != null && isHandler(beanType)) {
        detectHandlerMethods(beanName);
    }
}

/**
 * {@inheritDoc}
 * <p>Expects a handler to have either a type-level @{@link Controller}
 * annotation or a type-level @{@link RequestMapping} annotation.
 */
// 来自子类 RequestMappingHandlerMapping
@Override
protected boolean isHandler(Class<?> beanType) {
    return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
            AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}

/**
 * Look for handler methods in the specified handler bean.
 * @param handler either a bean name or an actual handler instance
 * @see #getMappingForMethod
 */
protected void detectHandlerMethods(Object handler) {
    // 这里的hander为beanName,即String类型
    Class<?> handlerType = (handler instanceof String ?
            obtainApplicationContext().getType((String) handler) : handler.getClass());

    if (handlerType != null) {
        // 如果是cglib生成的子类,那么这里获取的还是父类
        Class<?> userType = ClassUtils.getUserClass(handlerType);
        // 这行代码又臭又长,但我们需要知道的是到这一步过后,每个@RequestMapping方法都会生成自己的RequestMappingInfo对象
        Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
                (MethodIntrospector.MetadataLookup<T>) method -> {
                    try {
                        return getMappingForMethod(method, userType);
                    }
                    catch (Throwable ex) {
                        throw new IllegalStateException("Invalid mapping on handler class [" +
                                userType.getName() + "]: " + method, ex);
                    }
                });
        if (logger.isTraceEnabled()) {
            // 就是这一行打印了日志中的匹配信息
            // 例如 { /testLogin}: testLogin(String,String,HttpServletRequest)
            logger.trace(formatMappings(userType, methods));
        }
        // 这个东西怎么看都有点不伦不类,类似js中的map((value, index) => {})
        // 这里的method即Map的key,mapping即value
        methods.forEach((method, mapping) -> {
            Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
            // 注册url以特定方法
            registerHandlerMethod(handler, invocableMethod, mapping);
        });
    }
}

// 来自子类 RequestMappingHandlerMapping
@Override
protected void registerHandlerMethod(Object handler, Method method, RequestMappingInfo mapping) {
    super.registerHandlerMethod(handler, method, mapping);
    updateConsumesCondition(mapping, method);
}

/**
 * Register a handler method and its unique mapping. Invoked at startup for
 * each detected handler method.
 * @param handler the bean name of the handler or the handler instance
 * @param method the method to register
 * @param mapping the mapping conditions associated with the handler method
 * @throws IllegalStateException if another method was already registered
 * under the same mapping
 */
protected void registerHandlerMethod(Object handler, Method method, T mapping) {
    this.mappingRegistry.register(mapping, handler, method);
}

public void register(T mapping, Object handler, Method method) {
    // Assert that the handler method is not a suspending one.
    if (KotlinDetector.isKotlinType(method.getDeclaringClass()) && KotlinDelegate.isSuspend(method)) {
        throw new IllegalStateException("Unsupported suspending handler method detected: " + method);
    }
    this.readWriteLock.writeLock().lock();
    try {
        HandlerMethod handlerMethod = createHandlerMethod(handler, method);
        validateMethodMapping(handlerMethod, mapping);
        this.mappingLookup.put(mapping, handlerMethod);

        List<String> directUrls = getDirectUrls(mapping);
        // 注意这一行,所以即使@RequestMapping中有多个匹配路径,这里还是会为分别为每个url单独匹配
        for (String url : directUrls) {
            this.urlLookup.add(url, mapping);
        }

        String name = null;
        if (getNamingStrategy() != null) {
            name = getNamingStrategy().getName(handlerMethod, mapping);
            addMappingName(name, handlerMethod);
        }

        // 这里添加了对于方法级别的跨域的支持
        CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
        if (corsConfig != null) {
            this.corsLookup.put(handlerMethod, corsConfig);
        }

        this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
    }
    finally {
        this.readWriteLock.writeLock().unlock();
    }
}

// 来自子类 
@Override
protected CorsConfiguration initCorsConfiguration(Object handler, Method method, RequestMappingInfo mappingInfo) {
    HandlerMethod handlerMethod = createHandlerMethod(handler, method);
    Class<?> beanType = handlerMethod.getBeanType();
    CrossOrigin typeAnnotation = AnnotatedElementUtils.findMergedAnnotation(beanType, CrossOrigin.class);
    CrossOrigin methodAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, CrossOrigin.class);

    if (typeAnnotation == null && methodAnnotation == null) {
        return null;
    }

    CorsConfiguration config = new CorsConfiguration();
    updateCorsConfig(config, typeAnnotation);
    updateCorsConfig(config, methodAnnotation);

    if (CollectionUtils.isEmpty(config.getAllowedMethods())) {
        for (RequestMethod allowedMethod : mappingInfo.getMethodsCondition().getMethods()) {
            config.addAllowedMethod(allowedMethod.name());
        }
    }
    return config.applyPermitDefaultValues();
}

日志输出如下

10:34:59.752 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean 'org.springframework.web.servlet.handler.MappedInterceptor#0'
10:34:59.753 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 1841: Invoking afterPropertiesSet() on bean with name 'org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping'
10:34:59.792 [0.1][TRACE] servlet.handler.AbstractHandlerMethodMapping 284: 
	x.c.IndexController:
	{ /testPage}: testPage()
	{ /testStr}: testStr()
	{ /testLogin}: testLogin(String,String,HttpServletRequest)
	{ /redirect1}: redirect1(String,String,HttpServletRequest)
	{ /redirect2}: redirect2(String,String,RedirectAttributes)
	{ /redirectTarget}: redirectTarget(String,String,HttpServletRequest)
	{ /forwardTarget}: forwardTarget(String,String)
	{ /redirect}: redirect(String,String)
	{ /forward}: forward(String,String)
	{ [/index, /]}: index()
10:34:59.800 [0.1][DEBUG] servlet.handler.AbstractHandlerMethodMapping 351: 10 mappings in 'org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping'
10:34:59.801 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean 'org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping'

没错,就有这么一大堆。

/**
 * Creates {@link RequestMappingInfo} instances from type and method-level
 * {@link RequestMapping @RequestMapping} annotations in
 * {@link Controller @Controller} classes.
 *
 */
// 回过头来看下类声明,这个类实现了BeanNameAware、InitializingBean、ApplicationContextAware、ServletContextAware接口
public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping
		implements MatchableHandlerMapping, EmbeddedValueResolverAware {
    // 省略其他属性、方法
}
10:34:59.943 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 1841: Invoking afterPropertiesSet() on bean with name 'org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter'
10:34:59.948 [0.1][DEBUG] method.annotation.RequestMappingHandlerAdapter 612: ControllerAdvice beans: none
10:34:59.996 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean 'org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter'
10:34:59.997 [0.1][DEBUG] factory.support.DefaultSingletonBeanRegistry 213: Creating shared instance of singleton bean 'mvcUriComponentsContributor'
10:34:59.997 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean 'mvcUriComponentsContributor'
10:34:59.998 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 585: Eagerly caching bean 'mvcUriComponentsContributor' to allow for resolving potential circular references
10:34:59.998 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean '(inner bean)#3d6b105d'
10:35:00.001 [0.1][TRACE] factory.support.AbstractBeanFactory 257: Returning cached instance of singleton bean 'mvcContentNegotiationManager'
10:35:00.002 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean '(inner bean)#399347ed#1'
10:35:00.002 [0.1][TRACE] factory.support.AbstractBeanFactory 257: Returning cached instance of singleton bean 'org.springframework.format.support.FormattingConversionServiceFactoryBean#0'
10:35:00.003 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean '(inner bean)#399347ed#1'
10:35:00.004 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean '(inner bean)#7bfa90b5#1'
10:35:00.005 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean '(inner bean)#7bfa90b5#1'
10:35:00.005 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean '(inner bean)#6a2d13e7#1'
10:35:00.006 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean '(inner bean)#6a2d13e7#1'
10:35:00.007 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean '(inner bean)#36d6eefe#1'
10:35:00.008 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean '(inner bean)#36d6eefe#1'
10:35:00.009 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean '(inner bean)#47a9e300#1'
10:35:00.010 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean '(inner bean)#47a9e300#1'
10:35:00.010 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean '(inner bean)#6b9c2558#1'
10:35:00.011 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean '(inner bean)#6b9c2558#1'
10:35:00.012 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean '(inner bean)#3e51a1f0#1'
10:35:00.014 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean '(inner bean)#3e51a1f0#1'
10:35:00.014 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean '(inner bean)#1b45787b#1'
10:35:00.018 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean '(inner bean)#1b45787b#1'
10:35:00.020 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 1841: Invoking afterPropertiesSet() on bean with name '(inner bean)#3d6b105d'
10:35:00.022 [0.1][DEBUG] method.annotation.RequestMappingHandlerAdapter 612: ControllerAdvice beans: none
10:35:00.023 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean '(inner bean)#3d6b105d'
10:35:00.025 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 270: Getting BeanInfo for class [org.springframework.web.servlet.config.AnnotationDrivenBeanDefinitionParser$CompositeUriComponentsContributorFactoryBean]
10:35:00.030 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 275: Caching PropertyDescriptors for class [org.springframework.web.servlet.config.AnnotationDrivenBeanDefinitionParser$CompositeUriComponentsContributorFactoryBean]
10:35:00.030 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'class' of type [java.lang.Class]
10:35:00.030 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'conversionService' of type [org.springframework.core.convert.ConversionService]
10:35:00.031 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'handlerAdapter' of type [org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter]
10:35:00.031 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'object' of type [org.springframework.web.method.support.CompositeUriComponentsContributor]
10:35:00.032 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'objectType' of type [java.lang.Class]
10:35:00.032 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'singleton' of type [boolean]
10:35:00.033 [0.1][TRACE] factory.support.AbstractBeanFactory 257: Returning cached instance of singleton bean 'org.springframework.format.support.FormattingConversionServiceFactoryBean#0'
10:35:00.033 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 1841: Invoking afterPropertiesSet() on bean with name 'mvcUriComponentsContributor'
10:35:00.034 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean 'mvcUriComponentsContributor'
10:35:00.035 [0.1][TRACE] factory.support.AbstractBeanFactory 257: Returning cached instance of singleton bean 'org.springframework.web.servlet.handler.MappedInterceptor#0'
10:35:00.035 [0.1][DEBUG] factory.support.DefaultSingletonBeanRegistry 213: Creating shared instance of singleton bean 'org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#0'
10:35:00.035 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean 'org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#0'
10:35:00.039 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 585: Eagerly caching bean 'org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#0' to allow for resolving potential circular references
10:35:00.039 [0.1][TRACE] factory.support.AbstractBeanFactory 257: Returning cached instance of singleton bean 'mvcContentNegotiationManager'
10:35:00.040 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 270: Getting BeanInfo for class [org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver]
10:35:00.063 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 275: Caching PropertyDescriptors for class [org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver]
10:35:00.063 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'applicationContext' of type [org.springframework.context.ApplicationContext]
10:35:00.064 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'argumentResolvers' of type [org.springframework.web.method.support.HandlerMethodArgumentResolverComposite]
10:35:00.066 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'class' of type [java.lang.Class]
10:35:00.068 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'contentNegotiationManager' of type [org.springframework.web.accept.ContentNegotiationManager]
10:35:00.069 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'customArgumentResolvers' of type [java.util.List]
10:35:00.069 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'customReturnValueHandlers' of type [java.util.List]
10:35:00.074 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'exceptionHandlerAdviceCache' of type [java.util.Map]
10:35:00.074 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'mappedHandlerClasses' of type [[Ljava.lang.Class;]
10:35:00.077 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'mappedHandlers' of type [java.util.Set]
10:35:00.077 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'messageConverters' of type [java.util.List]
10:35:00.078 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'order' of type [int]
10:35:00.078 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'preventResponseCaching' of type [boolean]
10:35:00.078 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'responseBodyAdvice' of type [java.util.List]
10:35:00.079 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'returnValueHandlers' of type [org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite]
10:35:00.079 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'warnLogCategory' of type [java.lang.String]
10:35:00.085 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean '(inner bean)#7bfa90b5#2'
10:35:00.086 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean '(inner bean)#7bfa90b5#2'
10:35:00.087 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean '(inner bean)#6a2d13e7#2'
10:35:00.088 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean '(inner bean)#6a2d13e7#2'
10:35:00.088 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean '(inner bean)#36d6eefe#2'
10:35:00.089 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean '(inner bean)#36d6eefe#2'
10:35:00.090 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean '(inner bean)#47a9e300#2'
10:35:00.090 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean '(inner bean)#47a9e300#2'
10:35:00.091 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean '(inner bean)#6b9c2558#2'
10:35:00.092 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean '(inner bean)#6b9c2558#2'
10:35:00.092 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean '(inner bean)#3e51a1f0#2'
10:35:00.096 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean '(inner bean)#3e51a1f0#2'
10:35:00.097 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean '(inner bean)#1b45787b#2'
10:35:00.102 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean '(inner bean)#1b45787b#2'
10:35:00.107 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 1841: Invoking afterPropertiesSet() on bean with name 'org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#0'
10:35:00.109 [0.1][DEBUG] method.annotation.ExceptionHandlerExceptionResolver 294: ControllerAdvice beans: none
10:35:00.110 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean 'org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#0'
10:35:00.110 [0.1][DEBUG] factory.support.DefaultSingletonBeanRegistry 213: Creating shared instance of singleton bean 'org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver#0'
10:35:00.110 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean 'org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver#0'
10:35:00.111 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 585: Eagerly caching bean 'org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver#0' to allow for resolving potential circular references
10:35:00.112 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 270: Getting BeanInfo for class [org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver]
10:35:00.118 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 275: Caching PropertyDescriptors for class [org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver]
10:35:00.118 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'class' of type [java.lang.Class]
10:35:00.119 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'mappedHandlerClasses' of type [[Ljava.lang.Class;]
10:35:00.120 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'mappedHandlers' of type [java.util.Set]
10:35:00.120 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'messageSource' of type [org.springframework.context.MessageSource]
10:35:00.121 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'order' of type [int]
10:35:00.121 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'preventResponseCaching' of type [boolean]
10:35:00.122 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'warnLogCategory' of type [java.lang.String]
10:35:00.126 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean 'org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver#0'
10:35:00.126 [0.1][DEBUG] factory.support.DefaultSingletonBeanRegistry 213: Creating shared instance of singleton bean 'org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver#0'
10:35:00.127 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean 'org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver#0'
10:35:00.128 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 585: Eagerly caching bean 'org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver#0' to allow for resolving potential circular references
10:35:00.128 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 270: Getting BeanInfo for class [org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver]
10:35:00.134 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 275: Caching PropertyDescriptors for class [org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver]
10:35:00.134 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'class' of type [java.lang.Class]
10:35:00.134 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'mappedHandlerClasses' of type [[Ljava.lang.Class;]
10:35:00.135 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'mappedHandlers' of type [java.util.Set]
10:35:00.135 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'order' of type [int]
10:35:00.136 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'preventResponseCaching' of type [boolean]
10:35:00.136 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'warnLogCategory' of type [java.lang.String]
10:35:00.137 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean 'org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver#0'
10:35:00.138 [0.1][DEBUG] factory.support.DefaultSingletonBeanRegistry 213: Creating shared instance of singleton bean 'org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping'
10:35:00.138 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean 'org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping'
10:35:00.140 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 585: Eagerly caching bean 'org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping' to allow for resolving potential circular references
10:35:00.141 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 270: Getting BeanInfo for class [org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping]
10:35:00.150 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 275: Caching PropertyDescriptors for class [org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping]
10:35:00.150 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'alwaysUseFullPath' of type [boolean]
10:35:00.151 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'applicationContext' of type [org.springframework.context.ApplicationContext]
10:35:00.151 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'beanName' of type [java.lang.String]
10:35:00.151 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'class' of type [java.lang.Class]
10:35:00.152 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'corsConfigurationSource' of type [org.springframework.web.cors.CorsConfigurationSource]
10:35:00.152 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'corsConfigurations' of type [java.util.Map]
10:35:00.153 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'corsProcessor' of type [org.springframework.web.cors.CorsProcessor]
10:35:00.153 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'defaultHandler' of type [java.lang.Object]
10:35:00.153 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'detectHandlersInAncestorContexts' of type [boolean]
10:35:00.154 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'handlerMap' of type [java.util.Map]
10:35:00.155 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'interceptors' of type [[Ljava.lang.Object;]
10:35:00.156 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'lazyInitHandlers' of type [boolean]
10:35:00.156 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'order' of type [int]
10:35:00.156 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'pathMatcher' of type [org.springframework.util.PathMatcher]
10:35:00.157 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'removeSemicolonContent' of type [boolean]
10:35:00.157 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'rootHandler' of type [java.lang.Object]
10:35:00.157 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'servletContext' of type [javax.servlet.ServletContext]
10:35:00.158 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'urlDecode' of type [boolean]
10:35:00.158 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'urlPathHelper' of type [org.springframework.web.util.UrlPathHelper]
10:35:00.158 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'useTrailingSlashMatch' of type [boolean]
10:35:00.159 [0.1][TRACE] factory.support.AbstractBeanFactory 257: Returning cached instance of singleton bean 'mvcCorsConfigurations'
10:35:00.160 [0.1][TRACE] factory.support.AbstractBeanFactory 257: Returning cached instance of singleton bean 'org.springframework.web.servlet.handler.MappedInterceptor#0'
10:35:00.162 [0.1][DEBUG] factory.support.DefaultSingletonBeanRegistry 213: Creating shared instance of singleton bean '/last'
10:35:00.163 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean '/last'
10:35:00.164 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 585: Eagerly caching bean '/last' to allow for resolving potential circular references
10:35:00.165 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean '/last'
10:35:00.166 [0.1][TRACE] servlet.handler.AbstractUrlHandlerMapping 368: Mapped [/last] onto '/last'
10:35:00.166 [0.1][DEBUG] servlet.handler.AbstractDetectingUrlHandlerMapping 86: Detected 1 mappings in 'org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping'
10:35:00.167 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean 'org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping'
10:35:00.167 [0.1][DEBUG] factory.support.DefaultSingletonBeanRegistry 213: Creating shared instance of singleton bean 'org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter'
10:35:00.168 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean 'org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter'
10:35:00.168 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 585: Eagerly caching bean 'org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter' to allow for resolving potential circular references
10:35:00.168 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean 'org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter'
10:35:00.169 [0.1][DEBUG] factory.support.DefaultSingletonBeanRegistry 213: Creating shared instance of singleton bean 'org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter'
10:35:00.169 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean 'org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter'
10:35:00.170 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 585: Eagerly caching bean 'org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter' to allow for resolving potential circular references
10:35:00.170 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean 'org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter'
10:35:00.170 [0.1][DEBUG] factory.support.DefaultSingletonBeanRegistry 213: Creating shared instance of singleton bean 'afterRun'
10:35:00.171 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean 'afterRun'
10:35:00.171 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 585: Eagerly caching bean 'afterRun' to allow for resolving potential circular references
10:35:00.173 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean 'afterRun'
10:35:00.173 [0.1][DEBUG] factory.support.DefaultSingletonBeanRegistry 213: Creating shared instance of singleton bean 'springConfig'
10:35:00.174 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean 'springConfig'
10:35:00.174 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 585: Eagerly caching bean 'springConfig' to allow for resolving potential circular references
10:35:00.176 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean 'springConfig'
10:35:00.176 [0.1][DEBUG] factory.support.DefaultSingletonBeanRegistry 213: Creating shared instance of singleton bean 'indexController'
10:35:00.176 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean 'indexController'
10:35:00.183 [0.1][TRACE] factory.annotation.InjectionMetadata 100: Registered injected element on class [xiaokui.controller.IndexController]: AutowiredFieldElement for private xiaokui.service.UserService xiaokui.controller.IndexController.userService
10:35:00.183 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 585: Eagerly caching bean 'indexController' to allow for resolving potential circular references
10:35:00.184 [0.1][TRACE] factory.annotation.InjectionMetadata 114: Processing injected element of bean 'indexController': AutowiredFieldElement for private xiaokui.service.UserService xiaokui.controller.IndexController.userService
10:35:00.190 [0.1][DEBUG] factory.support.DefaultSingletonBeanRegistry 213: Creating shared instance of singleton bean 'userService'
10:35:00.190 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean 'userService'
10:35:00.191 [0.1][TRACE] factory.annotation.InjectionMetadata 100: Registered injected element on class [xiaokui.service.UserService]: AutowiredFieldElement for private xiaokui.dao.UserDao xiaokui.service.UserService.userDao
10:35:00.192 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 585: Eagerly caching bean 'userService' to allow for resolving potential circular references
10:35:00.192 [0.1][TRACE] factory.annotation.InjectionMetadata 114: Processing injected element of bean 'userService': AutowiredFieldElement for private xiaokui.dao.UserDao xiaokui.service.UserService.userDao
10:35:00.193 [0.1][DEBUG] factory.support.DefaultSingletonBeanRegistry 213: Creating shared instance of singleton bean 'userDao'
10:35:00.193 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean 'userDao'
10:35:00.194 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 585: Eagerly caching bean 'userDao' to allow for resolving potential circular references
10:35:00.195 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean 'userDao'
10:35:00.196 [0.1][TRACE] factory.annotation.AutowiredAnnotationBeanPostProcessor 586: Autowiring by type from bean name 'userService' to bean named 'userDao'
10:35:00.197 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean 'userService'
10:35:00.198 [0.1][TRACE] factory.annotation.AutowiredAnnotationBeanPostProcessor 586: Autowiring by type from bean name 'indexController' to bean named 'userService'
10:35:00.198 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean 'indexController'
10:35:00.198 [0.1][TRACE] factory.support.AbstractBeanFactory 257: Returning cached instance of singleton bean '/last'
10:35:00.199 [0.1][TRACE] factory.support.AbstractBeanFactory 257: Returning cached instance of singleton bean 'userDao'
10:35:00.199 [0.1][TRACE] factory.support.AbstractBeanFactory 257: Returning cached instance of singleton bean 'userService'
10:35:00.199 [0.1][TRACE] factory.support.AbstractBeanFactory 257: Returning cached instance of singleton bean 'org.springframework.context.annotation.internalConfigurationAnnotationProcessor'
10:35:00.199 [0.1][TRACE] factory.support.AbstractBeanFactory 257: Returning cached instance of singleton bean 'org.springframework.context.annotation.internalAutowiredAnnotationProcessor'
10:35:00.199 [0.1][TRACE] factory.support.AbstractBeanFactory 257: Returning cached instance of singleton bean 'org.springframework.context.annotation.internalCommonAnnotationProcessor'
10:35:00.200 [0.1][TRACE] factory.support.AbstractBeanFactory 257: Returning cached instance of singleton bean 'org.springframework.context.event.internalEventListenerProcessor'
10:35:00.200 [0.1][TRACE] factory.support.AbstractBeanFactory 257: Returning cached instance of singleton bean 'org.springframework.context.event.internalEventListenerFactory'
10:35:00.200 [0.1][DEBUG] factory.support.DefaultSingletonBeanRegistry 213: Creating shared instance of singleton bean 'userBean'
10:35:00.200 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean 'userBean'
10:35:00.201 [0.1][TRACE] factory.support.AbstractBeanFactory 257: Returning cached instance of singleton bean 'springConfig'
10:35:00.227 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 585: Eagerly caching bean 'userBean' to allow for resolving potential circular references
10:35:00.227 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean 'userBean'
10:35:00.230 [0.1][TRACE] context.event.EventListenerMethodProcessor 166: No @EventListener annotations found on bean class: xiaokui.config.AfterRun
10:35:00.231 [0.1][TRACE] context.event.EventListenerMethodProcessor 166: No @EventListener annotations found on bean class: xiaokui.config.SpringConfig$$EnhancerBySpringCGLIB$$91072d89
10:35:00.231 [0.1][TRACE] context.event.EventListenerMethodProcessor 166: No @EventListener annotations found on bean class: xiaokui.controller.IndexController
10:35:00.234 [0.1][TRACE] context.event.EventListenerMethodProcessor 166: No @EventListener annotations found on bean class: xiaokui.controller.LastModifiedCacheController
10:35:00.234 [0.1][TRACE] context.event.EventListenerMethodProcessor 166: No @EventListener annotations found on bean class: xiaokui.dao.UserDao
10:35:00.235 [0.1][TRACE] context.event.EventListenerMethodProcessor 166: No @EventListener annotations found on bean class: xiaokui.service.UserService
10:35:00.235 [0.1][TRACE] context.event.EventListenerMethodProcessor 166: No @EventListener annotations found on bean class: xiaokui.entity.Apple
10:35:00.238 [0.1][TRACE] context.event.EventListenerMethodProcessor 166: No @EventListener annotations found on bean class: org.apache.catalina.core.ApplicationContextFacade
10:35:00.240 [0.1][TRACE] context.support.AbstractApplicationContext 802: No 'lifecycleProcessor' bean, using [DefaultLifecycleProcessor]
10:35:00.241 [0.1][TRACE] factory.support.AbstractBeanFactory 257: Returning cached instance of singleton bean 'lifecycleProcessor'
10:35:00.248 [0.1][TRACE] factory.support.AbstractBeanFactory 257: Returning cached instance of singleton bean 'afterRun'
org.apache.commons.logging.impl.Log4JLogger@16c90e82
class org.apache.commons.logging.impl.Log4JLogger
10:35:00.255 [0.1][TRACE] core.env.PropertySourcesPropertyResolver 82: Searching for key 'spring.liveBeansView.mbeanDomain' in PropertySource 'servletConfigInitParams'
10:35:00.256 [0.1][TRACE] core.env.PropertySourcesPropertyResolver 82: Searching for key 'spring.liveBeansView.mbeanDomain' in PropertySource 'servletContextInitParams'
10:35:00.257 [0.1][TRACE] core.env.PropertySourcesPropertyResolver 82: Searching for key 'spring.liveBeansView.mbeanDomain' in PropertySource 'jndiProperties'
10:35:00.257 [0.1][DEBUG] springframework.jndi.JndiTemplate 155: Looking up JNDI object with name [java:comp/env/spring.liveBeansView.mbeanDomain]
10:35:00.262 [0.1][DEBUG] springframework.jndi.JndiLocatorSupport 102: Converted JNDI name [java:comp/env/spring.liveBeansView.mbeanDomain] not found - trying original name [spring.liveBeansView.mbeanDomain]. javax.naming.NameNotFoundException: Name [spring.liveBeansView.mbeanDomain] is not bound in this Context. Unable to find [spring.liveBeansView.mbeanDomain].
10:35:00.264 [0.1][DEBUG] springframework.jndi.JndiTemplate 155: Looking up JNDI object with name [spring.liveBeansView.mbeanDomain]
10:35:00.265 [0.1][DEBUG] springframework.jndi.JndiPropertySource 101: JNDI lookup for name [spring.liveBeansView.mbeanDomain] threw NamingException with message: Name [spring.liveBeansView.mbeanDomain] is not bound in this Context. Unable to find [spring.liveBeansView.mbeanDomain].. Returning null.
10:35:00.266 [0.1][TRACE] core.env.PropertySourcesPropertyResolver 82: Searching for key 'spring.liveBeansView.mbeanDomain' in PropertySource 'systemProperties'
10:35:00.266 [0.1][TRACE] core.env.PropertySourcesPropertyResolver 82: Searching for key 'spring.liveBeansView.mbeanDomain' in PropertySource 'systemEnvironment'
10:35:00.270 [0.1][TRACE] core.env.PropertySourcesPropertyResolver 96: Could not find key 'spring.liveBeansView.mbeanDomain' in any property source
10:35:00.284 [0.1][INFO] web.context.ContextLoader 307: Root WebApplicationContext initialized in 1941 ms
10:35:00.326 [0.1][INFO] web.servlet.FrameworkServlet 525: Initializing Servlet 'dispatcher'
10:35:00.329 [0.1][TRACE] core.env.PropertySourcesPropertyResolver 82: Searching for key 'spring.profiles.active' in PropertySource 'servletConfigInitParams'
10:35:00.329 [0.1][TRACE] core.env.PropertySourcesPropertyResolver 82: Searching for key 'spring.profiles.active' in PropertySource 'servletContextInitParams'
10:35:00.329 [0.1][TRACE] core.env.PropertySourcesPropertyResolver 82: Searching for key 'spring.profiles.active' in PropertySource 'jndiProperties'
10:35:00.330 [0.1][DEBUG] springframework.jndi.JndiTemplate 155: Looking up JNDI object with name [java:comp/env/spring.profiles.active]
10:35:00.330 [0.1][DEBUG] springframework.jndi.JndiLocatorSupport 102: Converted JNDI name [java:comp/env/spring.profiles.active] not found - trying original name [spring.profiles.active]. javax.naming.NameNotFoundException: Name [spring.profiles.active] is not bound in this Context. Unable to find [spring.profiles.active].
10:35:00.331 [0.1][DEBUG] springframework.jndi.JndiTemplate 155: Looking up JNDI object with name [spring.profiles.active]
10:35:00.331 [0.1][DEBUG] springframework.jndi.JndiPropertySource 101: JNDI lookup for name [spring.profiles.active] threw NamingException with message: Name [spring.profiles.active] is not bound in this Context. Unable to find [spring.profiles.active].. Returning null.
10:35:00.331 [0.1][TRACE] core.env.PropertySourcesPropertyResolver 82: Searching for key 'spring.profiles.active' in PropertySource 'systemProperties'
10:35:00.332 [0.1][TRACE] core.env.PropertySourcesPropertyResolver 82: Searching for key 'spring.profiles.active' in PropertySource 'systemEnvironment'
10:35:00.332 [0.1][TRACE] core.env.PropertySourcesPropertyResolver 96: Could not find key 'spring.profiles.active' in any property source
10:35:00.332 [0.1][TRACE] core.env.PropertySourcesPropertyResolver 82: Searching for key 'spring.profiles.default' in PropertySource 'servletConfigInitParams'
10:35:00.332 [0.1][TRACE] core.env.PropertySourcesPropertyResolver 82: Searching for key 'spring.profiles.default' in PropertySource 'servletContextInitParams'
10:35:00.333 [0.1][TRACE] core.env.PropertySourcesPropertyResolver 82: Searching for key 'spring.profiles.default' in PropertySource 'jndiProperties'
10:35:00.333 [0.1][DEBUG] springframework.jndi.JndiTemplate 155: Looking up JNDI object with name [java:comp/env/spring.profiles.default]
10:35:00.333 [0.1][DEBUG] springframework.jndi.JndiLocatorSupport 102: Converted JNDI name [java:comp/env/spring.profiles.default] not found - trying original name [spring.profiles.default]. javax.naming.NameNotFoundException: Name [spring.profiles.default] is not bound in this Context. Unable to find [spring.profiles.default].
10:35:00.333 [0.1][DEBUG] springframework.jndi.JndiTemplate 155: Looking up JNDI object with name [spring.profiles.default]
10:35:00.335 [0.1][DEBUG] springframework.jndi.JndiPropertySource 101: JNDI lookup for name [spring.profiles.default] threw NamingException with message: Name [spring.profiles.default] is not bound in this Context. Unable to find [spring.profiles.default].. Returning null.
10:35:00.335 [0.1][TRACE] core.env.PropertySourcesPropertyResolver 82: Searching for key 'spring.profiles.default' in PropertySource 'systemProperties'
10:35:00.336 [0.1][TRACE] core.env.PropertySourcesPropertyResolver 82: Searching for key 'spring.profiles.default' in PropertySource 'systemEnvironment'
10:35:00.336 [0.1][TRACE] core.env.PropertySourcesPropertyResolver 96: Could not find key 'spring.profiles.default' in any property source
10:35:00.338 [0.1][TRACE] context.support.AbstractApplicationContext 592: Refreshing WebApplicationContext for namespace 'dispatcher-servlet', started on Thu May 07 10:35:00 CST 2020, parent: Root WebApplicationContext
10:35:00.344 [0.1][TRACE] factory.xml.XmlBeanDefinitionReader 318: Loading XML bean definitions from ServletContext resource [/WEB-INF/dispatcher-servlet.xml]
10:35:00.346 [0.1][TRACE] factory.xml.DefaultDocumentLoader 74: Using JAXP provider [com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl]
10:35:00.350 [0.1][TRACE] factory.xml.PluggableSchemaResolver 111: Trying to resolve XML entity with public id [null] and system id [http://www.springframework.org/schema/beans/spring-beans.xsd]
10:35:00.350 [0.1][TRACE] factory.xml.PluggableSchemaResolver 154: Loading schema mappings from [META-INF/spring.schemas]
10:35:00.353 [0.1][TRACE] factory.xml.PluggableSchemaResolver 160: Loaded schema mappings: {https://www.springframework.org/schema/jdbc/spring-jdbc.xsd=org/springframework/jdbc/config/spring-jdbc.xsd, https://www.springframework.org/schema/aop/spring-aop.xsd=org/springframework/aop/config/spring-aop.xsd, https://www.springframework.org/schema/tx/spring-tx-4.3.xsd=org/springframework/transaction/config/spring-tx.xsd......以下省略50行
10:35:00.354 [0.1][TRACE] factory.xml.PluggableSchemaResolver 128: Found XML schema [http://www.springframework.org/schema/beans/spring-beans.xsd] in classpath: org/springframework/beans/factory/xml/spring-beans.xsd
10:35:00.375 [0.1][TRACE] factory.xml.BeanDefinitionParserDelegate 458: Neither XML 'id' nor 'name' specified - using generated bean name [org.springframework.web.servlet.view.InternalResourceViewResolver#0]
10:35:00.376 [0.1][TRACE] springframework.core.SimpleAliasRegistry 82: Alias definition 'org.springframework.web.servlet.view.InternalResourceViewResolver' registered for name 'org.springframework.web.servlet.view.InternalResourceViewResolver#0'
10:35:00.377 [0.1][DEBUG] factory.xml.XmlBeanDefinitionReader 396: Loaded 1 bean definitions from ServletContext resource [/WEB-INF/dispatcher-servlet.xml]
10:35:00.377 [0.1][TRACE] factory.support.AbstractBeanDefinitionReader 229: Loaded 1 bean definitions from location pattern [/WEB-INF/dispatcher-servlet.xml]
10:35:00.377 [0.1][TRACE] factory.support.AbstractBeanFactory 980: Registering scope 'request' with implementation [org.springframework.web.context.request.RequestScope@3c6d1ed]
10:35:00.378 [0.1][TRACE] factory.support.AbstractBeanFactory 980: Registering scope 'session' with implementation [org.springframework.web.context.request.SessionScope@376104e0]
10:35:00.378 [0.1][TRACE] factory.support.AbstractBeanFactory 980: Registering scope 'application' with implementation [org.springframework.web.context.support.ServletContextScope@5eadb738]
10:35:00.381 [0.1][TRACE] context.support.AbstractApplicationContext 753: No 'messageSource' bean, using [Empty MessageSource]
10:35:00.382 [0.1][TRACE] context.support.AbstractApplicationContext 776: No 'applicationEventMulticaster' bean, using [SimpleApplicationEventMulticaster]
10:35:00.382 [0.1][DEBUG] context.support.UiApplicationContextUtils 85: Unable to locate ThemeSource with name 'themeSource': using default [org.springframework.ui.context.support.DelegatingThemeSource@640e0c2c]
10:35:00.383 [0.1][TRACE] factory.support.DefaultListableBeanFactory 848: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@1dc24bf8: defining beans [org.springframework.web.servlet.view.InternalResourceViewResolver#0]; parent: org.springframework.beans.factory.support.DefaultListableBeanFactory@a2ea04d
10:35:00.383 [0.1][DEBUG] factory.support.DefaultSingletonBeanRegistry 213: Creating shared instance of singleton bean 'org.springframework.web.servlet.view.InternalResourceViewResolver#0'
10:35:00.384 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean 'org.springframework.web.servlet.view.InternalResourceViewResolver#0'
10:35:00.393 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 585: Eagerly caching bean 'org.springframework.web.servlet.view.InternalResourceViewResolver#0' to allow for resolving potential circular references
10:35:00.394 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 270: Getting BeanInfo for class [org.springframework.web.servlet.view.InternalResourceViewResolver]
10:35:00.403 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 275: Caching PropertyDescriptors for class [org.springframework.web.servlet.view.InternalResourceViewResolver]
10:35:00.403 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'alwaysInclude' of type [boolean]
10:35:00.404 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'applicationContext' of type [org.springframework.context.ApplicationContext]
10:35:00.404 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'attributes' of type [java.util.Properties]
10:35:00.404 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'attributesMap' of type [java.util.Map]
10:35:00.405 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'cache' of type [boolean]
10:35:00.405 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'cacheFilter' of type [org.springframework.web.servlet.view.AbstractCachingViewResolver$CacheFilter]
10:35:00.405 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'cacheLimit' of type [int]
10:35:00.406 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'cacheUnresolved' of type [boolean]
10:35:00.406 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'class' of type [java.lang.Class]
10:35:00.407 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'contentType' of type [java.lang.String]
10:35:00.407 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'exposeContextBeansAsAttributes' of type [boolean]
10:35:00.407 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'exposePathVariables' of type [java.lang.Boolean]
10:35:00.408 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'exposedContextBeanNames' of type [[Ljava.lang.String;]
10:35:00.408 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'order' of type [int]
10:35:00.408 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'prefix' of type [java.lang.String]
10:35:00.409 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'redirectContextRelative' of type [boolean]
10:35:00.409 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'redirectHosts' of type [[Ljava.lang.String;]
10:35:00.409 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'redirectHttp10Compatible' of type [boolean]
10:35:00.410 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'requestContextAttribute' of type [java.lang.String]
10:35:00.410 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'servletContext' of type [javax.servlet.ServletContext]
10:35:00.410 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'suffix' of type [java.lang.String]
10:35:00.411 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'viewClass' of type [java.lang.Class]
10:35:00.411 [0.1][TRACE] springframework.beans.CachedIntrospectionResults 288: Found bean property 'viewNames' of type [[Ljava.lang.String;]
10:35:00.415 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean 'org.springframework.web.servlet.view.InternalResourceViewResolver#0'
10:35:00.416 [0.1][TRACE] context.support.AbstractApplicationContext 802: No 'lifecycleProcessor' bean, using [DefaultLifecycleProcessor]
10:35:00.416 [0.1][TRACE] factory.support.AbstractBeanFactory 257: Returning cached instance of singleton bean 'lifecycleProcessor'
10:35:00.419 [0.1][TRACE] factory.support.DefaultListableBeanFactory 803: No bean named 'multipartResolver' found in org.springframework.beans.factory.support.DefaultListableBeanFactory@a2ea04d: defining beans [org.springframework.aop.config.internalAutoProxyCreator,mvcContentNegotiationManager,org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,mvcCorsConfigurations,org.springframework.format.support.FormattingConversionServiceFactoryBean#0,org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,mvcUriComponentsContributor,org.springframework.web.servlet.handler.MappedInterceptor#0,org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#0,org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver#0,org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver#0,org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,mvcHandlerMappingIntrospector,afterRun,springConfig,indexController,/last,userDao,userService,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.context.event.internalEventListenerProcessor,org.springframework.context.event.internalEventListenerFactory,userBean]; root of factory hierarchy
10:35:00.420 [0.1][TRACE] web.servlet.DispatcherServlet 533: No MultipartResolver 'multipartResolver' declared
10:35:00.422 [0.1][TRACE] factory.support.DefaultListableBeanFactory 803: No bean named 'localeResolver' found in org.springframework.beans.factory.support.DefaultListableBeanFactory@a2ea04d: defining beans [org.springframework.aop.config.internalAutoProxyCreator,mvcContentNegotiationManager,org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,mvcCorsConfigurations,org.springframework.format.support.FormattingConversionServiceFactoryBean#0,org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,mvcUriComponentsContributor,org.springframework.web.servlet.handler.MappedInterceptor#0,org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#0,org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver#0,org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver#0,org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,mvcHandlerMappingIntrospector,afterRun,springConfig,indexController,/last,userDao,userService,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.context.event.internalEventListenerProcessor,org.springframework.context.event.internalEventListenerFactory,userBean]; root of factory hierarchy
10:35:00.424 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean 'org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver'
10:35:00.426 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean 'org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver'
10:35:00.428 [0.1][TRACE] web.servlet.DispatcherServlet 557: No LocaleResolver 'localeResolver': using default [AcceptHeaderLocaleResolver]
10:35:00.430 [0.1][TRACE] factory.support.DefaultListableBeanFactory 803: No bean named 'themeResolver' found in org.springframework.beans.factory.support.DefaultListableBeanFactory@a2ea04d: defining beans [org.springframework.aop.config.internalAutoProxyCreator,mvcContentNegotiationManager,org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,mvcCorsConfigurations,org.springframework.format.support.FormattingConversionServiceFactoryBean#0,org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,mvcUriComponentsContributor,org.springframework.web.servlet.handler.MappedInterceptor#0,org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#0,org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver#0,org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver#0,org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,mvcHandlerMappingIntrospector,afterRun,springConfig,indexController,/last,userDao,userService,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.context.event.internalEventListenerProcessor,org.springframework.context.event.internalEventListenerFactory,userBean]; root of factory hierarchy
10:35:00.432 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean 'org.springframework.web.servlet.theme.FixedThemeResolver'
10:35:00.433 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean 'org.springframework.web.servlet.theme.FixedThemeResolver'
10:35:00.434 [0.1][TRACE] web.servlet.DispatcherServlet 582: No ThemeResolver 'themeResolver': using default [FixedThemeResolver]
10:35:00.434 [0.1][TRACE] factory.support.AbstractBeanFactory 257: Returning cached instance of singleton bean 'org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping'
10:35:00.435 [0.1][TRACE] factory.support.AbstractBeanFactory 257: Returning cached instance of singleton bean 'org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping'
10:35:00.436 [0.1][TRACE] factory.support.AbstractBeanFactory 257: Returning cached instance of singleton bean 'org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter'
10:35:00.437 [0.1][TRACE] factory.support.AbstractBeanFactory 257: Returning cached instance of singleton bean 'org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter'
10:35:00.437 [0.1][TRACE] factory.support.AbstractBeanFactory 257: Returning cached instance of singleton bean 'org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter'
10:35:00.439 [0.1][TRACE] factory.support.AbstractBeanFactory 257: Returning cached instance of singleton bean 'org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#0'
10:35:00.440 [0.1][TRACE] factory.support.AbstractBeanFactory 257: Returning cached instance of singleton bean 'org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver#0'
10:35:00.440 [0.1][TRACE] factory.support.AbstractBeanFactory 257: Returning cached instance of singleton bean 'org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver#0'
10:35:00.440 [0.1][TRACE] factory.support.DefaultListableBeanFactory 803: No bean named 'viewNameTranslator' found in org.springframework.beans.factory.support.DefaultListableBeanFactory@a2ea04d: defining beans [org.springframework.aop.config.internalAutoProxyCreator,mvcContentNegotiationManager,org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,mvcCorsConfigurations,org.springframework.format.support.FormattingConversionServiceFactoryBean#0,org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,mvcUriComponentsContributor,org.springframework.web.servlet.handler.MappedInterceptor#0,org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#0,org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver#0,org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver#0,org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,mvcHandlerMappingIntrospector,afterRun,springConfig,indexController,/last,userDao,userService,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.context.event.internalEventListenerProcessor,org.springframework.context.event.internalEventListenerFactory,userBean]; root of factory hierarchy
10:35:00.441 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean 'org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator'
10:35:00.442 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean 'org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator'
10:35:00.443 [0.1][TRACE] web.servlet.DispatcherServlet 725: No RequestToViewNameTranslator 'viewNameTranslator': using default [DefaultRequestToViewNameTranslator]
10:35:00.443 [0.1][TRACE] factory.support.AbstractBeanFactory 257: Returning cached instance of singleton bean 'org.springframework.web.servlet.view.InternalResourceViewResolver#0'
10:35:00.447 [0.1][TRACE] factory.support.DefaultListableBeanFactory 803: No bean named 'flashMapManager' found in org.springframework.beans.factory.support.DefaultListableBeanFactory@a2ea04d: defining beans [org.springframework.aop.config.internalAutoProxyCreator,mvcContentNegotiationManager,org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,mvcCorsConfigurations,org.springframework.format.support.FormattingConversionServiceFactoryBean#0,org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,mvcUriComponentsContributor,org.springframework.web.servlet.handler.MappedInterceptor#0,org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#0,org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver#0,org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver#0,org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,mvcHandlerMappingIntrospector,afterRun,springConfig,indexController,/last,userDao,userService,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.context.event.internalEventListenerProcessor,org.springframework.context.event.internalEventListenerFactory,userBean]; root of factory hierarchy
10:35:00.452 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 482: Creating instance of bean 'org.springframework.web.servlet.support.SessionFlashMapManager'
10:35:00.453 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean 'org.springframework.web.servlet.support.SessionFlashMapManager'
10:35:00.453 [0.1][TRACE] web.servlet.DispatcherServlet 789: No FlashMapManager 'flashMapManager': using default [SessionFlashMapManager]
org.apache.commons.logging.impl.Log4JLogger@16c90e82
class org.apache.commons.logging.impl.Log4JLogger
10:35:00.454 [0.1][TRACE] core.env.PropertySourcesPropertyResolver 82: Searching for key 'spring.liveBeansView.mbeanDomain' in PropertySource 'servletConfigInitParams'
10:35:00.454 [0.1][TRACE] core.env.PropertySourcesPropertyResolver 82: Searching for key 'spring.liveBeansView.mbeanDomain' in PropertySource 'servletContextInitParams'
10:35:00.454 [0.1][TRACE] core.env.PropertySourcesPropertyResolver 82: Searching for key 'spring.liveBeansView.mbeanDomain' in PropertySource 'jndiProperties'
10:35:00.454 [0.1][DEBUG] springframework.jndi.JndiTemplate 155: Looking up JNDI object with name [java:comp/env/spring.liveBeansView.mbeanDomain]
10:35:00.455 [0.1][DEBUG] springframework.jndi.JndiLocatorSupport 102: Converted JNDI name [java:comp/env/spring.liveBeansView.mbeanDomain] not found - trying original name [spring.liveBeansView.mbeanDomain]. javax.naming.NameNotFoundException: Name [spring.liveBeansView.mbeanDomain] is not bound in this Context. Unable to find [spring.liveBeansView.mbeanDomain].
10:35:00.458 [0.1][DEBUG] springframework.jndi.JndiTemplate 155: Looking up JNDI object with name [spring.liveBeansView.mbeanDomain]
10:35:00.459 [0.1][DEBUG] springframework.jndi.JndiPropertySource 101: JNDI lookup for name [spring.liveBeansView.mbeanDomain] threw NamingException with message: Name [spring.liveBeansView.mbeanDomain] is not bound in this Context. Unable to find [spring.liveBeansView.mbeanDomain].. Returning null.
10:35:00.460 [0.1][TRACE] core.env.PropertySourcesPropertyResolver 82: Searching for key 'spring.liveBeansView.mbeanDomain' in PropertySource 'systemProperties'
10:35:00.461 [0.1][TRACE] core.env.PropertySourcesPropertyResolver 82: Searching for key 'spring.liveBeansView.mbeanDomain' in PropertySource 'systemEnvironment'
10:35:00.464 [0.1][TRACE] core.env.PropertySourcesPropertyResolver 96: Could not find key 'spring.liveBeansView.mbeanDomain' in any property source
10:35:00.464 [0.1][DEBUG] web.servlet.FrameworkServlet 542: enableLoggingRequestDetails='false': request parameters and headers will be masked to prevent unsafe logging of potentially sensitive data
10:35:00.464 [0.1][INFO] web.servlet.FrameworkServlet 547: Completed initialization in 138 ms
[2020-05-07 10:35:00,522] Artifact spring-web-demo:war exploded: Artifact is deployed successfully
[2020-05-07 10:35:00,523] Artifact spring-web-demo:war exploded: Deploy took 4,500 milliseconds

3、RequestMappingHandlerAdapter

先上日志

# 省略其他日志
16:36:35.583 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 1841: Invoking afterPropertiesSet() on bean with name 'org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter'
16:36:35.590 [0.1][DEBUG] method.annotation.RequestMappingHandlerAdapter 612: ControllerAdvice beans: none
16:36:35.674 [0.1][TRACE] factory.support.AbstractAutowireCapableBeanFactory 519: Finished creating instance of bean 'org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter'

再上代码

// 来自类 RequestMappingHandlerAdapter
@Override
public void afterPropertiesSet() {
    // Do this first, it may add ResponseBody advice beans
    initControllerAdviceCache();

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

// 主要用于处理@ControllerAdvice
private void initControllerAdviceCache() {
    if (getApplicationContext() == null) {
        return;
    }

    List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());

    List<Object> requestResponseBodyAdviceBeans = new ArrayList<>();

    for (ControllerAdviceBean adviceBean : adviceBeans) {
        Class<?> beanType = adviceBean.getBeanType();
        if (beanType == null) {
            throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
        }
        Set<Method> attrMethods = MethodIntrospector.selectMethods(beanType, MODEL_ATTRIBUTE_METHODS);
        if (!attrMethods.isEmpty()) {
            this.modelAttributeAdviceCache.put(adviceBean, attrMethods);
        }
        Set<Method> binderMethods = MethodIntrospector.selectMethods(beanType, INIT_BINDER_METHODS);
        if (!binderMethods.isEmpty()) {
            this.initBinderAdviceCache.put(adviceBean, binderMethods);
        }
        if (RequestBodyAdvice.class.isAssignableFrom(beanType) || ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
            requestResponseBodyAdviceBeans.add(adviceBean);
        }
    }

    if (!requestResponseBodyAdviceBeans.isEmpty()) {
        this.requestResponseBodyAdvice.addAll(0, requestResponseBodyAdviceBeans);
    }

    if (logger.isDebugEnabled()) {
        int modelSize = this.modelAttributeAdviceCache.size();
        int binderSize = this.initBinderAdviceCache.size();
        int reqCount = getBodyAdviceCount(RequestBodyAdvice.class);
        int resCount = getBodyAdviceCount(ResponseBodyAdvice.class);
        if (modelSize == 0 && binderSize == 0 && reqCount == 0 && resCount == 0) {
            logger.debug("ControllerAdvice beans: none");
        }
        else {
            logger.debug("ControllerAdvice beans: " + modelSize + " @ModelAttribute, " + binderSize +
                    " @InitBinder, " + reqCount + " RequestBodyAdvice, " + resCount + " ResponseBodyAdvice");
        }
    }
}

4、其他bean的初始化

剩下bean的初始化没什么好提的了,就先跳过吧,我们下一节着重看下Web中请求的流转。

六、dispatcher-servlet启动流程概览

1、前情回顾

# ServletContextListener监听器成功加载了application.xml后,成功初始化了RootServletContext
# 在ServletContext成功初始化之后,开始了DispacherServlet的初始化,而入口就是DispatchServlet中的init方法中initServletBean代码
# Servelt容器开始再初始化Servlet
16:51:40.525 [0.1][INFO] web.context.ContextLoader 307: Root WebApplicationContext initialized in 79116 ms
16:51:57.683 [0.1][INFO] web.servlet.FrameworkServlet 525: Initializing Servlet 'dispatcher'

关键代码如下

/**
 * Map config parameters onto bean properties of this servlet, and
 * invoke subclass initialization.
 * @throws ServletException if bean properties are invalid (or required
 * properties are missing), or if subclass initialization fails.
 */
// 来自类 HttpServletBean
@Override
public final void init() throws ServletException {

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

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

// 省略其他
public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware {
}


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

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

    if (logger.isDebugEnabled()) {
        String value = this.enableLoggingRequestDetails ?
                "shown which may lead to unsafe logging of potentially sensitive data" :
                "masked to prevent unsafe logging of potentially sensitive data";
        logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +
                "': request parameters and headers will be " + value);
    }

    if (logger.isInfoEnabled()) {
        logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
    }
}

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

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

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

    if (this.publishContext) {
        // Publish the context as a servlet context attribute.
        String attrName = getServletContextAttributeName();
        getServletContext().setAttribute(attrName, wac);
    }

    return wac;
}

// 省略其他
public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
}
// 省略其他
public class DispatcherServlet extends FrameworkServlet {
}

中间有一些其他的流程,但大致还是回到了wac.refresh方法。

2、wac.refresh

主要是加载dispatch-servlet.xm中的bean。

3、onFresh

还记得这个方法吗,这里也只是简单提一下。由于之前已经内置许多web组件,所以这里的默认组件可能并不会被启用。

当这一个方法执行完后,也就以为着Spring Web环境已经启动的差不多了。

我们下一节的关注点是一个Web请求到底在Spring MVC中经历了什么。

七、processRequest

还是回到类DispatcherServlet来,前文我们比较详细的分析了Spring MVC中RootApplicationContext、ChildApplicationContext的启动流程,,这次我们详细看下Spring MVC对请求的处理流程。在DispatcherServlet上寻找我们熟悉的doGet、doPost、service方法(不是init方法了),如下

// 三个方法均来自DispatcherServlet的父类FrameworkServlet
/**
 * Delegate GET requests to processRequest/doService.
 * <p>Will also be invoked by HttpServlet's default implementation of {@code doHead},
 * with a {@code NoBodyResponse} that just captures the content length.
 * @see #doService
 * @see #doHead
 */
@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
    processRequest(request, response);
}

/**
 * Delegate POST requests to {@link #processRequest}.
 * @see #doService
 */
@Override
protected final void doPost(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
    processRequest(request, response);
}

/**
 * Process this request, publishing an event regardless of the outcome.
 * <p>The actual event handling is performed by the abstract
 * {@link #doService} template method.
 */
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
	// 记录请求处理耗时
    long startTime = System.currentTimeMillis();
    Throwable failureCause = null;
	// 一般为null
    LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
    // 整合request中的Locale
    LocaleContext localeContext = buildLocaleContext(request);
	// 默认为null
    RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
    // 简单的将request和response放进类 ServletRequestAttributes中
    ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

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

    initContextHolders(request, localeContext, requestAttributes);

    try {
        // 真正的逻辑处理doService留给了子类DispatcherServlet
        doService(request, response);
    }
    catch (ServletException | IOException ex) {
        failureCause = ex;
        throw ex;
    }
    catch (Throwable ex) {
        failureCause = ex;
        throw new NestedServletException("Request processing failed", ex);
    }

    finally {
        resetContextHolders(request, previousLocaleContext, previousAttributes);
        if (requestAttributes != null) {
            requestAttributes.requestCompleted();
        }
        logResult(request, response, failureCause, asyncManager);
        publishRequestHandledEvent(request, response, startTime, failureCause);
    }
}

方法中已经开始了对请求的处理,虽然把细节转移到了doService方法中实现,但是我们不难看出来处理请求前后所做的不少准备与结尾工作,如下

  • 为了保证当前线程的LocaleContext以及RequestAttributes可以在当前请求后还能恢复,提取当前线程的两个属性。
  • 根据当前request创建对应的LocaleContext和RequestAttributes,并绑定到当前线程。
  • 委托给doService方法进一步处理。
  • 请求处理结束后恢复线程到原始状态。
  • 请求处理结束后无论成功与否发布时间通知。

1、对Locale的处理

// 一般为null
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
// 整合request中的Locale
// 这里的Locale代指浏览器环境/用户OS环境,后台可以根据这个可以进行国际化定制
LocaleContext localeContext = buildLocaleContext(request);
// 一般为null
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
// 简单的将request和response放进类 ServletRequestAttributes中
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

// 子类DispatchServlet重写了父类FrameServlet的buildLocaleContext方法
// 注意,虽然上面3个方法是调用父类的,但实际请求发送给的是DispatchServlet,而并非FrameServlet
/**
 * Build a LocaleContext for the given request, exposing the request's primary locale as current locale.
 * <p>The default implementation uses the dispatcher's LocaleResolver to obtain the current locale,
 * which might change during a request.
 * @param request current HTTP request
 * @return the corresponding LocaleContext
 */
@Override
protected LocaleContext buildLocaleContext(final HttpServletRequest request) {
    // 这就是上节我们提到过的 LocaleResolver,默认为AcceptHeaderLocaleResolver
    LocaleResolver lr = this.localeResolver;
    if (lr instanceof LocaleContextResolver) {
        return ((LocaleContextResolver) lr).resolveLocaleContext(request);
    }
    // 走到这里
    else {
        // lambda真tm是个sb,就是进不去,cnmb
        // 个人不是很喜欢这种语法
        return () -> (lr != null ? lr.resolveLocale(request) : request.getLocale());
    }
}

// 进到这里来了
@Override
public Locale resolveLocale(HttpServletRequest request) {
    // 默认为null,可以自定义
    Locale defaultLocale = getDefaultLocale();
    // 如果默认Locale不为null且浏览器端没有指定语言环境
    if (defaultLocale != null && request.getHeader("Accept-Language") == null) {
        return defaultLocale;
    }
    // 进一步进tomcat走了一万个方法
    // 最后得出requestLocale = zh_CN
    Locale requestLocale = request.getLocale();
    // 默认为空
    List<Locale> supportedLocales = getSupportedLocales();
    if (supportedLocales.isEmpty() || supportedLocales.contains(requestLocale)) {
        return requestLocale;
    }
    Locale supportedLocale = findSupportedLocale(request, supportedLocales);
    if (supportedLocale != null) {
        return supportedLocale;
    }
    return (defaultLocale != null ? defaultLocale : requestLocale);
}

LocaleResolver默认实现为

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

2、WebAsyncManager

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

// 简单的注册属性,暂时还没发现有什么用途
/**
 * Obtain the {@link WebAsyncManager} for the current request, or if not
 * found, create and associate it with the request.
 */
public static WebAsyncManager getAsyncManager(ServletRequest servletRequest) {
    WebAsyncManager asyncManager = null;
    Object asyncManagerAttr = servletRequest.getAttribute(WEB_ASYNC_MANAGER_ATTRIBUTE);
    if (asyncManagerAttr instanceof WebAsyncManager) {
        asyncManager = (WebAsyncManager) asyncManagerAttr;
    }
    if (asyncManager == null) {
        asyncManager = new WebAsyncManager();
        servletRequest.setAttribute(WEB_ASYNC_MANAGER_ATTRIBUTE, asyncManager);
    }
    return asyncManager;
}

// 暂时还没发现有什么用
/**
 * CallableProcessingInterceptor implementation that initializes and resets
 * FrameworkServlet's context holders, i.e. LocaleContextHolder and RequestContextHolder.
 */
private class RequestBindingInterceptor implements CallableProcessingInterceptor {

    @Override
    public <T> void preProcess(NativeWebRequest webRequest, Callable<T> task) {
        HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
        if (request != null) {
            HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
            initContextHolders(request, buildLocaleContext(request),
                    buildRequestAttributes(request, response, null));
        }
    }
    @Override
    public <T> void postProcess(NativeWebRequest webRequest, Callable<T> task, Object concurrentResult) {
        HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
        if (request != null) {
            resetContextHolders(request, null, null);
        }
    }
}

3、initContextHolders

private void initContextHolders(HttpServletRequest request,
        @Nullable LocaleContext localeContext, @Nullable RequestAttributes requestAttributes) {

    if (localeContext != null) {
        LocaleContextHolder.setLocaleContext(localeContext, this.threadContextInheritable);
    }
    if (requestAttributes != null) {
        RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);
    }
}

这个地方初看可能会觉得很奇怪,可仔细一跟源码就会豁然开朗,如下

// RequestContextHolder逻辑一模一样,这里就不列了
public final class LocaleContextHolder {

	private static final ThreadLocal<LocaleContext> localeContextHolder =
			new NamedThreadLocal<>("LocaleContext");

	private static final ThreadLocal<LocaleContext> inheritableLocaleContextHolder =
			new NamedInheritableThreadLocal<>("LocaleContext");
	// 省略其他
}

/**
 * Associate the given LocaleContext with the current thread.
 * <p>The given LocaleContext may be a {@link TimeZoneAwareLocaleContext},
 * containing a locale with associated time zone information.
 * @param localeContext the current LocaleContext,
 * or {@code null} to reset the thread-bound context
 * @param inheritable whether to expose the LocaleContext as inheritable
 * for child threads (using an {@link InheritableThreadLocal})
 * @see SimpleLocaleContext
 * @see SimpleTimeZoneAwareLocaleContext
 */
public static void setLocaleContext(@Nullable LocaleContext localeContext, boolean inheritable) {
    if (localeContext == null) {
        resetLocaleContext();
    }
    else {
        if (inheritable) {
            inheritableLocaleContextHolder.set(localeContext);
            localeContextHolder.remove();
        }
        else {
            localeContextHolder.set(localeContext);
            inheritableLocaleContextHolder.remove();
        }
    }
}

于是我们可以在单个请求中的任何地方调用如下代码以后去request和response,形如

@RequestMapping("/testStr")
@ResponseBody
public String testStr() {
    HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
    HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getResponse();
    return request + "  " + response;
}

八、doService

接下来到了doService方法,如下

// 来自FrameworkServlet子类DispatcherServlet
/**
 * Exposes the DispatcherServlet-specific request attributes and delegates to {@link #doDispatch}
 * for the actual dispatching.
 */
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
    // debug一下请求
    logRequest(request);
    // Keep a snapshot of the request attributes in case of an include,
    // to be able to restore the original attributes after the include.
    // 这一段是对jsp中include指令的支持,具体细节这里暂时跳过去
    Map<String, Object> attributesSnapshot = null;
    if (WebUtils.isIncludeRequest(request)) {
        attributesSnapshot = new HashMap<>();
        Enumeration<?> attrNames = request.getAttributeNames();
        while (attrNames.hasMoreElements()) {
            String attrName = (String) attrNames.nextElement();
            if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
                attributesSnapshot.put(attrName, request.getAttribute(attrName));
            }
        }
    }

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

    // 对FlashMap的处理,解决redirect中传递参数失效的问题
    // forward不存在参数丢失的问题
    if (this.flashMapManager != null) {
        FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
        if (inputFlashMap != null) {
            request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
        }
        request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
        request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
    }

    try {
        // 这里才是真正的逻辑调用
        doDispatch(request, response);
    }
    finally {
        if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
            // Restore the original attribute snapshot, in case of an include.
            if (attributesSnapshot != null) {
                restoreAttributesAfterInclude(request, attributesSnapshot);
            }
        }
    }
}

doService方法中并没有过多的逻辑,除了注入request一些框架组件外(方便后面的处理),还有就是对FlashMap的处理了。如下是一个简单示例

// 目前只发现这一种实现方式,经过多番实践,这个东西并没有什么卵用,建议忘掉
@RequestMapping("/redirect2")
public String redirect2(String username, String password, RedirectAttributes redirectAttributes) {
    // 这里传入的参数会出现在重定向后的url
    redirectAttributes.addAttribute("username", username);
    redirectAttributes.addAttribute("password", password);
    return "redirect: /redirectTarget";
}

@RequestMapping("/redirectTarget")
@ResponseBody
public String redirectTarget(String username, String password, HttpServletRequest request) {
    return "username=" + username + ",password=" + password;
}

1、doDispatch

doService将真正逻辑处理委托给了doDispatch方法,如下

/**
 * Process the actual dispatching to the handler.
 * <p>The handler will be obtained by applying the servlet's HandlerMappings in order.
 * The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters
 * to find the first that supports the handler class.
 * <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers
 * themselves to decide which methods are acceptable.
 * @param request current HTTP request
 * @param response current HTTP response
 * @throws Exception in case of any kind of processing failure
 */
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HttpServletRequest processedRequest = request;
    HandlerExecutionChain mappedHandler = null;
    boolean multipartRequestParsed = false;

    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

    try {
        ModelAndView mv = null;
        Exception dispatchException = null;

        try {
            // 如果是MultiparContent类型的request类型则转换request为MultipartHttpServletRequest类型的request
            processedRequest = checkMultipart(request);
            multipartRequestParsed = (processedRequest != request);

            // Determine handler for the current request.
            // 根据request信息寻找对应的Handler
            mappedHandler = getHandler(processedRequest);
            if (mappedHandler == null) {
                // 如果没有找到对应的handler则通过response反馈错误信息
                noHandlerFound(processedRequest, response);
                return;
            }

            // Determine handler adapter for the current request.
            // 根据当前handler寻找对应的HandlerAdapter
            HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

            // Process last-modified header, if supported by the handler.
            String method = request.getMethod();
            boolean isGet = "GET".equals(method);
            if (isGet || "HEAD".equals(method)) {
                long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                    return;
                }
            }

            // 拦截器的preHandler方法的调用
            if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                return;
            }

            // Actually invoke the handler.
            // 真正的激活handler并返回视图
            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

            if (asyncManager.isConcurrentHandlingStarted()) {
                return;
            }
			// 视图名称转换应用于需要添加前缀后缀的情况
            applyDefaultViewName(processedRequest, mv);
            // 应用所有拦截器的postHander方法
            mappedHandler.applyPostHandle(processedRequest, response, mv);
        }
        catch (Exception ex) {
            dispatchException = ex;
        }
        catch (Throwable err) {
            // As of 4.3, we're processing Errors thrown from handler methods as well,
            // making them available for @ExceptionHandler methods and other scenarios.
            dispatchException = new NestedServletException("Handler dispatch failed", err);
        }
        // 处理请求返回的处理结果
        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    }
    catch (Exception ex) {
        triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
    }
    catch (Throwable err) {
        triggerAfterCompletion(processedRequest, response, mappedHandler,
                new NestedServletException("Handler processing failed", err));
    }
    finally {
        if (asyncManager.isConcurrentHandlingStarted()) {
            // Instead of postHandle and afterCompletion
            if (mappedHandler != null) {
                mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
            }
        }
        else {
            // Clean up any resources used by a multipart request.
            if (multipartRequestParsed) {
                cleanupMultipart(processedRequest);
            }
        }
    }
}

doDispatch方法中展示了Spring请求处理所涉及的主要逻辑,而我们之前设置在request中的各种辅助属性也都有被派上了用场。下面回顾以下逻辑处理的全过程。

2、checkMultipart

对于请求的处理,Spring首先考虑的是对于Multipart的处理,如果是MultipartContent类型的request,则转换request为MultipartHttpServletRequest类型的request。

processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);

/**
 * Convert the request into a multipart request, and make multipart resolver available.
 * <p>If no multipart resolver is set, simply use the existing request.
 * @param request current HTTP request
 * @return the processed request (multipart wrapper if necessary)
 * @see MultipartResolver#resolveMultipart
 */
protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
    // 如果指定了MultipartResolver
    if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
        if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {
            if (request.getDispatcherType().equals(DispatcherType.REQUEST)) {
                logger.trace("Request already resolved to MultipartHttpServletRequest, e.g. by MultipartFilter");
            }
        }
        else if (hasMultipartException(request)) {
            logger.debug("Multipart resolution previously failed for current request - " +
                    "skipping re-resolution for undisturbed error rendering");
        }
        else {
            try {
                return this.multipartResolver.resolveMultipart(request);
            }
            catch (MultipartException ex) {
                if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) != null) {
                    logger.debug("Multipart resolution failed for error dispatch", ex);
                    // Keep processing error dispatch with regular request handle below
                }
                else {
                    throw ex;
                }
            }
        }
    }
    // If not returned before: return original request.
    return request;
}

// 来源于类 StandardServletMultipartResolver
// 一般是根据multipart/form-data
@Override
public boolean isMultipart(HttpServletRequest request) {
    // Same check as in Commons FileUpload...
    if (!"post".equalsIgnoreCase(request.getMethod())) {
        return false;
    }
    String contentType = request.getContentType();
    return StringUtils.startsWithIgnoreCase(contentType, "multipart/");
}

@Override
public MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException {
    return new StandardMultipartHttpServletRequest(request, this.resolveLazily);
}

// 后面就是对http协议中的文件转换了
/**
 * Create a new StandardMultipartHttpServletRequest wrapper for the given request.
 * @param request the servlet request to wrap
 * @param lazyParsing whether multipart parsing should be triggered lazily on
 * first access of multipart files or parameters
 * @throws MultipartException if an immediate parsing attempt failed
 * @since 3.2.9
 */
public StandardMultipartHttpServletRequest(HttpServletRequest request, boolean lazyParsing)
        throws MultipartException {

    super(request);
    if (!lazyParsing) {
        parseRequest(request);
    }
}

3、getHandler

// Determine handler for the current request.
// 根据request信息寻找对应的Handler
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
    // 如果没有找到对应的handler则通过response反馈错误信息
    noHandlerFound(processedRequest, response);
    return;
}

/**
 * Return the HandlerExecutionChain for this request.
 * <p>Tries all handler mappings in order.
 * @param request current HTTP request
 * @return the HandlerExecutionChain, or {@code null} if no handler could be found
 */
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    // 注意这里的handlerMappings并不等于3,即并不是默认策略中的3个
    // 原因是加载配置文件时,自定义标签mvc已经配置好了需要的bean
    // 为RequestMappingHandlerMapping、BeanNameUrlHandlerMapping
    if (this.handlerMappings != null) {
        for (HandlerMapping mapping : this.handlerMappings) {
            HandlerExecutionChain handler = mapping.getHandler(request);
            // 如果找对对应的handler则直接返回,默认优先查RequestMappingHandlerMapping
            if (handler != null) {
                return handler;
            }
        }
    }
    return null;
}

在之前的内容我们提过,在系统启动时Spring会将所有的映射类型的bean注册到this.handlerMappings变量中,所以此方法的目的就是遍历所有的HanderMapping,并调用getHandler方法进行封装处理。

对于BeanNameUrlHandlerMapping的使用需要该beanName以/开头(用于识别),且继承AbstractController(用于使用),关键代码如下

// BeanNameUrlHandlerMapping的上层父类 AbstractHandlerMapping 实现了ApplicationContextAware和ServletContextAware
// 因此在setApplicationContext中会调用initApplicationContext方法,会自动扫描以/开头的bean
public class BeanNameUrlHandlerMapping extends AbstractDetectingUrlHandlerMapping {

	/**
	 * Checks name and aliases of the given bean for URLs, starting with "/".
	 */
	@Override
	protected String[] determineUrlsForHandler(String beanName) {
		List<String> urls = new ArrayList<>();
		if (beanName.startsWith("/")) {
			urls.add(beanName);
		}
		String[] aliases = obtainApplicationContext().getAliases(beanName);
		for (String alias : aliases) {
			if (alias.startsWith("/")) {
				urls.add(alias);
			}
		}
		return StringUtils.toStringArray(urls);
	}
}

以RequestMappingHandlerMapping为例查看其getHandler方法。

// getHandler方法还是由父类AbstractHandlerMapping提供
/**
 * Look up a handler for the given request, falling back to the default
 * handler if no specific one is found.
 * @param request current HTTP request
 * @return the corresponding handler instance, or the default handler
 * @see #getHandlerInternal
 */
@Override
@Nullable
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 = obtainApplicationContext().getBean(handlerName);
    }

    HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);

    if (logger.isTraceEnabled()) {
        logger.trace("Mapped to " + handler);
    }
    else if (logger.isDebugEnabled() && !request.getDispatcherType().equals(DispatcherType.ASYNC)) {
        logger.debug("Mapped to " + executionChain.getHandler());
    }

    if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
        CorsConfiguration config = (this.corsConfigurationSource != null ? this.corsConfigurationSource.getCorsConfiguration(request) : null);
        CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
        config = (config != null ? config.combine(handlerConfig) : handlerConfig);
        executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
    }

    return executionChain;
}

// 类RequestMappingHandlerMapping重写了父类方法
@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
    try {
        // 又调回了父类
        return super.getHandlerInternal(request);
    }
    finally {
        ProducesRequestCondition.clearMediaTypesAttribute(request);
    }
}

1、getHandlerInternal

/**
 * Look up a handler method for the given request.
 */
@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
    // 截取用于匹配的url有效路径,如请求是${app}/test_url/a,那么这里就是test_url/a
    String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
    request.setAttribute(LOOKUP_PATH, lookupPath);
    // 获取内部读写锁ReentrantReadWriteLock的读锁
    // 内部缓存了Controller处理方法与url对映射关系
    this.mappingRegistry.acquireReadLock();
    try {
        // 根据路径寻找Handler
        HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
        return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
    }
    finally {
        // 释放读锁
        this.mappingRegistry.releaseReadLock();
    }
}

/**
 * Return the mapping lookup path for the given request, within the current
 * servlet mapping if applicable, else within the web application.
 * <p>Detects include request URL if called within a RequestDispatcher include.
 * @param request current HTTP request
 * @return the lookup path
 * @see #getPathWithinServletMapping
 * @see #getPathWithinApplication
 */
public String getLookupPathForRequest(HttpServletRequest request) {
    // Always use full path within current servlet context?
    if (this.alwaysUseFullPath) {
        return getPathWithinApplication(request);
    }
    // Else, use path within current servlet mapping if applicable
    // 优先匹配Servlet
    String rest = getPathWithinServletMapping(request);
    if (!"".equals(rest)) {
        return rest;
    }
    else {
        // 再匹配DispatchServlet内部
        return getPathWithinApplication(request);
    }
}

/**
 * Look up the best-matching handler method for the current request.
 * If multiple matches are found, the best match is selected.
 * @param lookupPath mapping lookup path within the current servlet mapping
 * @param request the current request
 * @return the best-matching handler method, or {@code null} if no match
 * @see #handleMatch(Object, String, HttpServletRequest)
 * @see #handleNoMatch(Set, String, HttpServletRequest)
 */
@Nullable
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
    List<Match> matches = new ArrayList<>();
    // 先查找缓存,自定义标签mvc会事先解析所有请求并存进这里,所以这里一般不为空
    List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
    if (directPathMatches != null) {
        addMatchingMappings(directPathMatches, matches, request);
    }
    if (matches.isEmpty()) {
        // No choice but to go through all mappings...
        addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
    }

    if (!matches.isEmpty()) {
        Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
        matches.sort(comparator);
        Match bestMatch = matches.get(0);
        if (matches.size() > 1) {
            if (logger.isTraceEnabled()) {
                logger.trace(matches.size() + " matching mappings: " + matches);
            }
            if (CorsUtils.isPreFlightRequest(request)) {
                return PREFLIGHT_AMBIGUOUS_MATCH;
            }
            Match secondBestMatch = matches.get(1);
            if (comparator.compare(bestMatch, secondBestMatch) == 0) {
                Method m1 = bestMatch.handlerMethod.getMethod();
                Method m2 = secondBestMatch.handlerMethod.getMethod();
                String uri = request.getRequestURI();
                throw new IllegalStateException(
                        "Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
            }
        }
        request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
        handleMatch(bestMatch.mapping, lookupPath, request);
        return bestMatch.handlerMethod;
    }
    else {
        return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
    }
}

2、getHandlerExecutionChain

在寻找到Handler后,接着将配置中的对应的拦截器加入到执行链中,以保证这些拦截器可以有效地作用于目标对象。

/**
 * Build a {@link HandlerExecutionChain} for the given handler, including
 * applicable interceptors.
 * <p>The default implementation builds a standard {@link HandlerExecutionChain}
 * with the given handler, the handler mapping's common interceptors, and any
 * {@link MappedInterceptor MappedInterceptors} matching to the current request URL. Interceptors
 * are added in the order they were registered. Subclasses may override this
 * in order to extend/rearrange the list of interceptors.
 * <p><b>NOTE:</b> The passed-in handler object may be a raw handler or a
 * pre-built {@link HandlerExecutionChain}. This method should handle those
 * two cases explicitly, either building a new {@link HandlerExecutionChain}
 * or extending the existing chain.
 * <p>For simply adding an interceptor in a custom subclass, consider calling
 * {@code super.getHandlerExecutionChain(handler, request)} and invoking
 * {@link HandlerExecutionChain#addInterceptor} on the returned chain object.
 * @param handler the resolved handler instance (never {@code null})
 * @param request current HTTP request
 * @return the HandlerExecutionChain (never {@code null})
 * @see #getAdaptedInterceptors()
 */
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
    HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
            (HandlerExecutionChain) handler : new HandlerExecutionChain(handler));

    String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, LOOKUP_PATH);
    for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
        if (interceptor instanceof MappedInterceptor) {
            MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
            if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
                chain.addInterceptor(mappedInterceptor.getInterceptor());
            }
        }
        else {
            chain.addInterceptor(interceptor);
        }
    }
    return chain;
}

4、noHandlerFound

每个请求都应该对应着一个Hander,因为每个请求都会在后台有相应的逻辑对应,而逻辑的实现就是在Handler中,所以一旦遇到没有找到相应Handler的情况(包含默认),就只能通过response向用户返回错误信息。

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

5、getHandlerAdapter

在WebApplicationContext的初始化过程中我们讨论了HanderAdapters的初始化,了解了在默认情况下普通的Web请求会交给SimpleControllerHandlerAdapter去处理。下面我们以SimpleControllerHandlerAdapter为例来分析获取适配器的逻辑。

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

/**
 * Return the HandlerAdapter for this handler object.
 * @param handler the handler object to find an adapter for
 * @throws ServletException if no HandlerAdapter can be found for the handler. This is a fatal error.
 */
// 这一步只是寻找,判断的规则也很简单,如下
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
    // 这里默认会有三个,分别是RequestMappingHandlerAdapter、HttpRequestHandlerAdapter、SimpleControllerHandlerAdapter
    if (this.handlerAdapters != null) {
        for (HandlerAdapter adapter : this.handlerAdapters) {
            if (adapter.supports(handler)) {
                return adapter;
            }
        }
    }
    throw new ServletException("No adapter for handler [" + handler +
            "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}

// 来源于类 RequestMappingHandlerAdapter,一般用于处理@RequestMapping注解,与@Controller配合使用
/**
 * This implementation expects the handler to be an {@link HandlerMethod}.
 * @param handler the handler instance to check
 * @return whether or not this adapter can adapt the given handler
 */
@Override
public final boolean supports(Object handler) {
    return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
}

// 来源于类 HttpRequestHandlerAdapter
@Override
public boolean supports(Object handler) {
    return (handler instanceof HttpRequestHandler);
}

// 来源于类 SimpleControllerHandlerAdapter,多用于类直接继承子AbstractController
@Override
public boolean supports(Object handler) {
    return (handler instanceof Controller);
}

更为复杂的逻辑是在后面实现的。

6、LastModified

在研究Spring对缓存处理的功能支持前,我们先了解一个概念:Last-Modified缓存机制。

  • 在客户端第一次输入URL时,服务端会返回内容和状态吗200,表示请求成功,同时会添加一个“Last-Modified”的响应头,表示此文件在服务器上的最后更新时间,例如“Last-Modified:Wed, 14 Mar 2020 20:26:22 GMT”。
  • 客户端第二次请求此URL时,客户端会向服务器发送请求头"If-Modified-Since",询问服务器该时间之后当前请求内容是否有被修改过,如果服务器端的内容没有变化,则自动返回HTTP 304状态码(只要响应头,内容为空,这样就节省了网络带宽)。

Spring提供了对Last-Modified机制的支持,只需要实现LastModified接口,如下所示

// 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 (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
        return;
    }
}

@Component("/last")
public class LastModifiedCacheController extends AbstractController implements LastModified {

    private long lastModifiedTime;

    private static final DateTimeFormatter DTF_DATE_TIME = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");


    @Override
    protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
        String str = parseLongTime(lastModifiedTime);
        response.setCharacterEncoding("utf-8");
        response.getWriter().write("<h2>当前时间:" + str + "<h2>");
        return null;
    }

    @Override
    public long getLastModified(HttpServletRequest request) {
        if (lastModifiedTime == 0) {
            lastModifiedTime = System.currentTimeMillis();
        }
        // 这一行决定了是否总是返回最新值,注释掉则每次都返回第一次访问的时间,不注释则每次返回最新时间
        // lastModifiedTime = System.currentTimeMillis();
        return lastModifiedTime;
    }

    public static String parseLongTime(long longTime) {
        Instant instant = Instant.ofEpochMilli(longTime);
        LocalDateTime nowTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
        return DTF_DATE_TIME.format(nowTime);
    }
}

Spring判断是否过期,通过判断请求的“If-Modified-Since”是否大于等于当前的getLastModified方法的时间戳,如果是,则认为没有修改。否则认为修改了返回最新值。

7、HandlerInterceptor的处理

Servlet API定义的servlet过滤器可以在servlet处理每个Web请求的前后分别对它进行前置处理和后置处理。此外,有些时候,你可能只想处理由某些Spring MVC组件处理的Web请求,并在这些处理程序返回的模型属性被传递到视图之前,对他们进行一些操作。

// 前置拦截
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
    return;
}
// Actually invoke the handler.
// 真正调用
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

if (asyncManager.isConcurrentHandlingStarted()) {
    return;
}
applyDefaultViewName(processedRequest, mv);
// 后置处理
mappedHandler.applyPostHandle(processedRequest, response, mv);


public class TestInterceptor implements HandlerInterceptor {

    private Logger log = LogManager.getLogger(this.getClass());

    /**
     * 如果请求放行(调用Controller之前),则返回true,否则返回false
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.debug("放行请求:" + request.getRequestURI());
        sleep(100);
        return true;
    }

    /**
     * 请求已被处理完(调用Controller之后,返回逻辑视图之前),调用此方法
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        log.debug("执行请求完,返回" + response.getStatus());
        sleep(100);
    }

    /**
     * 在返回逻辑试图之后(通常用来释放资源),调用此方法
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        log.debug("关闭连接资源");
        sleep(100);
    }
}

1、applyPreHandle

/**
 * Apply preHandle methods of registered interceptors.
 * @return {@code true} if the execution chain should proceed with the
 * next interceptor or the handler itself. Else, DispatcherServlet assumes
 * that this interceptor has already dealt with the response itself.
 */
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HandlerInterceptor[] interceptors = getInterceptors();
    if (!ObjectUtils.isEmpty(interceptors)) {
        // Spring会帮我们内置两个HandlerInterceptor,用于实现Web的特定拦截,只有返回true,则会放行请求
        // 第一个是CorsInterceptor,用于拦截不允许跨域的请求
        // 第二个是ConversionServiceExposingInterceptor,主要用于支持Jsp标签
        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;
}

2、applyPostHandle

/**
 * Apply postHandle methods of registered interceptors.
 */
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
        throws Exception {

    HandlerInterceptor[] interceptors = getInterceptors();
    if (!ObjectUtils.isEmpty(interceptors)) {
        // 还是前面两个,需要主要的是,后处理的执行顺序是倒过来的,这个与Spring Cloud Gateway很像
        // 即前面是123经过拦截器,后面是321流出拦截器
        for (int i = interceptors.length - 1; i >= 0; i--) {
            HandlerInterceptor interceptor = interceptors[i];
            interceptor.postHandle(request, response, this.handler, mv);
        }
    }
}

8、ha.handle**

handler方法算是Spring MVC中的重中之重,主要分为两个部分,在找到目标方法后,如何调用方法?又怎么处理方法的返回值?这里为了具体化,我简单列几个比较关注的点吧,如下

  • 调用方法中参数是如何确定,包括个数、类型?
  • @RequestParam、@ResponseBody、@PathVariable等注解如何生效?
  • 如何处理返回的非String类型对象?
  • 自动注入的是如何进行的?
// Actually invoke the handler.
// 如果是视图页面,则mv不为null,反之则为null
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());


public class ModelAndView {

	/** View instance or view name String. */
	@Nullable
	private Object view;

	/** Model Map. */
	@Nullable
	private ModelMap model;

	/** Optional HTTP status for the response. */
	@Nullable
	private HttpStatus status;

	/** Indicates whether or not this instance has been cleared with a call to {@link #clear()}. */
	private boolean cleared = false;
    
 	// 省略其他get/set方法
}

public interface View {

	/**
	 * Name of the {@link HttpServletRequest} attribute that contains the response status code.
	 * <p>Note: This attribute is not required to be supported by all View implementations.
	 * @since 3.0
	 */
	String RESPONSE_STATUS_ATTRIBUTE = View.class.getName() + ".responseStatus";

	/**
	 * Name of the {@link HttpServletRequest} attribute that contains a Map with path variables.
	 * The map consists of String-based URI template variable names as keys and their corresponding
	 * Object-based values -- extracted from segments of the URL and type converted.
	 * <p>Note: This attribute is not required to be supported by all View implementations.
	 * @since 3.1
	 */
	String PATH_VARIABLES = View.class.getName() + ".pathVariables";

	/**
	 * The {@link org.springframework.http.MediaType} selected during content negotiation,
	 * which may be more specific than the one the View is configured with. For example:
	 * "application/vnd.example-v1+xml" vs "application/*+xml".
	 * @since 3.2
	 */
	String SELECTED_CONTENT_TYPE = View.class.getName() + ".selectedContentType";


	/**
	 * Return the content type of the view, if predetermined.
	 * <p>Can be used to check the view's content type upfront,
	 * i.e. before an actual rendering attempt.
	 * @return the content type String (optionally including a character set),
	 * or {@code null} if not predetermined
	 */
	@Nullable
	default String getContentType() {
		return null;
	}

	/**
	 * Render the view given the specified model.
	 * <p>The first step will be preparing the request: In the JSP case, this would mean
	 * setting model objects as request attributes. The second step will be the actual
	 * rendering of the view, for example including the JSP via a RequestDispatcher.
	 * @param model a Map with name Strings as keys and corresponding model
	 * objects as values (Map can also be {@code null} in case of empty model)
	 * @param request current HTTP request
	 * @param response he HTTP response we are building
	 * @throws Exception if rendering failed
	 */
	void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
			throws Exception;
}

/**
 * This implementation expects the handler to be an {@link HandlerMethod}.
 */
// 这里还是以ReqeustMappingHandlerAdapter为例
@Override
@Nullable
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
        throws Exception {

    return handleInternal(request, response, (HandlerMethod) handler);
}

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

    ModelAndView mav;
    checkRequest(request);

    // 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) {
                mav = invokeHandlerMethod(request, response, handlerMethod);
            }
        }
        else {
            // No HttpSession available -> no mutex necessary
            mav = invokeHandlerMethod(request, response, handlerMethod);
        }
    }
    else {
        // No synchronization on session demanded at all...
        // 我们主要看下这里,这一步之后已经确定入参参数以及需要返回值类型
        mav = invokeHandlerMethod(request, response, handlerMethod);
    }

    if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
        if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
            applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
        }
        else {
            prepareResponse(response);
        }
    }

    return mav;
}

/**
 * Invoke the {@link RequestMapping} handler method preparing a {@link ModelAndView}
 * if view resolution is required.
 * @since 4.2
 * @see #createInvocableHandlerMethod(HandlerMethod)
 */
@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
        HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

    ServletWebRequest webRequest = new ServletWebRequest(request, response);
    try {
        WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
        ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);

        ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
        // 这里的参数解析支持有26种,后文简单列下
        if (this.argumentResolvers != null) {
            invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
        }
        // 返回值解析有15种
        if (this.returnValueHandlers != null) {
            invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
        }
        invocableMethod.setDataBinderFactory(binderFactory);
        invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
		// 这个mvcContainer很重要
        ModelAndViewContainer mavContainer = new ModelAndViewContainer();
        mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
        modelFactory.initModel(webRequest, mavContainer, invocableMethod);
        mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
		// 这里主要是一些对于异步的支持
        AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
        asyncWebRequest.setTimeout(this.asyncRequestTimeout);

        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();
            LogFormatUtils.traceDebug(logger, traceOn -> {
                String formatted = LogFormatUtils.formatValue(result, !traceOn);
                return "Resume with async result [" + formatted + "]";
            });
            invocableMethod = invocableMethod.wrapConcurrentResult(result);
        }
		// 我们主要看下这个方法
        invocableMethod.invokeAndHandle(webRequest, mavContainer);
        if (asyncManager.isConcurrentHandlingStarted()) {
            return null;
        }

        return getModelAndView(mavContainer, modelFactory, webRequest);
    }
    finally {
        webRequest.requestCompleted();
    }
}

/**
 * Invoke the method and handle the return value through one of the
 * configured {@link HandlerMethodReturnValueHandler HandlerMethodReturnValueHandlers}.
 * @param webRequest the current request
 * @param mavContainer the ModelAndViewContainer for this request
 * @param providedArgs "given" arguments matched by type (not resolved)
 */
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) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
            disableContentCachingIfNecessary(webRequest);
            mavContainer.setRequestHandled(true);
            return;
        }
    }
    else if (StringUtils.hasText(getResponseStatusReason())) {
        mavContainer.setRequestHandled(true);
        return;
    }

    mavContainer.setRequestHandled(false);
    Assert.state(this.returnValueHandlers != null, "No return value handlers");
    try {
        // 这一步主要是解析返回值类型
        this.returnValueHandlers.handleReturnValue(
                returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
    }
    catch (Exception ex) {
        if (logger.isTraceEnabled()) {
            logger.trace(formatErrorForReturnValue(returnValue), ex);
        }
        throw ex;
    }
}

/**
 * Invoke the method after resolving its argument values in the context of the given request.
 * <p>Argument values are commonly resolved through
 * {@link HandlerMethodArgumentResolver HandlerMethodArgumentResolvers}.
 * The {@code providedArgs} parameter however may supply argument values to be used directly,
 * i.e. without argument resolution. Examples of provided argument values include a
 * {@link WebDataBinder}, a {@link SessionStatus}, or a thrown exception instance.
 * Provided argument values are checked before argument resolvers.
 * <p>Delegates to {@link #getMethodArgumentValues} and calls {@link #doInvoke} with the
 * resolved arguments.
 * @param request the current request
 * @param mavContainer the ModelAndViewContainer for this request
 * @param providedArgs "given" arguments matched by type, not resolved
 * @return the raw value returned by the invoked method
 * @throws Exception raised if no suitable argument resolver can be found,
 * or if the method raised an exception
 * @see #getMethodArgumentValues
 * @see #doInvoke
 */
@Nullable
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
        Object... providedArgs) throws Exception {
	// 这一步过后,Spring MVC已经可以得到了特定位置参数的值,即已经完成了参数的自动注入
    Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
    if (logger.isTraceEnabled()) {
        logger.trace("Arguments: " + Arrays.toString(args));
    }
    // 接下来开始利用参数调用方法,这里返回的是方法结果值
    return doInvoke(args);
}

1、入参处理

这里列下之前提到的26种用于解析参数的类,如下

0 = {RequestParamMethodArgumentResolver@6892} 
1 = {RequestParamMapMethodArgumentResolver@6893} 
2 = {PathVariableMethodArgumentResolver@6894} 
3 = {PathVariableMapMethodArgumentResolver@6895} 
4 = {MatrixVariableMethodArgumentResolver@6896} 
5 = {MatrixVariableMapMethodArgumentResolver@6897} 
6 = {ServletModelAttributeMethodProcessor@6898} 
7 = {RequestResponseBodyMethodProcessor@6899} 
8 = {RequestPartMethodArgumentResolver@6900} 
9 = {RequestHeaderMethodArgumentResolver@6901} 
10 = {RequestHeaderMapMethodArgumentResolver@6902} 
11 = {ServletCookieValueMethodArgumentResolver@6903} 
12 = {ExpressionValueMethodArgumentResolver@6904} 
13 = {SessionAttributeMethodArgumentResolver@6905} 
14 = {RequestAttributeMethodArgumentResolver@6906} 
15 = {ServletRequestMethodArgumentResolver@6907} 
16 = {ServletResponseMethodArgumentResolver@6908} 
17 = {HttpEntityMethodProcessor@6909} 
18 = {RedirectAttributesMethodArgumentResolver@6910} 
19 = {ModelMethodProcessor@6911} 
20 = {MapMethodProcessor@6912} 
21 = {ErrorsMethodArgumentResolver@6913} 
22 = {SessionStatusMethodArgumentResolver@6914} 
23 = {UriComponentsBuilderMethodArgumentResolver@6915} 
24 = {RequestParamMethodArgumentResolver@6916} 
25 = {ServletModelAttributeMethodProcessor@6917} 

大致过一下这些类的定义,会发现Spring默认提供了很多功能,但我们平时常用的就那么一点,这一部分有待研究,暂时跳过,关键代码如下

/**
 * Get the method argument values for the current request, checking the provided
 * argument values and falling back to the configured argument resolvers.
 * <p>The resulting array will be passed into {@link #doInvoke}.
 * @since 5.1.2
 */
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
        Object... providedArgs) throws Exception {

    MethodParameter[] parameters = getMethodParameters();
    if (ObjectUtils.isEmpty(parameters)) {
        return EMPTY_ARGS;
    }

    Object[] args = new Object[parameters.length];
    for (int i = 0; i < parameters.length; i++) {
        MethodParameter parameter = parameters[i];
        parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
        args[i] = findProvidedArgument(parameter, providedArgs);
        if (args[i] != null) {
            continue;
        }

        if (!this.resolvers.supportsParameter(parameter)) {
            throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
        }
        try {
            // 主要在于这一行
            args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
        }
        catch (Exception ex) {
            // Leave stack trace for later, exception may actually be resolved and handled...
            if (logger.isDebugEnabled()) {
                String exMsg = ex.getMessage();
                if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
                    logger.debug(formatArgumentError(parameter, exMsg));
                }
            }
            throw ex;
        }
    }
    return args;
}

/**
 * Iterate over registered
 * {@link HandlerMethodArgumentResolver HandlerMethodArgumentResolvers}
 * and invoke the one that supports it.
 * @throws IllegalArgumentException if no suitable argument resolver is found
 */
@Override
@Nullable
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
        NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

    HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
    if (resolver == null) {
        throw new IllegalArgumentException("Unsupported parameter type [" +
                parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
    }
    return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}

	@Override
	@Nullable
	public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

		NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
		MethodParameter nestedParameter = parameter.nestedIfOptional();

		Object resolvedName = resolveStringValue(namedValueInfo.name);
		if (resolvedName == null) {
			throw new IllegalArgumentException(
					"Specified name must not resolve to null: [" + namedValueInfo.name + "]");
		}

		Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
		if (arg == null) {
			if (namedValueInfo.defaultValue != null) {
				arg = resolveStringValue(namedValueInfo.defaultValue);
			}
			else if (namedValueInfo.required && !nestedParameter.isOptional()) {
				handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
			}
			arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
		}
		else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
			arg = resolveStringValue(namedValueInfo.defaultValue);
		}

		if (binderFactory != null) {
			WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
			try {
				arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
			}
			catch (ConversionNotSupportedException ex) {
				throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(),
						namedValueInfo.name, parameter, ex.getCause());
			}
			catch (TypeMismatchException ex) {
				throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(),
						namedValueInfo.name, parameter, ex.getCause());
			}
		}

		handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);

		return arg;
	}

大致就跟到这里吧,具体的作用就是给予特定参数以特定的资源。

2、返回值处理

这里列下之前提到的15种用于解析方法返回值的类,如下

0 = {ModelAndViewMethodReturnValueHandler@6501} 
1 = {ModelMethodProcessor@6502} 
2 = {ViewMethodReturnValueHandler@6503} 
3 = {ResponseBodyEmitterReturnValueHandler@6504} 
4 = {StreamingResponseBodyReturnValueHandler@6505} 
5 = {HttpEntityMethodProcessor@6506} 
6 = {HttpHeadersReturnValueHandler@6507} 
7 = {CallableMethodReturnValueHandler@6508} 
8 = {DeferredResultMethodReturnValueHandler@6509} 
9 = {AsyncTaskMethodReturnValueHandler@6510} 
10 = {ModelAttributeMethodProcessor@6511} 
11 = {RequestResponseBodyMethodProcessor@6512} 
12 = {ViewNameMethodReturnValueHandler@6513} 
13 = {MapMethodProcessor@6514} 
14 = {ModelAttributeMethodProcessor@6515} 

大概还是跟上面一样吧,Spring默认提供了很多功能,兼顾了绝大部分场景,这里简单列下关键代码,如下

/**
 * Invoke the method and handle the return value through one of the
 * configured {@link HandlerMethodReturnValueHandler HandlerMethodReturnValueHandlers}.
 * @param webRequest the current request
 * @param mavContainer the ModelAndViewContainer for this request
 * @param providedArgs "given" arguments matched by type (not resolved)
 */
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) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
            disableContentCachingIfNecessary(webRequest);
            mavContainer.setRequestHandled(true);
            return;
        }
    }
    else if (StringUtils.hasText(getResponseStatusReason())) {
        mavContainer.setRequestHandled(true);
        return;
    }

    mavContainer.setRequestHandled(false);
    Assert.state(this.returnValueHandlers != null, "No return value handlers");
    try {
        // 这一行是对于返回值的处理
        this.returnValueHandlers.handleReturnValue(
                returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
    }
    catch (Exception ex) {
        if (logger.isTraceEnabled()) {
            logger.trace(formatErrorForReturnValue(returnValue), ex);
        }
        throw ex;
    }
}

/**
 * Iterate over registered {@link HandlerMethodReturnValueHandler HandlerMethodReturnValueHandlers} and invoke the one that supports it.
 * @throws IllegalStateException if no suitable {@link HandlerMethodReturnValueHandler} is found.
 */
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
        ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {

    HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
    if (handler == null) {
        throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
    }
    handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}

@Nullable
private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {
    boolean isAsyncValue = isAsyncReturnValue(value, returnType);
    // 这里还是之前的15种,这里会逐个遍历
    for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
        if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {
            continue;
        }
        if (handler.supportsReturnType(returnType)) {
            return handler;
        }
    }
    return null;
}

具体细节就跟到这里吧,有需要再来研究。

3、自定义**

1、顶层接口

通过代码,我们知道了上述过程,虽然不知道内部的实现细节。我们照葫芦画瓢,试着去自己实现一个,先上父接口

/**
 * Strategy interface for resolving method parameters into argument values in
 * the context of a given request.
 *
 * @author Arjen Poutsma
 * @since 3.1
 * @see HandlerMethodReturnValueHandler
 */
// 最顶层解析参数的接口
public interface HandlerMethodArgumentResolver {

	/**
	 * Whether the given {@linkplain MethodParameter method parameter} is
	 * supported by this resolver.
	 * @param parameter the method parameter to check
	 * @return {@code true} if this resolver supports the supplied parameter;
	 * {@code false} otherwise
	 */
	boolean supportsParameter(MethodParameter parameter);

	/**
	 * Resolves a method parameter into an argument value from a given request.
	 * A {@link ModelAndViewContainer} provides access to the model for the
	 * request. A {@link WebDataBinderFactory} provides a way to create
	 * a {@link WebDataBinder} instance when needed for data binding and
	 * type conversion purposes.
	 * @param parameter the method parameter to resolve. This parameter must
	 * have previously been passed to {@link #supportsParameter} which must
	 * have returned {@code true}.
	 * @param mavContainer the ModelAndViewContainer for the current request
	 * @param webRequest the current request
	 * @param binderFactory a factory for creating {@link WebDataBinder} instances
	 * @return the resolved argument value, or {@code null} if not resolvable
	 * @throws Exception in case of errors with the preparation of argument values
	 */
	@Nullable
	Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;

}

/**
 * Strategy interface to handle the value returned from the invocation of a
 * handler method .
 *
 * @author Arjen Poutsma
 * @since 3.1
 * @see HandlerMethodArgumentResolver
 */
// 顶层接口处理返回值
public interface HandlerMethodReturnValueHandler {

	/**
	 * Whether the given {@linkplain MethodParameter method return type} is
	 * supported by this handler.
	 * @param returnType the method return type to check
	 * @return {@code true} if this handler supports the supplied return type;
	 * {@code false} otherwise
	 */
	boolean supportsReturnType(MethodParameter returnType);

	/**
	 * Handle the given return value by adding attributes to the model and
	 * setting a view or setting the
	 * {@link ModelAndViewContainer#setRequestHandled} flag to {@code true}
	 * to indicate the response has been handled directly.
	 * @param returnValue the value returned from the handler method
	 * @param returnType the type of the return value. This type must have
	 * previously been passed to {@link #supportsReturnType} which must
	 * have returned {@code true}.
	 * @param mavContainer the ModelAndViewContainer for the current request
	 * @param webRequest the current request
	 * @throws Exception if the return value handling results in an error
	 */
	void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
			ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;

}

/**
 * Strategy interface that specifies a converter that can convert from and to HTTP requests and responses.
 *
 * @author Arjen Poutsma
 * @author Juergen Hoeller
 * @since 3.0
 * @param <T> the converted object type
 */
// 顶层接口转换结果消息,一般用于@ResponseBody的转换
// 而对于@ResponseBody处理的类为 RequestResponseBodyMethodProcessor
// 该类同时实现了 HandlerMethodArgumentResolver和HandlerMethodReturnValueHandler接口
public interface HttpMessageConverter<T> {

	/**
	 * Indicates whether the given class can be read by this converter.
	 * @param clazz the class to test for readability
	 * @param mediaType the media type to read (can be {@code null} if not specified);
	 * typically the value of a {@code Content-Type} header.
	 * @return {@code true} if readable; {@code false} otherwise
	 */
	boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);

	/**
	 * Indicates whether the given class can be written by this converter.
	 * @param clazz the class to test for writability
	 * @param mediaType the media type to write (can be {@code null} if not specified);
	 * typically the value of an {@code Accept} header.
	 * @return {@code true} if writable; {@code false} otherwise
	 */
	boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);

	/**
	 * Return the list of {@link MediaType} objects supported by this converter.
	 * @return the list of supported media types, potentially an immutable copy
	 */
	List<MediaType> getSupportedMediaTypes();

	/**
	 * Read an object of the given type from the given input message, and returns it.
	 * @param clazz the type of object to return. This type must have previously been passed to the
	 * {@link #canRead canRead} method of this interface, which must have returned {@code true}.
	 * @param inputMessage the HTTP input message to read from
	 * @return the converted object
	 * @throws IOException in case of I/O errors
	 * @throws HttpMessageNotReadableException in case of conversion errors
	 */
	T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
			throws IOException, HttpMessageNotReadableException;

	/**
	 * Write an given object to the given output message.
	 * @param t the object to write to the output message. The type of this object must have previously been
	 * passed to the {@link #canWrite canWrite} method of this interface, which must have returned {@code true}.
	 * @param contentType the content type to use when writing. May be {@code null} to indicate that the
	 * default content type of the converter must be used. If not {@code null}, this media type must have
	 * previously been passed to the {@link #canWrite canWrite} method of this interface, which must have
	 * returned {@code true}.
	 * @param outputMessage the message to write to
	 * @throws IOException in case of I/O errors
	 * @throws HttpMessageNotWritableException in case of conversion errors
	 */
	void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
			throws IOException, HttpMessageNotWritableException;

}

2、关键代码

关键代码如下

@RequestMapping("/testParam")
// 如果使用@ResponseBody的话,等于是选择了RequestResponseBodyMethodProcessor来处理返回值
// 这是可能需要自定义一个HttpMessageConverter,以便让RequestResponseBodyMethodProcessor进行对象转换 
public User testParam(@CurrentUser User user) {
    System.out.println(user);
    return user;
}

public class CurrentUserMethodArgumentResolver implements HandlerMethodArgumentResolver {

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.getParameterType().isAssignableFrom(User.class)
                && parameter.hasParameterAnnotation(CurrentUser.class);
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
        User user = new User();
        user.setId(1);
        user.setName("11");
        user.setAge(21);
        return user;
    }
}

public class UserMethodReturnValueHandler implements HandlerMethodReturnValueHandler {

    @Override
    public boolean supportsReturnType(MethodParameter returnType) {
        System.out.println("supportsReturnType" + " " + returnType + " " + returnType.getParameterType());
        return returnType.getParameterType() == User.class;
    }

    @Override
    public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
        // 不渲染视图
        mavContainer.setRequestHandled(true);
        HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
        Assert.state(response != null, "No HttpServletResponse");
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.setCharacterEncoding("UTF-8");
        System.out.println("handleReturnValue" + returnValue + " " + returnType + " " + mavContainer + " " + webRequest);
        response.getWriter().write(returnValue.toString());
        response.flushBuffer();
    }
}

<mvc:annotation-driven>
    <mvc:argument-resolvers>
        <bean class="xiaokui.config.CurrentUserMethodArgumentResolver"/>
    </mvc:argument-resolvers>
    <mvc:return-value-handlers>
        <bean class="xiaokui.config.UserMethodReturnValueHandler"/>
    </mvc:return-value-handlers>
</mvc:annotation-driven>

3、自定义HttpMessageConverter

@RequestMapping("/testParam")
@ResponseBody
public User testParam(@CurrentUser User user) {
    System.out.println(user);
    return user;
}

@Component
public class UserMessageConvert implements HttpMessageConverter<User>, ApplicationListener<ContextRefreshedEvent>, ApplicationContextAware {

    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        RequestMappingHandlerAdapter adapter = applicationContext.getBean(RequestMappingHandlerAdapter.class);
        if (!adapter.getMessageConverters().contains(this)) {
            adapter.getMessageConverters().add(this);
            System.out.println("onApplicationEvent成功添加自定义转换器");
        }
    }

    @Override
    public boolean canRead(Class<?> clazz, MediaType mediaType) {
        return clazz == User.class;
    }

    @Override
    public boolean canWrite(Class<?> clazz, MediaType mediaType) {
        return clazz == User.class;
    }

    @Override
    public List<MediaType> getSupportedMediaTypes() {
        // 不能返回null
        List<MediaType> list = new ArrayList<MediaType>(2);
        list.add(MediaType.TEXT_PLAIN);
        return list;
    }

    @Override
    public User read(Class<? extends User> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputMessage.getBody()));
        StringBuilder s = new StringBuilder();
        String temp;
        while ((temp = bufferedReader.readLine()) != null) {
            s.append(temp);
        }
        System.out.println(s);
        return null;
    }

    @Override
    public void write(User user, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
        outputMessage.getBody().write(user.toString().getBytes());
    }
}

4、getModelAndView

@Nullable
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, mavContainer.getStatus());
    if (!mavContainer.isViewReference()) {
        mav.setView((View) mavContainer.getView());
    }
    if (model instanceof RedirectAttributes) {
        Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
        HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
        if (request != null) {
            RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
        }
    }
    return mav;
}

9、applyDefaultViewName

applyDefaultViewName(processedRequest, mv);

/**
 * Do we need view name translation?
 */
private void applyDefaultViewName(HttpServletRequest request, @Nullable ModelAndView mv) throws Exception {
    // 如果返回的是视图页面,但有没有指定视图名称,则返回默认视图
    if (mv != null && !mv.hasView()) {
        String defaultViewName = getDefaultViewName(request);
        if (defaultViewName != null) {
            mv.setViewName(defaultViewName);
        }
    }
}

10、processDispatchResult

Spring在这一步对请求结果进行处理,不管最终的结果是流数据、还是视图界面、还是抛出异常,都在这里统一处理。我们先来回顾一下代码,如下

/**
 * Process the actual dispatching to the handler.
 * <p>The handler will be obtained by applying the servlet's HandlerMappings in order.
 * The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters
 * to find the first that supports the handler class.
 * <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers
 * themselves to decide which methods are acceptable.
 * @param request current HTTP request
 * @param response current HTTP response
 * @throws Exception in case of any kind of processing failure
 */
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HttpServletRequest processedRequest = request;
    HandlerExecutionChain mappedHandler = null;
    boolean multipartRequestParsed = false;

    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

    try {
        ModelAndView mv = null;
        Exception dispatchException = null;

        try {
            processedRequest = checkMultipart(request);
            multipartRequestParsed = (processedRequest != request);

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

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

            // Process last-modified header, if supported by the handler.
            String method = request.getMethod();
            boolean isGet = "GET".equals(method);
            if (isGet || "HEAD".equals(method)) {
                long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                    return;
                }
            }

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

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

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

            applyDefaultViewName(processedRequest, mv);
            mappedHandler.applyPostHandle(processedRequest, response, mv);
        }
        catch (Exception ex) {
            // 这里记录一下可能出现的异常
            dispatchException = ex;
        }
        catch (Throwable err) {
            // As of 4.3, we're processing Errors thrown from handler methods as well,
            // making them available for @ExceptionHandler methods and other scenarios.
            // 而针对Error,则直接抛错即可
            dispatchException = new NestedServletException("Handler dispatch failed", err);
        }
        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    }
    catch (Exception ex) {
        // 这里调用拦截器中afterCompletion
        // 注意这里把异常传进去了
        triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
    }
    catch (Throwable err) {
        triggerAfterCompletion(processedRequest, response, mappedHandler,
                new NestedServletException("Handler processing failed", err));
    }
    finally {
        if (asyncManager.isConcurrentHandlingStarted()) {
            // Instead of postHandle and afterCompletion
            if (mappedHandler != null) {
                mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
            }
        }
        else {
            // Clean up any resources used by a multipart request.
            if (multipartRequestParsed) {
                cleanupMultipart(processedRequest);
            }
        }
    }
}

/**
 * Handle the result of handler selection and handler invocation, which is
 * either a ModelAndView or an Exception to be resolved to a ModelAndView.
 */
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
        @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
        @Nullable 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.isTraceEnabled()) {
            logger.trace("No view rendering, null ModelAndView returned.");
        }
    }

    if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
        // Concurrent handling started during a forward
        return;
    }

    if (mappedHandler != null) {
        // Exception (if any) is already handled..
        mappedHandler.triggerAfterCompletion(request, response, null);
    }
}

1、异常处理

对于找不到的请求url,可以直接报404,但对于正常情况下的服务器处理请求错误,那么就会进到这里,可以决定是否报500还是200。

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

    // Success and error responses may use different content types
    request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);

    // Check registered HandlerExceptionResolvers...
    ModelAndView exMv = null;
    // Spring帮我们内置了3个
    // 2 = {DefaultHandlerExceptionResolver@5233} 
	// 1 = {ResponseStatusExceptionResolver@5232} 
	// 0 = {ExceptionHandlerExceptionResolver@5231} 
    if (this.handlerExceptionResolvers != null) {
        for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
            exMv = resolver.resolveException(request, response, handler, ex);
            if (exMv != null) {
                break;
            }
        }
    }
    // 如果exMV为null,则说明异常处理器不能处理该异常,不为null则代表以处理
    if (exMv != null) {
        // 处理为空的特例,只返回状态码
        if (exMv.isEmpty()) {
            request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
            return null;
        }
        // We might still need view name translation for a plain error model...
        if (!exMv.hasView()) {
            String defaultViewName = getDefaultViewName(request);
            if (defaultViewName != null) {
                exMv.setViewName(defaultViewName);
            }
        }
        if (logger.isTraceEnabled()) {
            logger.trace("Using resolved error view: " + exMv, ex);
        }
        else if (logger.isDebugEnabled()) {
            logger.debug("Using resolved error view: " + exMv);
        }
        WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
        return exMv;
    }
	// 否则直接抛出去
    throw ex;
}

2、渲染界面

我们看下渲染界面的逻辑,如下

/**
 * Render the given ModelAndView.
 * <p>This is the last stage in handling a request. It may involve resolving the view by name.
 * @param mv the ModelAndView to render
 * @param request current HTTP servlet request
 * @param response current HTTP servlet response
 * @throws ServletException if view is missing or cannot be resolved
 * @throws Exception if there's a problem rendering the view
 */
// 方法均来自类 DispatchServlet
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 != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
    // 解析loccal,国内一般为zh_CN,可参考前面对Locale的处理
    response.setLocale(locale);

    View view;
    String viewName = mv.getViewName();
    if (viewName != null) {
        // We need to resolve the view name.
        // 开始根据视图名解析视图了
        view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
        // 找不到视图对应的页面
        if (view == null) {
            throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
                    "' in servlet with name '" + getServletName() + "'");
        }
    }
    else {
        // No need to lookup: the ModelAndView object contains the actual View object.
        view = mv.getView();
        if (view == null) {
            throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
                    "View object in servlet with name '" + getServletName() + "'");
        }
    }

    // Delegate to the View object for rendering.
    if (logger.isTraceEnabled()) {
        logger.trace("Rendering view [" + view + "] ");
    }
    try {
        if (mv.getStatus() != null) {
            response.setStatus(mv.getStatus().value());
        }
        // 开始渲染,输出字节流
        view.render(mv.getModelInternal(), request, response);
    }
    catch (Exception ex) {
        if (logger.isDebugEnabled()) {
            logger.debug("Error rendering view [" + view + "]", ex);
        }
        throw ex;
    }
}

/**
 * Resolve the given view name into a View object (to be rendered).
 * <p>The default implementations asks all ViewResolvers of this dispatcher.
 * Can be overridden for custom resolution strategies, potentially based on
 * specific model attributes or request parameters.
 * @param viewName the name of the view to resolve
 * @param model the model to be passed to the view
 * @param locale the current locale
 * @param request current HTTP servlet request
 * @return the View object, or {@code null} if none found
 * @throws Exception if the view cannot be resolved
 * (typically in case of problems creating an actual View object)
 * @see ViewResolver#resolveViewName
 */
@Nullable
protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
        Locale locale, HttpServletRequest request) throws Exception {
	// 注意这里的逻辑,如果有多个视图解析器的话,那么返回第一个视图解析器能够处理的View
    // 在有多个视图解析器的情况下,这一步可能会发送意料之外的情况,例如期待a页面被解析器1解析,而却被解析器2解析
    if (this.viewResolvers != null) {
        for (ViewResolver viewResolver : this.viewResolvers) {
            // 这一步留给了视图解析器很大的空间,我就在这一步中遇到过坑,这一步里面包含的view资源可能存在,也可能不存在,看解析器里面是否做了资源存在性的检查
            View view = viewResolver.resolveViewName(viewName, locale);
            if (view != null) {
                return view;
            }
        }
    }
    return null;
}

// 这里以类 InternalResourceViewResolver 为例,大致过下
@Override
@Nullable
public View resolveViewName(String viewName, Locale locale) throws Exception {
    // 是否启用缓存,默认为true
    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.cacheFilter.filter(view, viewName, locale)) {
                        this.viewAccessCache.put(cacheKey, view);
                        this.viewCreationCache.put(cacheKey, view);
                    }
                }
            }
        }
        else {
            if (logger.isTraceEnabled()) {
                logger.trace(formatKey(cacheKey) + "served from cache");
            }
        }
        return (view != UNRESOLVED_VIEW ? view : null);
    }
}

/**
 * Prepares the view given the specified model, merging it with static
 * attributes and a RequestContext attribute, if necessary.
 * Delegates to renderMergedOutputModel for the actual rendering.
 * @see #renderMergedOutputModel
 */
@Override
public void render(@Nullable Map<String, ?> model, HttpServletRequest request,
        HttpServletResponse response) throws Exception {

    if (logger.isDebugEnabled()) {
        logger.debug("View " + formatViewName() +
                ", model " + (model != null ? model : Collections.emptyMap()) +
                (this.staticAttributes.isEmpty() ? "" : ", static attributes " + this.staticAttributes));
    }

    Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
    prepareResponse(request, response);
    renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
}

/**
 * Render the internal resource given the specified model.
 * This includes setting the model as request attributes.
 */
@Override
protected void renderMergedOutputModel(
        Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {

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

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

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

    // Obtain a RequestDispatcher for the target resource (typically a JSP).
    RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
    if (rd == null) {
        throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +
                "]: Check that the corresponding file exists within your web application archive!");
    }

    // If already included or response already committed, perform include, else forward.
    if (useInclude(request, response)) {
        response.setContentType(getContentType());
        if (logger.isDebugEnabled()) {
            logger.debug("Including [" + getUrl() + "]");
        }
        rd.include(request, response);
    }

    else {
        // Note: The forwarded resource is supposed to determine the content type itself.
        if (logger.isDebugEnabled()) {
            logger.debug("Forwarding to [" + getUrl() + "]");
        }
        // 这里使用Servlet中标准的RequestDispatcher来进行转发
        // Spring并不直接负责文件页面的输出,而是委托给了Servlet容器
        rd.forward(request, response);
    }
}

3、triggerAfterCompletion

/**
 * Trigger afterCompletion callbacks on the mapped HandlerInterceptors.
 * Will just invoke afterCompletion for all interceptors whose preHandle invocation
 * has successfully completed and returned true.
 */
// 调用拦截器中的各个afterCompletion
private void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response,
        @Nullable HandlerExecutionChain mappedHandler, Exception ex) throws Exception {

    if (mappedHandler != null) {
        mappedHandler.triggerAfterCompletion(request, response, ex);
    }
    throw ex;
}

/**
 * Trigger afterCompletion callbacks on the mapped HandlerInterceptors.
 * Will just invoke afterCompletion for all interceptors whose preHandle invocation
 * has successfully completed and returned true.
 */
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable 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);
            }
        }
    }
}

至此,Spring总算搞一段落了。剩下的计划有二:

  • 自己写一个迷你Spring,包含完整Spring框架的整个处理流程,但只保留核心步骤。这个之前也有写过一部分,但后面发现已经不是很有必要了,为什么呢?因为我对Spring已经到这种程度的理解,写一个不是花点时间的问题吗?但说归说,还是要抽个时间来搞的,毕竟纸上得来终觉浅。
  • 开始啃Netty源码,现在是2020年8月30日,希望能在本年内完成吧。
总访问次数: 829次, 一般般帅 创建于 2020-04-02, 最后更新于 2020-09-12

进大厂! 欢迎关注微信公众号,第一时间掌握最新动态!