源码级剖析ContextLoaderListener
文章目录
前言
在Spring项目中,ContextLoaderListener扮演着至关重要的作用。项目启动需要ContextLoaderListener加载spring配置文件,所以经常在web.xml中来配置ContextLoaderListener。
<!-- 配置spring核心监听器,默认会以 /WEB-INF/applicationContext.xml作为配置文件 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- contextConfigLocation参数用来指定Spring的配置文件 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext*.xml</param-value>
</context-param>
ContextLoaderListener是怎么工作的呢?通过ContextLoaderListener源码来分析一下
ContextLoaderListener
//ContextLoaderListener继承ContextLoader,实现ServletContextListener接口。
public class ContextLoaderListener extends ContextLoader implements ServletContextListener{
public ContextLoaderListener() {
}
public ContextLoaderListener(WebApplicationContext context) {
super(context);
}
//ContextLoaderListener在服务器启动时调用contextInitialized
public void contextInitialized(ServletContextEvent event) {
//此处ContextLoaderListener调用的ContextLoader的initWebApplicationContext方法
this.initWebApplicationContext(event.getServletContext());
}
//ContextLoaderListener在服务器关闭时调用contextDestroyed
public void contextDestroyed(ServletContextEvent event) {
this.closeWebApplicationContext(event.getServletContext());
ContextCleanupListener.cleanupAttributes(event.getServletContext());
}
}
- ContextLoaderListener继承ContextLoader
- ContextLoaderListener实现ServletContextListener接口
- contextInitialized是ContextLoaderListener的关键核心方法,也是我们的重点研究对象
我们先来了解下ServletContextListener接口
ServletContextListener
ServletContextListener主要用于监听ServletContext对象的生命周期,服务器启动时 创建ServletContext,服务器关闭时销毁ServletContext
public interface ServletContextListener extends EventListener {
//Servlet容器启动Web应用时调用该方法。在调用完该方法之后,初始化Filter,并且对那些在Web应用启动时就需要被初始化的Servlet 进行初始化
default void contextInitialized(ServletContextEvent sce) {
}
//当Servlet容器终止Web应用时调用该方法。在调用该方法之前,容器会先销毁所有的Servlet和Filter过滤器。
default void contextDestroyed(ServletContextEvent sce) {
}
}
当servlet容器启动或终止时,触发ServletContextEvent事件。ServletContextEvent作为参数参与了ContextLoader的initWebApplicationContext方法。
ContextLoaderListener调用父类ContextLoader的initWebApplicationContext方法,接下来重点看一下ContextLoader类。
ContextLoader
在看ContextLoader的initWebApplicationContext的方法前,先来看下ContextLoader的静态代码块
1. 静态代码块
static {
try {
//DEFAULT_STRATEGIES_PATH = "ContextLoader.properties",此处加载的是contextLoader.properties的配置文件
ClassPathResource resource = new ClassPathResource("ContextLoader.properties", ContextLoader.class);
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
} catch (IOException var1) {
throw new IllegalStateException("Could not load 'ContextLoader.properties': " + var1.getMessage());
}
currentContextPerThread = new ConcurrentHashMap(1);
}
源码里翻看下contextLoader.properties里是怎么设置的?接着翻
org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext
ContextLoader通过静态代码块读取XmlWebApplicationContext,在用户没有在配置文件指定CONTEXT_CLASS的时候,作为默认值使用。
2. initWebApplicationContext
看完静态代码块,重头戏来了,看看核心方法initWebApplicationContext。
public WebApplicationContext initWebApplicationContext(
ServletContext servletContext) {
// 检查application对象中是否有ApplicationContext,有则抛出异常
// 其中ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";
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!");
// 无法初始化上下文,因为已经存在根应用程序上下文
// web.xml中定义了多个ContextLoader
}else { //否则就初始化WebApplicationContext,"Initializing Spring root WebApplicationContext"这句 是不是很熟悉?
servletContext.log("Initializing Spring root WebApplicationContext");
Log logger = LogFactory.getLog(ContextLoader.class);
if (logger.isInfoEnabled()) {
logger.info("Root WebApplicationContext: initialization started");
}
/**
*
* 创建得到WebApplicationContext
* 将createWebApplicationContext返回值转换为ConfigurableWebApplicationContext类型
*/
if (this.context == null) {
//调用createWebApplicationContext,创建WebApplicationContext,看步骤2.1
this.context = createWebApplicationContext(servletContext);
}
// 拿到ConfigurableWebApplicationContext进入if判断
if (this.context instanceof ConfigurableWebApplicationContext) {
// 强制转换为ConfigurableWebApplicationContext类型
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
// cwac(ApplicationContext)没有生效,配置文件没有加载
if (!cwac.isActive()) {
//applicationContext没有刷新,要设置一个parent
if (cwac.getParent() == null) {
ApplicationContext parent = this.loadParentContext(servletContext);
cwac.setParent(parent);
}
// 加载配置文件
// 此处调用了configureAndRefreshWebApplicationContext方法,看步骤2.2
this.configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
// 将WebApplicationContext绑定到web应用程序的ServtContext上
servletContext.setAttribute(
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,
this.context);
// 返回webApplicationContext
return this.context;
}
init中有两个核心方法
- createWebApplicationContext()
- configureAndRefreshWebApplicationContext()
先来看看如何创建webApplicationContext
2.1 createWebApplicationContext
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
// 获取context类, 看步骤2.1.1
Class<?> contextClass = this.determineContextClass(sc);//判断使用什么样的类在Web容器中作为IoC容器
如果context类没有实现ConfigurableWebApplicationContext接口则抛出异常
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException("Custom context class [" + contextClass.getName() + "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
} else {
返回该context类的实例,调用BeanUtils.instantiateClass(contextClass),通过反射,调用XmlWebApplicationContext的无参构造函数实例化XmlWebApplicationContext对象
return (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);
}
}
2.1.1 determineContextClass
/**
* 返回context
*/
protected Class<?> determineContextClass(ServletContext servletContext) {
//从servletContext中获取初始化配置参数contextClass,配置类实现了XmlWebApplicationContext
String contextClassName = servletContext.getInitParameter("contextClass");//读取web.xml中的配置<context-param>contextClass</context-param>
//有配置使用配置的CONTEXT_CLASS,默认使用XmlWebApplicationContext.
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 {
// 如果没有配置则使用XmlWebApplicationContext,此处读取的contextLoad.properties配置
//
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);
}
}
}
- contextClass继承了XmlWebApplicationContext
- XmlWebApplicationContext继承了AbstractRefreshableWebApplicationContext,调用了getConfigLocations()
// 该方法默认加载contextConfigLocation中定义的xml 文件
public String[] getConfigLocations() {
return super.getConfigLocations();
}
- AbstractRefreshableWebApplicationContext继承了AbstractRefreshableConfigApplicationContext
// this.getDefaultConfigLocations()返回null
@Nullable
protected String[] getConfigLocations() {
return this.configLocations != null ? this.configLocations : this.getDefaultConfigLocations();
}
此方法直接返回AbstractRefreshableConfigApplicationContext自己的属性:String[] configLocations,ContextLoader完成了对contextConfigLocation中的spring配置文件的加载。
终于找到了ContextLoaderListener是如何加载spring配置文件的,但是init方法还没结束,接下来我们看看如何初始化WebApplicationContext。
2.2 configureAndRefreshWebApplicationContext
// 配置并且刷新WebApplicationContext,设置IoC容器的参数,并通过refresh开启容器初始化
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()));
}
}
//为wac绑定servletContext
wac.setServletContext(sc);
//CONFIG_LOCATION_PARAM=contextConfigLocation
//getInitParameter(CONFIG_LOCATION_PARAM)解释了为什么配置文件中需要有contextConfigLocation项
//sevletConfig.getInitParameter和servletContext.getInitParameter作用范围是不一样的
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);
wac.refresh();//容器的初始化
}
总结
做个总结,先上图:
-
ContextLoadListener实现了ServletContextListener接口,重写了init和distory方法
- ServletContextListener主要监听ServletContext对象的生命周期,在ServletContext对象创建后,webApplicationContext才能被创建并绑定到ServletContext对象
- 绑定以后就可以通过servletContext获取webApplicationContext了
-
ContextLoadListener继承了ContextLoader类
- 通过静态代码块获取XmlWebApplicationContext作为默认的CONTEXT_CLASS,用于WebApplicationContext的创建
- init方法中create方法用于创建WebApplicationContext,configureAndRefresh用于初始化WebApplicationContext
- create方法中通过determineContextClass方法,最终使用AbstractRefreshableConfigApplicationContext类的getConfigLocations()
加载spring的配置文件 - configureAndRefresh结束完成了WebApplicationContext的初始化工作。
- create方法中通过determineContextClass方法,最终使用AbstractRefreshableConfigApplicationContext类的getConfigLocations()
好了,讲到这里,ContextLoadListener已经说完了!