前言-阅读源码有利于陶冶情操,本文承接前文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()方法

记录总结

  1. ContextLoader在web.xml配置文件中没有指定contextClass的属性下会默认加载XmlWebApplicationContext类,如果指定了contextClass属性,Spring强调指定的contextClass必须是ConfigurableWebApplicationContext的实现类或者子类

  2. web.xml配置文件中如果没有指定contextId属性,则WebApplicationContext的id为org.springframework.web.context.WebApplicationContext:${contextPath},其中${contextPath}是项目的上下文路径,例如localhost:8080/test/info/query路径下的contextPath为/test

  3. web.xml配置的contextConfigLocation属性支持多文件,以,;为分隔符,不指定默认会加载applicationContext.xml,其在后续章节剖析

  4. web.xml倘若指定globalInitializerClasses参数或者contextInitializerClasses参数,具体要求如下

    * 指定的类实现ApplicationContextInitializer<C extends ConfigurableApplicationContext>接口

    * 指定的这些类中的泛型类必须是contextClass(默认为XmlWebApplicationContext)的父类或者一致,否则就会有异常抛出

下节预告

Spring源码情操陶冶-AbstractApplicationContext

Spring源码情操陶冶-ContextLoader的更多相关文章

  1. Spring源码情操陶冶-AbstractApplicationContext

    前言-阅读源码有利于陶冶情操,本文承接前文Spring源码情操陶冶-ContextLoader 约束:本文指定contextClass为默认的XmlWebApplicationContext Abst ...

  2. Spring源码情操陶冶-ContextLoaderListener

    前言:通过实例结合源码的方式解读,其中涉及到的文件来自于博主的Github毕设项目wxServer Note: Springboot应用不在本文章讨论范围 web.xml中启用Spring 在一般的w ...

  3. Spring源码情操陶冶-AbstractApplicationContext#finishBeanFactoryInitialization

    承接前文Spring源码情操陶冶-AbstractApplicationContext#registerListeners 约定web.xml配置的contextClass为默认值XmlWebAppl ...

  4. Spring源码情操陶冶-AbstractApplicationContext#registerListeners

    承接前文Spring源码情操陶冶-AbstractApplicationContext#onRefresh 约定web.xml配置的contextClass为默认值XmlWebApplicationC ...

  5. Spring源码情操陶冶-AbstractApplicationContext#onRefresh

    承接前文Spring源码情操陶冶-AbstractApplicationContext#initApplicationEventMulticaster 约定web.xml配置的contextClass ...

  6. Spring源码情操陶冶-AbstractApplicationContext#initApplicationEventMulticaster

    承接前文Spring源码情操陶冶-AbstractApplicationContext#initMessageSource 约定web.xml配置的contextClass为默认值XmlWebAppl ...

  7. Spring源码情操陶冶-AbstractApplicationContext#initMessageSource

    承接前文Spring源码情操陶冶-AbstractApplicationContext#registerBeanPostProcessors 约定web.xml配置的contextClass为默认值X ...

  8. Spring源码情操陶冶-AbstractApplicationContext#registerBeanPostProcessors

    承接前文Spring源码情操陶冶-AbstractApplicationContext#invokeBeanFactoryPostProcessors 瞧瞧官方注释 /** * Instantiate ...

  9. Spring源码情操陶冶-AbstractApplicationContext#invokeBeanFactoryPostProcessors

    阅读源码有利于陶冶情操,承接前文Spring源码情操陶冶-AbstractApplicationContext#postProcessBeanFactory 约定:web.xml中配置的context ...

随机推荐

  1. js禁止浏览器的回退事件

    直接来个终极方案: 查找了好多资料才找到的,这种方式,可以消除 后退的所有动作.包括 键盘.鼠标手势等产生的后退动作. <script language="javascript&quo ...

  2. (转)java web 学习之路(学习顺序)

    第一步:学习HTML和CSS HTML(超文本标记语言)是网页的核心,学好HTML是成为Web开发人员的基本条件.HTML很容易学习的,但也很容易误用,要学精还得费点功夫. 随着HTML5的发展和普及 ...

  3. CentOS6.8安装python2.7以及XX-Net

    xx-net在ubuntu上运行用起来很方便,如果有一些原因必须用CentOS,麻烦就比较大了. 首先需要升级python版本,CentOS自带的2.6版本是用不了的,必须升级到2.7以上,但是不能用 ...

  4. .NET和JAVA 反射对比

    反射是一个程序集发现及运行的过程,通过反射可以得到*.exe或*.dll等程序集内部的信息.使用反射可以看到一个程序集内部的接口.类.方法.字段.属性.特性等等信息.在System.Reflectio ...

  5. Linux命令 文件备份归档恢复

    cp [功能说明] 文件的备份 英文xxxx  #cp命令将源文件复制到另外安全的地方,复制的文件和源文件是两个相互独立的文件,对认识一个文件的操作不影响另一个文件,但与符号链接文件中的硬链接是有区别 ...

  6. PHP基础入门(四)---PHP数组实用基础知识

    PHP数组 数组是特殊的变量,它可以同时保存一个以上的值. ***关键词:数组基础.数组遍历.超全局数组.数组功能.数组函数. 下面来和大家分享一下有关PHP的数组基础知识,希望对你PHP的学习有所帮 ...

  7. nginx+ftp搭建图片服务器(Windows Server服务器环境下)

    几种图片服务器的对比 1.直接使用ftp服务器,访问图片路径为 ftp://账户:密码@192.168.0.106/31275-105.jpg 不采用这种方式,不安全容易暴露ftp账户信息 2.直接使 ...

  8. Redis的安装与使用(单节点)

    IP:192.168.4.111 环境:CentOS 6.6 Redis版本:redis-3.0 (考虑到Redis3.0在集群和性能提升方面的特性,rc版为正式版的候选版,而且很快就出正式版) 安装 ...

  9. js倒计时函数和(js禁用和恢复a标签的操作)

    <script type="text/javascript"> /*获取手机号*/ var start_time=60; function get_phone_nums ...

  10. JAX-RS REST 服务结果的自动封装

    如转发请注明: 原文luyiisme博客 当使用遵循 JAX-RS 标准的框架开发REST 服务时,我们倾向于定义个(含有JAX-RS)注解接口. 服务器端负责实现该接口,而客户端是该接口的代理进行远 ...