回顾
我们会在web.xml中配置监听器:ContextLoaderListener。本文就来介绍这个监听器在背后做了什么
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applictionContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
配置了ContextLoaderListener,那么在当前web应用启动时,就会生成一个Spring根容器(默认类型为XmlWebApplicationContext),并且将其存放在 ServletContext 域中(被整个应用所共享)
web应用启动 —> 创建ServletContext 对象 (ContextLoaderListener 监听ServletContext 对象) —> ServletContext对象创建完成 ,ContextLoaderListener 便会随即生成Spring根容器
ContextLoaderListener
是一个监听器,实现了 ServletContextListener
接口(负责监听 ServletContext 对象)(可以先看看ContextLoaderListener的继承结构图)
复习:JavaWeb 中的监听器
ServletContext 对象创建完成时,Tomcat 会调用 ServletContextListener 的 contextInitialized 方法
ServletContext 对象销毁前,Tomcat 会调用 ServletContextListener 的 contextDestroyed 方法
public interface ServletContextListener extends EventListener { public void contextInitialized ( ServletContextEvent sce ); public void contextDestroyed ( ServletContextEvent sce ); }
看一下 ContextLoaderListener 对 contextInitialized() 方法的实现
ContextLoaderListener # contextInitialized
@Override
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext()); //调用父类 ContextLoader 的 initWebApplicationContext() 方法
}
初始化Spring根容器
ContextLoader
# initWebApplicationContext
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
// 首先,先判断了当前 servletContext 的属性Attribute中是否已经有了Spring根容器,若有,则抛出异常提示(因为这才是第一次创建);
// 注:Spring根容器默认会保存在 ServletContext 的属性Attribute中,key 固定为 "org.springframework.web.context.WebApplicationContext.ROOT"
if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
throw new IllegalStateException("...");
}
//...
try {
/*
1、创建Spring根容器(父容器):WebApplicationContext 对象
*/
if (this.context == null) {
this.context = createWebApplicationContext(servletContext);
}
/*
2.1、为Spring根容器设置一个父类,这个是spring默认的行为,一般设置的父类都为null (不重要)
2.2、配置并且刷新根容器
*/
// java套路:在上一步的createWebApplicationContext()中返回的类型已经是ConfigurableWebApplicationContext类型的了,这里为什么又判断了一下呢?
// 1、有可能有子类重写了createWebApplicationContext()方法,自定义了自己的创建容器的逻辑,返回的容器可能不再是ConfigurableWebApplicationContext类型的了,所以这里要进行判断
// 2、有可能在 this.context 在此方法执行前已经被赋予了值(以JavaConfig的方式配置MVC)
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context; // 强转为 ConfigurableWebApplicationContext 类型
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
ApplicationContext parent = loadParentContext(servletContext); // 点进去可以发现,返回的是null
cwac.setParent(parent); // 根容器的父类设置为null
}
// 配置并且刷新根容器
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
/*
3、将Spring根容器设置到 servletContext 的 Attribute 域中,根容器对应的key为"org.springframework.web.context.WebApplicationContext.ROOT"
*/
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
//...
return this.context;
}// catch
}
1、创建Spring根容器
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
Class<?> contextClass = determineContextClass(sc); //得到根容器的Class类型
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException("..."); //若自定义了Spring根容器,那么此容器类型必须为ConfigurableWebApplicationContext类型,否则抛异常
}
return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); //使用 BeanUtils 实例化 Class
}
protected Class<?> determineContextClass(ServletContext servletContext) {
String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
//若在配置ServletContext时,param name配置了"contextClass"属性,那么就使用其值,进行反射创建,作为Spring根容器
if (contextClassName != null) {
try {
return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
}
}
//若没有配置,则使用默认策略defaultStrategies(使用 XmlWebApplicationContext 作为Spring根容器)
else {
contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
try {
return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
}
}
}
若配置 ServletContext 时,param name配置了contextClass,即可以指定使用我们自定义的Spring根容器: 则会根据param value的值进行反射实例化(一般情况下,我们不会配置)
<context-param>
...
<param-name> contextClass </param-name>
<param-value>com.itcode.springdemo.MyWebApplicationContext</param-value>
</context-param>
若没有配置,则使用默认策略defaultStrategies:使用 ContextLoader.properties 中配置的 XmlWebApplicationContext 类 (ContextLoader.properties文件位于ContextLoader.class源码的同级目录下)
注:ContextLoader.properties 的内容是在何时被读到 “defaultStrategies” 中去的?
ContextLoader 类中,有静态代码块:
private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties"; private static final Properties defaultStrategies; static { // Load default strategy implementations from properties file. // This is currently strictly internal and not meant to be customized by application developers. try { ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class); defaultStrategies = PropertiesLoaderUtils.loadProperties(resource); } //catch (..) {...} }
ContextLoader.properties 文件内容:
org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext
总结:
Spring根容器的创建依据:1、如果在ServletContext 的配置中,配置了contextClass 参数,则使用其配置的值,作为 Spring根容器。(一般情况下我们不会这么做),
2、否则会使用 XmlWebApplicationContext 来作为 Spring根容器(默认都是这样)
2、配置并刷新根容器
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
// 配置根容器的id
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
// 若在配置ServletContext时,param name配置了"contextId"属性,那么就使用其值,作为id。 若没有配置,则默认生成 id...
if (idParam != null) {
wac.setId(idParam);
}
else {
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(sc.getContextPath()));
}
}
// 配置根容器的 ServletContext 属性
wac.setServletContext(sc);
// 配置根容器的 configLocations 属性
// 注:XmlWebApplicationContext在载入BeanDefinition时(后续根容器的刷新阶段),利用的就是此属性。 即在web.xml中配置的"classpath:applictionContext.xml"
String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
if (configLocationParam != null) {
// 若在配置ServletContext时,param name配置了"contextConfigLocation"属性,那么就将其值,设置到根容器的configLocations属性中
wac.setConfigLocation(configLocationParam);
}
// (待....)
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
}
// 初始化自定义配置(待...)
customizeContext(sc, wac);
// 刷新根容器
wac.refresh();
}
总结:
在此阶段完成了对Spring根容器的初始化和刷新 (可以先看一下XmlWebApplicationContext的继承结构图)
依据web.xml中的配置,初始化了根容器(XmlWebApplicationContext)中的一些属性,如:setIdCalled、configLocations(后续刷新阶段载入BeanDefinition的依据)、servletContext
并完成了根容器的刷新
3、添加根容器到servletContext的Attribute域中
将根容器设置到 ServletContext 的 Attribute 域中,根容器对应的key为 “org.springframework.web.context.WebApplicationContext.ROOT”
这样的话,这个根容器就可以被多个子容器所共享
初始化Spring根容器【总结】
0、Spring根容器,是由监听器ContextLoaderListener创建的。创建时机:web应用中的 ServletContext 对象创建完成之后。
1、我们可以在web.xml中配置 ServletContext 时,间接地配置Spring根容器
<context-param>
<!-- contextClass属性:自定义决定Spring根容器-->
<param-name> contextClass </param-name>
<param-value>com.itcode.springdemo.MyWebApplicationContext</param-value>
<!-- contextId属性:配置根容器的id-->
<param-name> contextId </param-name>
<param-value>...</param-value>
<!-- contextConfigLocation属性:设置Spring根容器的BeanDefinition加载路径--> <!--只有此处是常用的-->
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applictionContext.xml</param-value>
</context-param>
2、默认创建的Spring根容器类型为 XmlWebApplicationContext (也可以通过web.xml中在中配置contextClass属性,来指定自定义的Spring根容器(不常用))
3、完成了容器的刷新
3、Spring根容器创建完成后,存放在 ServletContext 域中,key为 “org.springframework.web.context.WebApplicationContext.ROOT”