SpringMVC(一):Web环境中的IoC容器启动过程

在springmvc项目中,可以有多个容器,而且容器有上下层关系,此处的容器指的是spring容器,它是springmvc的父容器,springmvc容器将在下一章讲解。

WebApplicationContext

web环境下spring默认使用的是WebApplicationContext,它是一个接口,看一下WebApplicationContext的定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public interface WebApplicationContext extends ApplicationContext {
// 用于在ServletContext取根上下文
String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";
String SCOPE_REQUEST = "request";
String SCOPE_SESSION = "session";
String SCOPE_APPLICATION = "application";
String SERVLET_CONTEXT_BEAN_NAME = "servletContext";
String CONTEXT_PARAMETERS_BEAN_NAME = "contextParameters";
String CONTEXT_ATTRIBUTES_BEAN_NAME = "contextAttributes";

// 获取ServletContext
@Nullable
ServletContext getServletContext();
}

(1)WebApplicationContext接口本身中的内容并不多,可以看到它继承了ApplicationContext,有一个ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE成员变量,这个变量用于将IoC容器设置到ServletContext时的key,之后就可以从ServletContext中获取IoC容器。

1
servletContext.setAttribute(WebApplicationConservletContexttext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

(2)既然WebApplicationContext只是一个接口,那么一定有一个它的实现类,作为IoC容器的实现,这个类就是XmlWebApplicationContext。

XmlWebApplicationContext

Spring使用XmlWebApplicationContext作为默认的WebApplicationContext容器实现,从继承关系中可以看到,XmlWebApplicationContext继承了WebApplicationContext,其实从名字上就可以看出,XmlWebApplicationContext也是ApplicationContext容器的实现,在ApplicationContext的基础之上,增加了对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
public class XmlWebApplicationContext extends AbstractRefreshableWebApplicationContext {
// 默认的配置文件applicationContext.xml,在WEB-INF目录下
public static final String DEFAULT_CONFIG_LOCATION = "/WEB-INF/applicationContext.xml";
// web项目的WEB-INF目录
public static final String DEFAULT_CONFIG_LOCATION_PREFIX = "/WEB-INF/";
public static final String DEFAULT_CONFIG_LOCATION_SUFFIX = ".xml";

public XmlWebApplicationContext() {
}

// 加载BeanDefinition的方法,在容器refresh()时启动
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// XmlBeanDefinitionReader对象,解析xml文件使用
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
beanDefinitionReader.setEnvironment(this.getEnvironment());
// 设置ResourceLoader,XmlWebApplicationContext是DefaultResource的子类,同样使用DefaultResourceLoader对资源文件定位
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
this.initBeanDefinitionReader(beanDefinitionReader);
// 载入BeanDefinition
this.loadBeanDefinitions(beanDefinitionReader);
}

protected void initBeanDefinitionReader(XmlBeanDefinitionReader beanDefinitionReader) {
}

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
// 获取资源文件的地址
String[] configLocations = this.getConfigLocations();
if(configLocations != null) {
String[] var3 = configLocations;
int var4 = configLocations.length;

for(int var5 = 0; var5 < var4; ++var5) {
String configLocation = var3[var5];
// 根据资源文件的地址由XmlBeanDefinitionReader解析文件并载入BeanDefinition
reader.loadBeanDefinitions(configLocation);
}
}

}

// 获取默认的配置
protected String[] getDefaultConfigLocations() {
return this.getNamespace() != null?new String[]{"/WEB-INF/" + this.getNamespace() + ".xml"}:new String[]{"/WEB-INF/applicationContext.xml"};
}
}

从XmlWebApplicationContext的启动过程来看,与IoC容器的初始化过程基本差不多,只不过在Web环境中,已经定义好了一个默认的配置文件,即WEB-INF/appicationContext.xml,下面看一下如何进入到XmlWebApplicationContext的loadBeanDefinitions方法中的。

ContextLoaderListener

ContextLoaderListener是Web容器中配置的监听器,WebApplicationContext容器的载入就是它负责的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
public ContextLoaderListener() {
}

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

public void contextInitialized(ServletContextEvent event) {
// 初始化WebApplicationContext,具体的工作在ContextLoader中完成
this.initWebApplicationContext(event.getServletContext());
}

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

ContextLoaderListener的contextInitialized方法中调用了initWebApplicationContext方法初始化web容器,该方法是在它的父类ContextLoader中实现的。

