记忆、淡忘

Spring 启动过程详解

一、简述

1、我们在Java web 项目开发中,都会有一个web.xml文件,这个文件中一定会有如下一段代码:

1
2
3
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

这个ContextLoaderListener类中有一个方法如下:

1
2
3
4
5
6
7
/**
* Initialize the root web application context.
*/
@Override
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}

因此,我们可以知道web项目启动时调用contextInitialized进行初始化的,这个方法又会在
tomcat 或者Jetty等插件中被调用。当然了不管是Tomcat还是Jetty,操作的对象都是ServletContextListener
,这里不再详细描述,之后有时间学习一Jetty源码。

2、在web.xml中定义 contextConfigLocation参数.spring会使用这个参数加载所有逗号分割的xml,如果没有这个参数,Spring 自动加载applicationContext.xml 配置文件。

1
2
3
4
5
6
7
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath*:conf/applicationContext.xml,
classpath*:conf/spring-mvc.xml
</param-value>
</init-param>

这个ContextLoaderListener类中设置配置文件路径方法:

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 void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
// The application context id is still set to its original default value
// -> assign a more useful id based on available information
String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
if (idParam != null) {
wac.setId(idParam);
}
else {
// Generate default id...
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(sc.getContextPath()));
}
}
wac.setServletContext(sc);
//public static final String CONFIG_LOCATION_PARAM = "contextConfigLocation";
String initParameter = sc.getInitParameter(CONFIG_LOCATION_PARAM);
if (initParameter != null) {
wac.setConfigLocation(initParameter);
}
customizeContext(sc, wac);
wac.refresh();
}

二、ContextLoaderListener

Uml

1、在 Servlet API中有一个ServletContextListener接口,它能够监听ServletContext对象的生命周期,实际上就是监听Web应用的生命周期。
当Servlet容器启动或终止Web应用时,会触发ServletContextEvent事件,该事件由ServletContextListener来处理。

