0%

Spring ApplicationContext Initialization (Part 1)


1. Background

The Spring container is the heart of the Spring framework. It handles object creation, dependency injection, lifecycle management — basically everything. The container uses Inversion of Control (IoC), also called Dependency Injection (DI), to connect objects. These managed objects are called Beans. The process is straightforward: Spring reads the bean configuration, builds a registry, then instantiates and wires up the beans.

Spring provides two container types: BeanFactory and ApplicationContext. BeanFactory is the low-level foundation for IoC, but ApplicationContext builds on top of it with enterprise-level features. The official docs lay out the differences:

Feature BeanFactory ApplicationContext
Bean instantiation / wiring Yes Yes
Lifecycle management No Yes
BeanPostProcessor auto-registration No Yes
BeanFactoryPostProcessor auto-registration No Yes
i18n via MessageSource No Yes
Event publishing via ApplicationEvent No Yes

This post focuses on ApplicationContext. The Spring source version referenced here is 5.2.5.RELEASE.

2. Class Relationships

ApplicationContext has many implementations. For this analysis, I’ll use a Java EE web application (built with Spring) as the example. To understand the initialization process, it helps to look at the class diagram for XmlWebApplicationContext:

3. Container Initialization

When a Java EE web app starts, ContextLoaderListener fires and initializes the root WebApplicationContext. Here’s the implementation:

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
/**
* Triggered by <context>contextLoaderListener</context> in web.xml.
* Initializes the root container.
*
* @param servletContext The application context; only one per application
* @return The root container
*/
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
// The root container is mounted on ServletContext — there can only be one
if (servletContext
.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE)
!= null) {
throw new IllegalStateException("...");
}
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 {
if (this.context == null) {
// Default root container is XmlWebApplicationContext
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac
= (ConfigurableWebApplicationContext) this.context;
// Container hasn't been refreshed yet
if (!cwac.isActive()) {
// Set parent context
if (cwac.getParent() == null) {
// For web apps, parent context defaults to null
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
// Configure and refresh
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
// Mount the root container on ServletContext with a well-known attribute name
servletContext
.setAttribute(WebApplicationContext
.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
// Get the current thread's class loader
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
if (ccl == ContextLoader.class.getClassLoader()) {
currentContext = this.context;
} else if (ccl != null) {
// If the class loader isn't ContextLoader's, store it in the thread-local map
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);
// On failure, mount the exception on ServletContext — no further initialization
servletContext
.setAttribute(WebApplicationContext
.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
throw ex;
}
}

/**
* Create the root WebApplicationContext
*
* @param sc ServletContext; one per application
* @return The root WebApplicationContext
*/
WebApplicationContext createWebApplicationContext(ServletContext sc) {
// Determine the implementation class
Class<?> contextClass = determineContextClass(sc);
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException("...");
}
// Instantiate the root container
return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}

/**
* Determine the root container implementation class
*
* @param servletContext Application context; one per application
* @return Implementation class; defaults to XmlWebApplicationContext
*/
Class<?> determineContextClass(ServletContext servletContext) {
// Read <context-param>contextClass</context-param> from web.xml
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 {
// If not configured, use the default: XmlWebApplicationContext
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);
}
}
}

Key takeaways from the code:

  • A web app gets exactly one root container
  • The implementation class is configurable; XmlWebApplicationContext is the default
  • A parent context is supported but defaults to null — this is a placeholder for parent-child container hierarchies
  • The root WebApplicationContext is mounted on ServletContext under the attribute name WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE

Now let’s look at the configuration and refresh phase:

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
/**
* Configure and refresh the root WebApplicationContext
*
* @param wac Configurable root container
* @param sc ServletContext; one per application
*/
void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac,
ServletContext sc) {
// 1. Assign an ID to the root container (used later to load Spring MVC config)
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
if (idParam != null) {
wac.setId(idParam);
} else {
// Generate a default ID
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(sc.getContextPath()));
}
}
// 2. Attach the application context
wac.setServletContext(sc);
// 3. Set the config location
// Reads <context-param>configLocation</context-param> from web.xml
String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
if (configLocationParam != null) {
wac.setConfigLocation(configLocationParam);
}
// 4. Initialize environment properties
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
}
// 5. Run custom context initializers
customizeContext(sc, wac);
// 6. Refresh the root container
wac.refresh();
}

/**
* Run custom context initializers
*
* @param sc ServletContext; one per application
* @param wac Configurable root container
*/
void customizeContext(ServletContext sc, ConfigurableWebApplicationContext wac) {
List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>>
initializerClasses =
determineContextInitializerClasses(sc);
for (Class<ApplicationContextInitializer<ConfigurableApplicationContext>>
initializerClass : initializerClasses) {
Class<?> initializerContextClass =
GenericTypeResolver.resolveTypeArgument(initializerClass,
ApplicationContextInitializer.class);
if (initializerContextClass != null
&& !initializerContextClass.isInstance(wac)) {
throw new ApplicationContextException(String.format("..."));
}
// Instantiate the initializer and add to the list
this.contextInitializers.add(BeanUtils.instantiateClass(initializerClass));
AnnotationAwareOrderComparator.sort(this.contextInitializers);
for (ApplicationContextInitializer<ConfigurableApplicationContext>
initializer : this.contextInitializers) {
// Execute each initializer in order
initializer.initialize(wac);
}
}
}

What we see here:

  • Config location comes from <context-param>configLocation</context-param> in web.xml
  • Environment properties are initialized early because initPropertySources is called during every refresh — this ensures Servlet properties are available to post-processors and other early lifecycle components
  • Custom initializers run after configuration is loaded but before the container refreshes. They’re loaded from ServletContext‘s contextInitializerClasses and globalInitializerClasses settings, then executed in order
  • After customization, wac.refresh() triggers the full container refresh — that’s the subject of the next post
扫描二维码分享