SpringMvc 启动过程源码分析

寻找起源

先来看springmv中,web.xml的配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<listener>
<description>Spring监听器</description>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- ContextLoaderListener初始化Spring上下文时需要使用到的contextConfigLocation参数 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<!-- 配置spring.xml和spring-mybatis.xml这两个配置文件的位置,固定写法 ,classpath:dubbo-provider.xml-->
<param-value>classpath:spring.xml,classpath:spring-mybatis.xml,classpath:spring-redis.xml,classpath:dubbo-provider.xml</param-value>
</context-param>

<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

<!-- 配置log4j配置文件路径 -->
<listener>
<listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
</listener>
<context-param>
<param-name>log4jConfigLocation</param-name>
<param-value>classpath:property/log4j.properties</param-value>
</context-param>


<servlet>
<servlet-name>springServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>

<session-config>
<session-timeout>10000</session-timeout>
</session-config>

web.xml加载的顺序

  1. context-param

    初始化运行环境需要的配置信息

  2. listener

    监听器

  3. filter

    过滤器

  4. servlet

    处理



构造一个 无参ContextLoadListener

1
2
3
Alice->Bob: Hello Bob, how are you?
Note right of Bob: Bob thinks
Bob-->Alice: I am good thanks!

类图
contextLoader类图

  1. 根据加载顺序构造一个无参的ContextLoadListener
    初始化spring的解析策略


实际就是将WebApplicationContext初始化为XmlWebApplicationContext

1
2
3
4
5
6
7
# Default WebApplicationContext implementation class for ContextLoader.
# Used as fallback when no explicit context implementation has been specified as context-param.
# Not meant to be customized by application developers.

org.springframework.web.context.WebApplicationContext
=
org.springframework.web.context.support.XmlWebApplicationContext

  1. 因为ContextLoadListener继承 ServletContextListener 所以在tomcat容器启动的时候,会调用contextInitialized
    contextInitialized

    详细说下 ContextLoader.initWebApplicationContext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
throw new IllegalStateException(
"Cannot initialize context because there is already a root application context present - " +
"check whether you have multiple ContextLoader* definitions in your web.xml!");
}

servletContext.log("Initializing Spring root WebApplicationContext");
Log logger = LogFactory.getLog(ContextLoader.class);
if (logger.isInfoEnabled()) {
logger.info("Root WebApplicationContext: initialization started");
}
long startTime = System.currentTimeMillis();

try {
// Store context in local instance variable, to guarantee that
// it is available on ServletContext shutdown.
// 如果当前context为空,则创建servletcontext
if (this.context == null) {
/*
* 返回
* (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(servletContext);
*/
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
// cwac.getParent() 第一次进入结果返回 null
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent ->
// determine parent for root web application context, if any.
// loadParentContext 直接返回 null

ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
// 开始装载 Applicationcontextle
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

ClassLoader ccl = Thread.currentThread().getContextClassLoader();
if (ccl == ContextLoader.class.getClassLoader()) {
currentContext = this.context;
}
else if (ccl != null) {
currentContextPerThread.put(ccl, this.context);
}

if (logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms");
}

return this.context;
}
catch (RuntimeException | Error ex) {
logger.error("Context initialization failed", ex);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
throw ex;
}
}

configureAndRefreshWebApplicationContext的处理逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
.......
// 获取web.xml中标签是context-param并且 param-name 是contextConfigLocation的配置值
String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
if (configLocationParam != null) {
// 奖这些配置文件缓存到webApplicationContext中
wac.setConfigLocation(configLocationParam);
}

// The wac environment's #initPropertySources will be called in any case when the context
// is refreshed; do it eagerly here to ensure servlet property sources are in place for
// use in any post-processing or initialization that occurs below prior to #refresh
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
}
// 定制化设置applicationcontext
/*
* 步骤
* 1、判断web.xml中是否配置了 globalInitializerClasses,contextInitializerClasses
* 如果有的话就他们放到 List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>>中
* 2、循环1的结果实例化每个class后放到 contextInitializers
* 3、对contextInitializers排序
* 4、循环3结果,然后对每个元素调用initialize(wac);
*
* */
customizeContext(sc, wac);
wac.refresh();
}

refresh的步骤
refresh步骤

步骤分析

1、prepareRefresh()

准备刷新,里面主要是加载一些系统配置信息

1
2
3
// Initialize any placeholder property sources in the context environment.
initPropertySources();
// 具体实现是返回null

2、ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory()

构建beanFactory信息

1
2
3
4
5
6
7
8
9
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {

refreshBeanFactory();
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (logger.isDebugEnabled()) {
logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
}
return beanFactory;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
protected final void refreshBeanFactory() throws BeansException {
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
// beanFactory类型:DefaultListableBeanFactory
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
customizeBeanFactory(beanFactory);
// 通过xml加载声明的bean信息
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}


}

loadBeanDefinitions过程
ETRC8g.md.png

2.1 装载context-param文件信息
1
2
3
4
5
6
7
8
9
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
// 获取 context-param里的配置信息
String[] configLocations = getConfigLocations();
if (configLocations != null) {
for (String configLocation : configLocations) {
reader.loadBeanDefinitions(configLocation);
}
}
}
2.2 实例化bean信息

3、prepareBeanFactory

如下图所示,往beanfactory中注册,排除一些系统环境必须的bean信息

4、postProcessBeanFactory

注册后置处理器

1
2
3
4
5
6
7
8
9
10
11
12
/**
* Register request/session scopes, a {@link ServletContextAwareProcessor}, etc.
*/
@Override
protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
beanFactory.addBeanPostProcessor(new ServletContextAwareProcessor(this.servletContext, this.servletConfig));
beanFactory.ignoreDependencyInterface(ServletContextAware.class);
beanFactory.ignoreDependencyInterface(ServletConfigAware.class);

WebApplicationContextUtils.registerWebApplicationScopes(beanFactory, this.servletContext);
WebApplicationContextUtils.registerEnvironmentBeans(beanFactory, this.servletContext, this.servletConfig);
}

ServletContextAwareProcessor的说明:Web application contexts will automatically register this with their
underlying bean factory. Applications do not use this directly.

5、invokeBeanFactoryPostProcessors

Instantiate and invoke all registered BeanFactoryPostProcessor beans,
实例化并注册所有注册的BeanFactoryPostProcessor

5.1 获取所有beanFactoryPostprocess
1
2
3
4
5
6
7
/**
* Return the list of BeanFactoryPostProcessors that will get applied
* to the internal BeanFactory.
*/
public List<BeanFactoryPostProcessor> getBeanFactoryPostProcessors() {
return this.beanFactoryPostProcessors;
}
5.2 注入BeanFactoryPostProcessors

invokeBeanFactoryPostProcessors

它有个先后顺序。首先注入实现了PriorityOrdered,然后注入实现了Ordered

6、registerBeanPostProcessors