Spring:启动流程

启动流程

  • Spring的启动流程主要是定位 -> 加载 -> 注册 -> 实例化:
    • 定位:获取配置文件路径。
    • 加载:把配置文件读取成BeanDefinition。
    • 注册:存储BeanDefinition。
    • 实例化:根据BeanDefinition创建实例。
  • 所谓的IOC容器其实就是BeanFactory,BeanFactory 是一个接口,有很多对应的实现类。
  • IOC容器的关键入口方法是refresh()。

img

Web容器

Web应用中使用的容器是XmlWebApplicationContext,其类图如下,可以看出最终是一个实现了 BeanFactory的类。

img

对于Web项目存在一个web.xml,其关键是要配置一个 ContextLoaderListener 和一个 DispatcherServlet。

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
<web-app>

<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring.xml</param-value>
</context-param>

<!-- ContextLoaderListener -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<!-- DispatcherServlet -->
<servlet>
<description>spring mvc servlet</description>
<servlet-name>springMvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<description>spring mvc</description>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springMvc</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
</web-app>

在Java Web容器中相关组件的启动顺序是ServletContext -> listener -> filter -> servlet,listener是优于servlet启动的,所以我们先看一看ContextLoaderListener的内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
public ContextLoaderListener() {
}

public ContextLoaderListener(WebApplicationContext context) {
super(context);
}

public void contextInitialized(ServletContextEvent event) {
this.initWebApplicationContext(event.getServletContext());
}

public void contextDestroyed(ServletContextEvent event) {
this.closeWebApplicationContext(event.getServletContext());
ContextCleanupListener.cleanupAttributes(event.getServletContext());
}
}

当Listener启动时会调用contextInitialized方法,而ContextLoaderListener中该方法的内容是继续调用initWebApplicationContext方法,于是我们再跟踪initWebApplicationContext(ContextLoaderListener是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
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!");
} else {
Log logger = LogFactory.getLog(ContextLoader.class);
servletContext.log("Initializing Spring root WebApplicationContext");
if (logger.isInfoEnabled()) {
logger.info("Root WebApplicationContext: initialization started");
}

long startTime = System.currentTimeMillis();

try {
if (this.context == null) {
this.context = this.createWebApplicationContext(servletContext);
}
.
.
.
}

此处我们关心的是createWebApplicationContext方法。

1
2
3
4
5
6
7
8
9
10
11
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {

/** [note-by-leapmie] determineContextClass方法中获取contextClass **/
Class<?> contextClass = determineContextClass(sc);
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
}
/** [note-by-leapmie] 根据contextClass返回实例 */
return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}

从代码可知,方法中的逻辑主要是调用determineContextClass获取contextClass,然后根据contextClass创建IOC容器实例。所以,contextClass的值将是关键。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
protected Class<?> determineContextClass(ServletContext servletContext) {
String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
if (contextClassName != null) {
.
.
.
}
else {
/**
* [note-by-leapmie]
* defaultStrategies的值是在本类中的static方法中注入的
* 即该类加载过程中defaultStrategies已经被赋值
* 本类的开始部分有static代码块
* **/
contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
try {
return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
}
catch (ClassNotFoundException ex) {
throw new ApplicationContextException(
"Failed to load default context class [" + contextClassName + "]", ex);
}
}
}

可以看到,contextClassName是从defaultStrategies中获取的,而关于defaultStrategies的赋值需要追溯到ContextLoader类中的静态代码块。

参考

  1. SpringBoot源码分析之SpringBoot的启动过程
  2. Spring源码分析专题 —— IOC容器启动过程(上篇)