Spring源码阅读之WebapplicationContext初始化
当一个Web应用在服务器(tomcat)中启动的时,tomcat需要给该应用创建一个ServletContext对象作为公共容器保存了应用中的配置信息。这些配置信息是通过读取web.xml文件中的标签来获取的。
应用在tomcat中启动时,首先会读取web.xml中两个标签节点<context-param>、<listener>
并创建ServletContext对象,将context-param
标签中配置文件的位置通过键值对的方式存入该对象中。以后可以通过getInitParameter("")
的方式来获取(webapplicationContext的初始化/上下文环境的刷新正是使用这种方法来获取配置文件)
从WebApplicationContext中可以获得ServletContext的引用,整个Spring应用上下文对象将作为属性放置到ServletContext中,以便Web应用环境可以访问Sping应用上下文。
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/applicationContext*.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
全局的ServletContext
对象创建完成之后,接下来就需要对Spring的IOC容器进行初始化,并配置其上下文环境对象WebApplicationContext
。
将配置文件的信息存入ServletContext对象后,接着读取listener
标签并创建ContextLoaderListener
对象,该对象继承了ContextLoader
和实现了ServletContextListener
接口。ServletContextListener
是ServletContext的监听者,用来监听对象的创建和销毁,并执行相应的方法。
ContextLoaderListener
的作用就是读取配置文件的信息并存入webApplicationContext对象中。由于继承自ContextLoader
,所以这一过程由ContextLoader
完成。
源码解释:
ContextLoaderListener
对象创建的时候会执行contextInitialized
来完成初始化工作,具体的初始化流程在ContextLoader
中的initWebApplicationContext
方法。
public void contextInitialized(ServletContextEvent event) {
this.initWebApplicationContext(event.getServletContext());
}
initWebApplicationContext
创建并初始化Context对象。
private WebApplicationContext context;
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
//首先判断该sercontext对象中是否已经有WebApplicationContext对象,如果有表明已经初始化过了,就会报错。
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!");
} else {
try {
if (this.context == null) {
//如果WebApplicationContext没有被初始化过,通过该方法创建该对象。
this.context = this.createWebApplicationContext(servletContext);
}
/*如果创建的WebApplicationContext对象是ConfigurableWebApplicationContext的一个实例(有继承关系)。
那么就会将创建的对象强制转型为ConfigurableWebApplicationContext*/
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext)this.context;
//对象是否处于活跃状态
if (!cwac.isActive()) {
//是否有父环境对象
if (cwac.getParent() == null) {
//将ServletContext设置为父环境对象
ApplicationContext parent = this.loadParentContext(servletContext);
cwac.setParent(parent);
}
//通过servletContext中的配置信息来刷新ConfigurableWebApplicationContext对象环境
this.configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
//将创建并初始化完成的WebApplicationContext对象保存进ServletContext中。
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
}
}
首先看是如何创建的,并且创建的具体对象是什么。通过defaultStrategies
对象来得知实际创建的类名称,并实例化该类,该对象在静态代码块中初始化的,并通过加载ContextLoader.properties
文件完成初始化过程。最后实际创建的对象为XmlWebApplicationContext
。
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
//通过determineContextClass来得到具体类
Class<?> contextClass = this.determineContextClass(sc);
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException("Custom context class [" + contextClass.getName() + "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
} else {
return (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);
}
}
protected Class<?> determineContextClass(ServletContext servletContext) {
//读取ServletContext中的contextClass的值。一般来说web.xml并不会配置该信息。
String contextClassName = servletContext.getInitParameter("contextClass");
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来得到具体创建的类名。
contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
try {
//通过类的名称得到该类Class对象。
return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
} catch (ClassNotFoundException var5) {
throw new ApplicationContextException("Failed to load default context class [" + contextClassName + "]", var5);
}
}
}
//通过静态代码块来初始化defaultStrategies对象。
static {
try {
//加载资源文件ContextLoader.properties(相对路径)
ClassPathResource resource = new ClassPathResource("ContextLoader.properties", ContextLoader.class);
//通过配置文件类初始化defaultStrategies对象。
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
//可以看到XmlWebApplicationContext设置了一些默认的值,如果在web.xml中没有指定<context-param>标签那么默认会读取该类中指定的文件。
public class XmlWebApplicationContext extends AbstractRefreshableWebApplicationContext {
public static final String DEFAULT_CONFIG_LOCATION = "/WEB-INF/applicationContext.xml";
public static final String DEFAULT_CONFIG_LOCATION_PREFIX = "/WEB-INF/";
public static final String DEFAULT_CONFIG_LOCATION_SUFFIX = ".xml";
}
实际的创建过程了解之后,context对象如何读取我们指定的配置文件的呢?
首先了解一下webApplicationContenxt的继承关系:
通过图片得知XmlWebApplicationContext
继承AbstractRefreshableWebApplicationContext
,AbstractRefreshableWebApplicationContext
实现ConfigurableWebApplicationContext
接口,该接口继承了WebApplicationContext
接口。
再回到initWebApplicationContext
中这段代码:
if (this.context instanceof ConfigurableWebApplicationContext) {
/*如果创建的WebApplicationContext对象是ConfigurableWebApplicationContext的一个实例(有继承关系)。
那么就会将创建的对象强制转型为ConfigurableWebApplicationContext*/
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext)this.context;
//对象是否处于活跃状态
if (!cwac.isActive()) {
//是否有父环境对象
if (cwac.getParent() == null) {
//将ServletContext设置为父环境对象
ApplicationContext parent = this.loadParentContext(servletContext);
cwac.setParent(parent);
}
//通过servletContext中的配置信息来刷新ConfigurableWebApplicationContext对象环境
this.configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
//将创建并初始化完成的WebApplicationContext对象保存进ServletContext中。
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
通过创建过程了解到实际创建的是XmlWebApplicationContext
对象,该对象和ConfigurableWebApplicationContext
是存在继承关系的。因此这里会发生强制转型,通过configureAndRefreshWebApplicationContext
来初始化或者刷新context对象的上下文环境。
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()));
}
}
//设置ServletContext环境。
wac.setServletContext(sc);
/*读取Servlet对象中的contextConfigLocation. ServletContext在创建的时候就读取了
<context-param>标签,并将该标签中的配置文件的信息通过键值对的形式保存进来了。
*/
configLocationParam = sc.getInitParameter("contextConfigLocation");
if (configLocationParam != null) {
//保存配置文件信息的位置。
wac.setConfigLocation(configLocationParam);
}
//刷新环境
wac.refresh();
}
读取servletContext对象中contextConfigLocation
的值(配置文件的位置)保存进configureAndRefreshWebApplicationContext
对象中,并刷新上下文环境。
SpringIOC容器先根据监听初始化WebApplicationContext,然后再初始化web.xml中其他配置的servlet(DispatcherServlet),为其初始化自己的上下文信息servletContext,并加载其设置的配置信息和参数信息到该上下文中,将WebApplicationContext设置为它的父容器。所以最后的关系是ServletContext包含了WebApplicationContext,WebApplicationContext包含了其他的Servlet上下文环境。(DispatcherServlet),为其初始化自己的上下文信息servletContext,并加载其设置的配置信息和参数信息到该上下文中,将WebApplicationContext设置为它的父容器。所以最后的关系是ServletContext包含了WebApplicationContext,WebApplicationContext包含了其他的Servlet上下文环境。
参考:https://www.jianshu.com/p/f8d560962a68