1、在开始看具体的源码实现之前,我们先一起来看看现在“陌生”的 web.xml 配置。代码如下:
在排列顺序上一点要按照<context-param>、<filter><filter-mapping>、<listener>、<servlet><servlet-mapping>
这样的顺序进行配置不然会报错
<web-app>
<display-name>Archetype Created Web Application</display-name>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<!--会初始化一个Root Spring WebApplicationContext,配置文件即为name为contextConfigLocation的<context-param>的值-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!--是一个HttpServlet的对象,出了拦截*.do的请求外,也会初始化一个属于它的Spring WebApplicationContext容器,
并以上面的root容器作为父容器,为什么要这样呢?因为有可能会创建多个类似此处的容器,同时以root容器作为父容器,
当然一般不会如此-->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
</web-app>
2、ContextLooaderListener extends ContextLoader implements ServletContextListener
2.1构造方法
因为父类ContextLoader有两个构造方法所以必须定义如下两个
public ContextLoaderListener() {
}
传入一个WebApplicationContext,这样就不需要再创建一个新的了
public ContextLoaderListener(WebApplicationContext context) {
super(context);
}
2.2 contextInitialized、contextDestroyed 方法
实现ServletContextListener的两个方法,实际上是分别调用ContextLoader的initWebApplicationContext,closeWebApplicationContext进行
初始化WebAoolicationContext和关闭其的动作
@Override
public void contextInitialized(ServletContextEvent event) {
// 初始化 WebApplicationContext
initWebApplicationContext(event.getServletContext());
}
@Override
public void contextDestroyed(ServletContextEvent event) {
closeWebApplicationContext(event.getServletContext());
ContextCleanupListener.cleanupAttributes(event.getServletContext());
}
3、ContextLoader
真正实现初始化和销毁WebApplicationContext容器的逻辑的类,即2.2提到的initWebApplicationContext和closeWebApplicationContext
3.1、重要属性
//这个配置文件中存在的键值对如下
//org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext
//这意味着如果不在<context-param>中指定WebApplicationContext类型,就是用这边的XmlWebApplicationContext,下面会分析到,见3.3
private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties";
//默认的配置 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());
}
}
//Root WebApplicationContext 对象
private WebApplicationContext context;
//两个构造方法,其中一个传入了applicationContext对象
public ContextLoader() {
}
public ContextLoader(WebApplicationContext context) {
this.context = context;
}
3.2、initWebApplicationContext
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
// <1> 若已经存在 ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE 对应的 WebApplicationContext 对象,则抛出 IllegalStateException 异常。
// 例如,在 web.xml 中存在多个 ContextLoader 。
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!");
}
// <2> 打印日志
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 {
// 如果属性中的WebApplicationContext为null,则进行创建初始化
if (this.context == null) {
// 初始化 context ,即创建 context 对象,见3.3
this.context = createWebApplicationContext(servletContext);
}
// 如果是 ConfigurableWebApplicationContext 的子类,如果未刷新,则进行配置和刷新
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
if (!cwac.isActive()) { // 如果此applicationContext没有执行刷新(激活),也就是未执行refresh()方法,继续向下;
if (cwac.getParent() == null) { // 若无父容器,则进行加载和设置。
//loadParentContext()方法返回一个null,修饰符为protected,需要子类去实现
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
// 配置 WebApplicationContext 对象,并进行刷新,见3.4
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
// 记录在 servletContext 中,因此在此方法开头会进行判断,只能存在一个Root WebApplicationContext容器,如果定义了多个ContextLoader,就会报错
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
// 记录到 currentContext 或 currentContextPerThread 中,这两个变量会在销毁的时候用到
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");
}
// 返回 context
return this.context;
} catch (RuntimeException | Error ex) {
// 当发生异常,记录异常到 WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE 中,不再重新初始化。
logger.error("Context initialization failed", ex);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
throw ex;
}
}
3.3、createWebApplicationContext
// ContextLoader.java
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
// <1> 获得 context 的类
Class<?> contextClass = determineContextClass(sc);
// <2> 判断 context 的类,是否符合 ConfigurableWebApplicationContext 的类型
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
}
// <3> 创建 context 的类的对象
return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}
protected Class<?> determineContextClass(ServletContext servletContext) {
//从ServletContext中读取属性名为contextClass的值,用于初始化,即<context-param><param-name>contextClass</param-name><param-value>·······类名········</param-value></context-param>
String contextClassName = servletContext.getInitParameter("contextClass");
//如果不为空的话就加载我们在web.xml中设置的类
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 {
//如果我们没有配置的话,就从defaultStrategies获取org.springframework.web.context.support.XmlWebApplicationContext进行加载,初始化
//在3.1处提到了
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);
}
}
}
3.4 configureAndRefreshWebApplicationContext
public static final String CONTEXT_ID_PARAM = "contextId";
public static final String CONFIG_LOCATION_PARAM = "contextConfigLocation";
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
// 如果 wac 使用了默认编号,则重新设置 id 属性
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
// 情况一,使用 contextId 属性,在web.xml中配置<context-param>
String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
if (idParam != null) {
wac.setId(idParam);
// 情况二,自动生成
} else {
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(sc.getContextPath()));
}
}
//设置 context 的 ServletContext 属性
wac.setServletContext(sc);
// 设置 context 的配置文件地址
String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
if (configLocationParam != null) {
wac.setConfigLocation(configLocationParam);
}
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
}
//执行自定义初始化 context
customizeContext(sc, wac);
// 刷新 context ,执行初始化
wac.refresh();
}