ContextLoader

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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
public class ContextLoader {

// Web IoC容器,也称作根上下文
@Nullable
private WebApplicationContext context;

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) {
// 创建容器WebApplicationContext
this.context = this.createWebApplicationContext(servletContext);
}
//如果容器是ConfigurableWebApplicationContext的一个实现
if(this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext)this.context;
if(!cwac.isActive()) {
// 如果双亲为空
if(cwac.getParent() == null) {
// 加载根上下文的双亲上下文
ApplicationContext parent = this.loadParentContext(servletContext);
cwac.setParent(parent);
}
// 配置并refresh根上下文
this.configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
// 将当前创建的web IoC容器context设置为servletContext的根上下文
servletContext.setAttribute(WebApplicationConservletContexttext.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.isDebugEnabled()) {
logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" + WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
}

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

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

/**
* 创建web容器的地方
*/
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
// 判断使用什么样的类作为Web环境中的IoC容器
Class<?> contextClass = this.determineContextClass(sc);
if(!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException("Custom context class [" + contextClass.getName() + "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
} else {
// 根据类信息实例化web IoC容器
return (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);
}
}

/**
* 判断使用哪种web容器
*/
protected Class<?> determineContextClass(ServletContext servletContext) {
// 读取在ServletContext中对contextClass参数的配置
String contextClassName = servletContext.getInitParameter("contextClass");
// 如果在ServletContext中对contextClass参数配置不为空
if(contextClassName != null) {
try {
// 返回类信息
return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
} catch (ClassNotFoundException var4) {
throw new ApplicationContextException("Failed to load custom context class [" + contextClassName + "]", var4);
}
} else {
// 如果配置信息为空,使用默认的WebApplicationContext
contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());

try {
return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
} catch (ClassNotFoundException var5) {
throw new ApplicationContextException("Failed to load default context class [" + contextClassName + "]", var5);
}
}
}

/**
* 配置并refresh,这个方法中调用了容器的refresh方法来完成Web环境下IoC容器的初始化
*/
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
String configLocationParam;
if(ObjectUtils.identityToString(wac).equals(wac.getId())) {
configLocationParam = sc.getInitParameter("contextId");
if(configLocationParam != null) {
wac.setId(configLocationParam);
} else {
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(sc.getContextPath()));
}
}
// 为容器设置ServletContext
wac.setServletContext(sc);
configLocationParam = sc.getInitParameter("contextConfigLocation");
if(configLocationParam != null) {
wac.setConfigLocation(configLocationParam);
}

ConfigurableEnvironment env = wac.getEnvironment();
if(env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment)env).initPropertySources(sc, (ServletConfig)null);
}

this.customizeContext(sc, wac);
// 调用容器的refresh方法,完成容器的初始化
wac.refresh();
}
}
  1. 在ContextLoader类中,存在一个WebApplicationContext类型的字段context, 这个变量就是Web环境中的IoC容器,也会配置在ServletContext的根上下文中,ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE为属性名,已经在WebApplication中定义为了一个常量。

  2. initWebApplicationContext方法,是启动IoC容器的入口:

    (1)如果容器还没有创建,会调用createWebApplicationContext方法创建IoC容器,在该方法中,会调用determineContextClass方法判断使用哪种IoC容器,如果没有设置contextClass参数,就使用默认的容器即WebApplicationContext。

    (2)接着会判断创建的容器是否是ConfigurableWebApplicationContext的实例,如果是,将设置双亲上下文,并调用configureAndRefreshWebApplicationContext方法,将当前的serveltContext设置到Ioc容器中,最后调用容器的refresh()方法,完成容器的初始化。

    (3)最后会把容器设置到ServletContext的根上下文中,并将容器返回。

AbstractApplicationContext

再回顾一下XmlWebApplicationContext的继承关系,它继承了AbstractRefreshableApplicationContext,AbstractRefreshableApplicationContext又继承了AbstractApplicationContext方法,refresh方法就是在AbstractApplicationContext实现的,之后的流程就和IoC容器启动过程一样了。

AbstractApplicationContext中的refresh()方法:

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
public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext {

public void refresh() throws BeansException, IllegalStateException {
Object var1 = this.startupShutdownMonitor;
synchronized(this.startupShutdownMonitor) {
this.prepareRefresh();
ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
// 调用AbstractRefreshableApplicationContext的prepareBeanFactory方法
this.prepareBeanFactory(beanFactory);

try {
this.postProcessBeanFactory(beanFactory);
this.invokeBeanFactoryPostProcessors(beanFactory);
this.registerBeanPostProcessors(beanFactory);
this.initMessageSource();
this.initApplicationEventMulticaster();
this.onRefresh();
this.registerListeners();
this.finishBeanFactoryInitialization(beanFactory);
this.finishRefresh();
} catch (BeansException var9) {
if(this.logger.isWarnEnabled()) {
this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
}

this.destroyBeans();
this.cancelRefresh(var9);
throw var9;
} finally {
this.resetCommonCaches();
}

}
}
}

AbstractRefreshableApplicationContext

refresh方法中调用了调用AbstractRefreshableApplicationContext的prepareBeanFactory方法,在该方法中对BeanDefinition进行了载入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 public abstract class AbstractRefreshableApplicationContext extends AbstractApplicationContext {
protected final void refreshBeanFactory() throws BeansException {
if(this.hasBeanFactory()) {
this.destroyBeans();
this.closeBeanFactory();
}
try {
DefaultListableBeanFactory beanFactory = this.createBeanFactory();
beanFactory.setSerializationId(this.getId());
this.customizeBeanFactory(beanFactory);
// 载入BeanDefinition
this.loadBeanDefinitions(beanFactory);
Object var2 = this.beanFactoryMonitor;
synchronized(this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
} catch (IOException var5) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + this.getDisplayName(), var5);
}
}
}

到此,如何进入到XmlWebApplicationContext的loadBeanDefinitions方法中的过程已经很明了了。

参考:

spring技术内幕:深入解析spring架构与设计原理

Spring和SpringMVC父子容器关系初窥

Spring版本:5.0.5