Spring WebApplicationContext
基于Spring4.0.5
ContextLoaderListener
继承了ContextLoader
同时实现了ServletContextListener
接口,Spring通过ContextLoaderListener
在ServletContext的两个生命周期函数contextInitialized()
和contextDestroyed()
对WebApplicationContext
进行创建和销毁.
/**
* Initialize the root web application context.
*/
@Override
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}
/**
* Close the root web application context.
*/
@Override
public void contextDestroyed(ServletContextEvent event) {
closeWebApplicationContext(event.getServletContext());
ContextCleanupListener.cleanupAttributes(event.getServletContext());
}
initWebApplicationContext()
是创建WebApplicationContext
的核心函数.首先在ServletContext
里通过WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
这个key尝试在ServletContext
里获取WebApplicationContext
,若成功获取则代表已经进行过初始化,所以会抛出一个IllegalStateException
.
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!");
}
接下来是获得WebApplicationContext
的实例并保存本地引用和放入ServletContext
中.由于WebApplicationContext
只是一个接口,实际上我们获取的是此接口的实现类的实例,而具体是获取什么实现类的逻辑则放在了createWebApplicationContext()
中.先继续分析本方法的逻辑,在获得context
实例后从第7行到第21行都是根据相关判断对context
进行一些初始化工作.这里提一下,从第12行到17行的代码,对于单纯的Web应用来讲是不需要关心的,这个是针对EAR的设置,如果不是EJB开发,这段可以忽略掉,在loadParentContext()
的注释里有说明这一点.然后重点在第18行的configureAndRefreshWebApplicationContext()
,这里是对WebApplicationContext
进行初始化的核心函数,我们稍后分析.这段逻辑结束后,把已经初始化结束的WebApplicationContext
放入了servletContext
中,key为WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
.
然后在第23行到第29行,这里是为了解决一个Spring类库的位置问题.在Tomcat中,假设你的Web应用都是使用了Spring,而且又都是同一个版本,那你可以把jar包放在Tomcat目录的lib文件夹里,Tomcat的Common ClassLoader在默认配置下会读取并加载此文件夹里的jar包,从而使所有的Web应用都能使用这些类库,详细的Tomcat加载类库的内容可以去搜索相关资料查阅,这里不再多提.回到原题,如果是这种情况,Spring的类是由Common ClassLoader加载的,但是具体的Web应用的类却是由Tomcat实例为每个Web应用创建的ClassLoader加载的.根据JVM ClassLoader的双亲委派模型,由Common ClassLoader载入的Spring类是无法获得由WebApp ClassLoader载入的相关应用类的.所以这里Spring使用了一个线程上下文ClassLoader,确保不管Spring类库放在Web应用下还是放在Tomcat的lib下,都能成功获取相关Web应用的业务类.在Web应用里,线程上下文ClassLoader在默认情况下就是Web应用的ClassLoader,自然能够获得Web应用下的各种类.然后到此WebApplicationContext
的创建过程结束.
try {
// Store context in local instance variable, to guarantee that
// it is available on ServletContext shutdown.
if (this.context == null) {
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent ->
// determine parent for root web application context, if any.
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
if (ccl == ContextLoader.class.getClassLoader()) {
currentContext = this.context;
}
else if (ccl != null) {
currentContextPerThread.put(ccl, this.context);
}
if (logger.isDebugEnabled()) {
logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
}
if (logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
}
return this.context;
}
接下来我们先分析createWebApplicationContext()
函数,这里的逻辑很简答,通过determineContextClass()
获得contextClass
并且判断是否为ConfigurableWebApplicationContext
的实现类,是则返回,否则抛出异常.而在determineContextClass()
里,先判断开发者是否有设置contextClass
参数,有的话根据参数使用自定义的上下文类,否则就使用默认的上下文类.目前Spring是通过在ContextLoader.properties
里设置默认的上下文类,此文件包含在org.springframework.web.context
包下,设置了XmlWebApplicationContext
为默认的上下文类.而ContextLoader.properties
的载入代码则是被写在ContextLoader
的静态代码块中.
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
Class<?> contextClass = determineContextClass(sc);
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
}
return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}
protected Class<?> determineContextClass(ServletContext servletContext) {
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 {
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);
}
}
}
接下来我们分析初始化WebApplicationContext
的核心函数configureAndRefreshWebApplicationContext()
,从第2行到第14行是对上下文的id
作一个设置,比较简单.然后第16行到20行,设置应用上下文类的ServletContext
并根据contextConfigLocation
参数设置应用上下文的ConfigLocation
,如果没设置的话,根据默认配置,XmlWebApplicationContext
的ConfigLocation
是/WEB-INF/applicationContext.xml
,所以为什么大家默认都把Spring的配置文件这样命名并放在WEB-INF
下就能解释了.在设置ConfigLocation
的同时还设置好了Environment
,例如Java自带的systemProperties
和systemEnvironment
.然后第24行到28行对Environment
的PropertiesSources
进行初始化,这里是设置了ServletContext
进Environment
的propertiesSourcesList
里.然后customizeContext()
里面主要是执行配置了的ApplicationContextInitializer
的initialize()
,对ApplicationContext
进行一些自定义的操作,在Web应用下自然是WebApplicationContext
.(这里提一下,单纯的SpringMVC貌似这里没有任何作用,如果开发者自己没有编写ApplicationContextInitializer
的话,但是在SpringBoot的类库里则有用到这个特性.)
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
// The application context id is still set to its original default value
// -> assign a more useful id based on available information
String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
if (idParam != null) {
wac.setId(idParam);
}
else {
// Generate default id...
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(sc.getContextPath()));
}
}
wac.setServletContext(sc);
String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
if (configLocationParam != null) {
wac.setConfigLocation(configLocationParam);
}
// The wac environment's #initPropertySources will be called in any case when the context
// is refreshed; do it eagerly here to ensure servlet property sources are in place for
// use in any post-processing or initialization that occurs below prior to #refresh
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
}
customizeContext(sc, wac);
wac.refresh();
}
接下来重头戏来了,refresh()
里面才是真正通过配置进行Spring IOC容器的初始化和加载代码,包括了BeanFactory
的创建,BeanFactoryPostProcesser
和BeanPostProcesser
的注册和执行(这两个我的理解是一个hook,对BeanFactory
和Bean
的创建执行进行自定义操作),然后还有下面各种MessageSource
,EventMuticaster
,Listener
的初始化,最后是单例Bean
的初始化和refresh()
的一些收尾工作,这里面初始化了LifecycleProcessor
,这个Spring有提供一个默认的实现类,用于管理有生命周期的Bean
,单例的则在前面已经被生成了并且不需要做声明周期的管理,所以这里如果开发者有需要还可以自定义LifecycleProcessor
来管理Bean
的声明周期.然后发布ContextRefreshedEvent
,最后还有个LiveBeansView.registerApplicationContext(this)
貌似是提供查看当前应用上下文里存活的Bean
的.当前版本这个功能还处于beta状态,是设计给Spring Tool Suite 3.1及更高版本使用的.
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
}
}
到这里,WebApplicationContext
的创建和初始化就结束了,至于销毁部分就不详细解释了,代码比较少(其实是我懒= =).源码阅读完后收获还是挺大的,了解了Spring IOC容器是怎么切入到Web应用中,并且有提供了哪些地方可以hook进行自定义操作的,还有Spring IOC容器的初始化流程等等.
阅读源码过程中查阅了不少网上的资料,包括各种个人博客以及官方文档,因为自己没有做记录也就没罗列出来.本文如有不妥之处,希望大家能提出宝贵的意见.
网友评论