2、ContextLoaderListener监听器的作用就是启动Web容器时,自动装配ApplicationContext的配置信息。
因为它实现了ServletContextListener这个接口,在web.xml配置了这个监听器,启动容器时,就会默认执行它实现的方法。
由于在ContextLoaderListener中关联了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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
/**
* 所有事件监听器接口必须扩展
*/
public interface EventListener {
}
/**
*接受ServletContext的生命周期的事件通知接口
*
* 为了能接收通知事件,实现类要么在web应用程序的部署描述符中声明 ,用WebListener注解 或者通过ServletContext中的addListener 方法注册
*
* 接口的实现在contextInitialized 方法中按顺序什么,在contextDestroyed 方法中则是相反的顺序。
*/
public interface ServletContextListener extends EventListener {
/**
* web应用程序的初始化时收到通知,在所有的servlet和filter都已初始化前收到通知前
*/
public void contextInitialized(ServletContextEvent sce);
/**
* ServletContext销毁时会收到通知,收到通知前所有的servlet和filter都已被销毁
*/
public void contextDestroyed(ServletContextEvent sce);
}
/**
* 实际执行 root Application Context初始化的是ContextLoaderListener
*
* 在web.xml文件中寻找 <context-param></context-param>
* 这段标签指定上下文类型,如果找不到的话使用默认的XmlWebApplicationContext。
* 使用默认ContextLoader实现,任何上下文类都需要实现ConfigurableWebApplicationContext指定的接口
*
*
*<context-param><param-name>contextConfigLocation</param-name><param-value>classpath*:conf/applicationContext.xml</param-value></context-param>
*
*
*/
public class ContextLoader {
public static final String CONTEXT_CLASS_PARAM = "contextClass";
public static final String CONTEXT_ID_PARAM = "contextId";
public static final String CONTEXT_INITIALIZER_CLASSES_PARAM = "contextInitializerClasses";
public static final String CONFIG_LOCATION_PARAM = "contextConfigLocation";
public static final String LOCATOR_FACTORY_SELECTOR_PARAM = "locatorFactorySelector";
public static final String LOCATOR_FACTORY_KEY_PARAM = "parentContextKey";
/**
* 类路径资源名称(相对于ContextLoader类)定义ContextLoader名称的默认策略。
* 内容:org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext
*/
private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties";
private static final Properties defaultStrategies;
static {
//加载默认策略,实现属性文件中内容。
try {
ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
}
catch (IOException ex) {
throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());
}
}
/**
*ClassLoader WebApplicationContext映射
*/
private static final Map<ClassLoader, WebApplicationContext> currentContextPerThread =
new ConcurrentHashMap<ClassLoader, WebApplicationContext>(1);
/**
* The 'current' WebApplicationContext, if the ContextLoader class is
* 如果ContextLoader类是部署在web应用程序类加载器本身,当前的WebApplicationContext对象
*/
private static volatile WebApplicationContext currentContext;
/**
* 这个加载器管理的WebApplicationContext实例
*/
private WebApplicationContext context;
private BeanFactoryReference parentContextRef;
public ContextLoader() {
}
public ContextLoader(WebApplicationContext context) {
this.context = context;
}
/**
* 根据指定的servlet context初始化spring web应用上下文
*/
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!");
}
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 {
// 上下文存储在本地实例变量,保证它可以在ServletContext关闭时可用
if (this.context == null) {
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
if (!cwac.isActive()) {
// 上下文尚未刷新--> 提供 设置父母上下文、设置上下文Id的服务
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent ->
// determine parent for root web application context, if any.
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
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.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 ex) {
logger.error("Context initialization failed", ex);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
throw ex;
}
catch (Error err) {
logger.error("Context initialization failed", err);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
throw err;
}
}
/**
*为当前的加载器实例化WebApplicationContext,默认上下文类或一个自定义上下文类(如果指定)。
*
*/
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
Class<?> contextClass = determineContextClass(sc);
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
}
return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}
@Deprecated
protected WebApplicationContext createWebApplicationContext(ServletContext sc, ApplicationContext parent) {
return createWebApplicationContext(sc);
}
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
// 这个应用上下文Id 设置原来默认的值 ->分配一个更有价值的Id
String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
if (idParam != null) {
wac.setId(idParam);
}
else {
// Generate default id...
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(sc.getContextPath()));
}
}
wac.setServletContext(sc);
String initParameter = sc.getInitParameter(CONFIG_LOCATION_PARAM);
if (initParameter != null) {
wac.setConfigLocation(initParameter);
}
customizeContext(sc, wac);
wac.refresh();
}
/**
* 返回WebApplicationContext实现类使用,要么是默认XmlWebApplicationContext要么是自定义上下文类(在指定的情况下)。
*/
protected Class<?> determineContextClass(ServletContext servletContext) {
String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
if (contextClassName != null) {
try {
return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
}
catch (ClassNotFoundException ex) {
throw new ApplicationContextException(
"Failed to load custom context class [" + contextClassName + "]", ex);
}
}
else {
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);
}
}
}
/**
* Return the {@link ApplicationContextInitializer} implementation classes to use
* if any have been specified by {@link #CONTEXT_INITIALIZER_CLASSES_PARAM}.
* @param servletContext current servlet context
* @see #CONTEXT_INITIALIZER_CLASSES_PARAM
*/
@SuppressWarnings("unchecked")
protected List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>>
determineContextInitializerClasses(ServletContext servletContext) {
String classNames = servletContext.getInitParameter(CONTEXT_INITIALIZER_CLASSES_PARAM);
List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> classes =
new ArrayList<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>>();
if (classNames != null) {
for (String className : StringUtils.tokenizeToStringArray(classNames, ",")) {
try {
Class<?> clazz = ClassUtils.forName(className, ClassUtils.getDefaultClassLoader());
Assert.isAssignable(ApplicationContextInitializer.class, clazz,
"class [" + className + "] must implement ApplicationContextInitializer");
classes.add((Class<ApplicationContextInitializer<ConfigurableApplicationContext>>)clazz);
}
catch (ClassNotFoundException ex) {
throw new ApplicationContextException(
"Failed to load context initializer class [" + className + "]", ex);
}
}
}
return classes;
}
/**
* 在上下文刷新之前、在配置文位置配置好之后 加载器创建ConfigurableWebApplicationContext
*/
protected void customizeContext(ServletContext servletContext, ConfigurableWebApplicationContext applicationContext) {
List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> initializerClasses =
determineContextInitializerClasses(servletContext);
if (initializerClasses.isEmpty()) {
// no ApplicationContextInitializers have been declared -> nothing to do
return;
}
Class<?> contextClass = applicationContext.getClass();
ArrayList<ApplicationContextInitializer<ConfigurableApplicationContext>> initializerInstances =
new ArrayList<ApplicationContextInitializer<ConfigurableApplicationContext>>();
for (Class<ApplicationContextInitializer<ConfigurableApplicationContext>> initializerClass : initializerClasses) {
Class<?> initializerContextClass =
GenericTypeResolver.resolveTypeArgument(initializerClass, ApplicationContextInitializer.class);
if (initializerContextClass != null) {
Assert.isAssignable(initializerContextClass, contextClass, String.format(
"Could not add context initializer [%s] as its generic parameter [%s] " +
"is not assignable from the type of application context used by this " +
"context loader [%s]: ", initializerClass.getName(), initializerContextClass.getName(),
contextClass.getName()));
}
initializerInstances.add(BeanUtils.instantiateClass(initializerClass));
}
ConfigurableEnvironment env = applicationContext.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(servletContext, null);
}
AnnotationAwareOrderComparator.sort(initializerInstances);
for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : initializerInstances) {
initializer.initialize(applicationContext);
}
}
/**
*
*/
protected ApplicationContext loadParentContext(ServletContext servletContext) {
ApplicationContext parentContext = null;
String locatorFactorySelector = servletContext.getInitParameter(LOCATOR_FACTORY_SELECTOR_PARAM);
String parentContextKey = servletContext.getInitParameter(LOCATOR_FACTORY_KEY_PARAM);
if (parentContextKey != null) {
// locatorFactorySelector may be null, indicating the default "classpath*:beanRefContext.xml"
BeanFactoryLocator locator = ContextSingletonBeanFactoryLocator.getInstance(locatorFactorySelector);
Log logger = LogFactory.getLog(ContextLoader.class);
if (logger.isDebugEnabled()) {
logger.debug("Getting parent context definition: using parent context key of '" +
parentContextKey + "' with BeanFactoryLocator");
}
this.parentContextRef = locator.useBeanFactory(parentContextKey);
parentContext = (ApplicationContext) this.parentContextRef.getFactory();
}
return parentContext;
}
/**
* Close Spring's web application context for the given servlet context. If
* the default {@link #loadParentContext(ServletContext)} implementation,
* which uses ContextSingletonBeanFactoryLocator, has loaded any shared
* parent context, release one reference to that shared parent context.
* <p>If overriding {@link #loadParentContext(ServletContext)}, you may have
* to override this method as well.
* @param servletContext the ServletContext that the WebApplicationContext runs in
*/
public void closeWebApplicationContext(ServletContext servletContext) {
servletContext.log("Closing Spring root WebApplicationContext");
try {
if (this.context instanceof ConfigurableWebApplicationContext) {
((ConfigurableWebApplicationContext) this.context).close();
}
}
finally {
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
if (ccl == ContextLoader.class.getClassLoader()) {
currentContext = null;
}
else if (ccl != null) {
currentContextPerThread.remove(ccl);
}
servletContext.removeAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
if (this.parentContextRef != null) {
this.parentContextRef.release();
}
}
}
/**
* Obtain the Spring root web application context for the current thread
* (i.e. for the current thread's context ClassLoader, which needs to be
* the web application's ClassLoader).
* @return the current root web application context, or {@code null}
* if none found
* @see org.springframework.web.context.support.SpringBeanAutowiringSupport
*/
public static WebApplicationContext getCurrentWebApplicationContext() {
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
if (ccl != null) {
WebApplicationContext ccpt = currentContextPerThread.get(ccl);
if (ccpt != null) {
return ccpt;
}
}
return currentContext;
}
}
/**
*
*
* 如果使用Log4jConfigListener的话,在web.xml配置中,ContextLoaderListener要放在Log4jConfigListener后面
*
* 通过ContextLoaderListener(WebApplicationContext)构造函数引入web 应用上下文
*/
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
/**
* 在web.xml文件中声明<listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener>
* 时调用这个无参构造器
* 创建的应用上下文将被注册到ServletContext的属性名称WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE中,
* 当生命周期方法contextDestroyed调用时Spring应用程序上下文将被关闭时
*/
public ContextLoaderListener() {
}
/**
* 根据给定的应用程序上下文创建一个新的ContextLoaderListener
*/
public ContextLoaderListener(WebApplicationContext context) {
super(context);
}
/**
* Initialize the root web application context.
*/
@Override
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}
/**
* Close the root web application context.
*/
@Override
public void contextDestroyed(ServletContextEvent event) {
closeWebApplicationContext(event.getServletContext());
ContextCleanupListener.cleanupAttributes(event.getServletContext());
}
}