前言-閱讀源碼有利於陶冶情操,本文承接前文Spring源碼情操陶冶-ContextLoaderListener
靜態代碼塊內容
ContextLoader在被主動調用的時候,會執行其的一個靜態塊,代碼如下
static {
// Load default strategy implementations from properties file.
// This is currently strictly internal and not meant to be customized
// by application developers.
try {
//這里DEFAULT_STRATEGIES_PATH值為ContextLoader.properties
ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
//這里的loadProperties方法其實調用的getClass().getResourceAsStream(path)方法
//其是從當前包路徑下查找相應的資源,點開相應的class路徑,果不其然ContextLoader.properties與ContextLoader.class在同一目錄下
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
}
catch (IOException ex) {
throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());
}
}
具體的解析功能見注釋,上面分析到其會讀取ContextLoader.properties,這個文件下只有一個屬性:
org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext
由此可見其最有可能是通過
XmlWebApplicationContext類來進行相應的web上下文初始化
初始化方法initWebApplicationContext
從前文得知,ContextLoaderListener會調用ContextLoader#initWebApplicationContext(ServletContext context)方法來創建web application上下文環境,代碼清單如下
//首先判斷有無org.springframework.web.context.WebApplicationContext.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!");
}
Log logger = LogFactory.getLog(ContextLoader.class);
servletContext.log("Initializing Spring root WebApplicationContext");
if (logger.isInfoEnabled()) {
logger.info("Root WebApplicationContext: initialization started");
}
//用來計算初始化這個過程會耗費多少時間
long startTime = System.currentTimeMillis();
try {
// Store context in local instance variable, to guarantee that
// it is available on ServletContext shutdown.
if (this.context == null) {
//創建方法,一般會創建前點所述的XmlWebApplicationContext
this.context = createWebApplicationContext(servletContext);
}
//XmlWebApplicationContext是ConfigurableWebApplicationContext的實現類,指定的contextClass應該是ConfigurableWebApplicationContext的實現類
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
//一般來說剛創建的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.
//在web.xml中配置了<context-param>的parentContextKey才會指定父級應用
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
//讀取相應的配置並且刷新context對象
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;
}
catch (RuntimeException ex) {
logger.error("Context initialization failed", ex);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
throw ex;
}
catch (Error err) {
logger.error("Context initialization failed", err);
//即使初始化失敗仍不允許有再次的初始化
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
throw err;
}
上述的代碼片段中主要用到了兩個關鍵方法createWebApplicationContext(ServletContext context)和configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac,ServletContext sc),本文必須對這兩個方法進行相應的解讀
ContextLoader#createWebApplicationContext(ServletContext context)
代碼清單如下
//決定采用默認的contextClass還是ServletContext中的參數屬性contextClass
Class<?> contextClass = determineContextClass(sc);
//這里Spring強調指定的contextClass必須是ConfigurableWebApplicationContext的實現類或者子類
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
}
//這里就是實例化指定的contextClass
return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
實現的主要功能為創建ConfigurableWebApplicationContext對象,這里可以稍微瞄一眼determineContextClass(ServletContext context)
//優先從ServletContext取contextClass參數對應的值,即查看是否在web.xml中配置
//對應的contextClass<context-param>參數值
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 {
//倘若不存在,則加載指定的XmlWebApplicationContext類
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);
}
}
ContextLoader#configureAndRefreshWebApplicationContext()
代碼清單如下
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
//獲取servletContext中的contextId屬性
String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
if (idParam != null) {
//存在則設為指定的id名
wac.setId(idParam);
}
else {
// Generate default id... 一般為org.springframework.web.context.WebApplicationContext:${contextPath}
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(sc.getContextPath()));
}
}
wac.setServletContext(sc);
//讀取contextConfigLocation屬性
String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
if (configLocationParam != null) {
//設置指定的spring文件所在地,支持classpath前綴並多文件,以,;為分隔符
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
//獲得真實對象為StandardEnvironment 其非ConfigurableWebEnvironment的實現類
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
}
//查看是否有ApplicationContextInitializer<C extends ConfigurableApplicationContext>啟動類,其實是web.xml需要指定globalInitializerClasses參數或者contextInitializerClasses參數,前提是指定的這些類的泛型類必須是wac的父類或者與wac類相同,否則就會有異常拋出
customizeContext(sc, wac);
//調用AbstractApplicationContext的refresh方法實現加載spring文件等操作
wac.refresh();
}
可見關鍵處理還是在AbstractApplicationContext#refresh()方法,前面都是refresh()方法的准備工作,包括指定contextConfigLocationSpring配置文件位置、給應用一個id倘若指定了contextId屬性、如果指定了globalInitializerClasses或者contextInitializerClasses參數則調用其中的initialize()方法
記錄總結
ContextLoader在web.xml配置文件中沒有指定
contextClass的屬性下會默認加載XmlWebApplicationContext類,如果指定了contextClass屬性,Spring強調指定的contextClass必須是ConfigurableWebApplicationContext的實現類或者子類web.xml配置文件中如果沒有指定
contextId屬性,則WebApplicationContext的id為org.springframework.web.context.WebApplicationContext:${contextPath},其中${contextPath}是項目的上下文路徑,例如localhost:8080/test/info/query路徑下的contextPath為/testweb.xml配置的
contextConfigLocation屬性支持多文件,以,;為分隔符,不指定默認會加載applicationContext.xml,其在后續章節剖析web.xml倘若指定globalInitializerClasses參數或者contextInitializerClasses參數,具體要求如下
* 指定的類實現ApplicationContextInitializer<C extends ConfigurableApplicationContext>接口
* 指定的這些類中的泛型類必須是contextClass(默認為XmlWebApplicationContext)的父類或者一致,否則就會有異常拋出
下節預告
Spring源碼情操陶冶-AbstractApplicationContext
