2019独角兽企业重金招聘Python工程师标准>>>

一、上下文加载器

要在项目中使用Spring框架,需要在web.xml做如下配置:

<!--contextConfigLocation在 ContextLoaderListener类中的默认值是 /WEB-INF/applicationContext.xml-->
<context-param><param-name>contextConfigLocation</param-name><param-value>/WEB-INF/applicationContext.xml</param-value><!-- <param-value>classpath:applicationContext*.xml</param-value> -->
</context-param>
<listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

以下为类ContextLoaderListener的声明:

public class ContextLoaderListener extends ContextLoader implements ServletContextListener{// 上下文加载器private ContextLoader contextLoader;// 无参构造放public ContextLoaderListener() {}// 带参构造函数public ContextLoaderListener(WebApplicationContext context){super(context);}// ......其它代码
}

ServletContextListener是Servlet API中的一个接口,它能够坚挺ServletContext对象的生命周期,也就是监听整个Web应用的生命周期。在Servlet容器启动时,会触发ServletContextEvent事件,这个事件就由ServletContextListener来处理。在ServletContextListener接口定义了两个处理ServletContextEvent事件的方法,分别是Web应用初始化时的contextInitialized方法和Web应用销毁时contextDestroyed方法。

/*** Initialize the root web application context.*/
public void contextInitialized(ServletContextEvent event) {this.contextLoader = createContextLoader();if (this.contextLoader == null) {this.contextLoader = this;}this.contextLoader.initWebApplicationContext(event.getServletContext());
}

上面的代码是Spring的ContextLoaderListener中覆写的初始化监听方法,所有支撑Spring工作的初始化工作,都在这个方法中完成。在这个方法中,第一行通过createContextLoader()创建一个上下文的加载器,不过在Spring中,这个方法的实现仅仅返回了一个null,同时注释中提示,它可以被子类覆写。另外一方面,这个方法已经过时。所以,整个初始化工作的关键,就是最后一行代码:

this.contextLoader.initWebApplicationContext(event.getServletContext());

由于createContextLoader()方法的不作为,实际上this.contextLoader就是对象本身的this。也就是说,如果没有覆写createContextLoader方法,那么默认的上下文加载器就会是ContextLoaderListener自身。

二、Web应用上下文

至于initWebApplicationContext方法,ContextLoaderListener本身并没有实现,而是它继承自ContextLoader的一个方法,源码如下:

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {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【将context保存在一个实例变量中,以保证】// it is available on ServletContext shutdown.                【在ServletContext关闭时可用。】if (this.context == null) {this.context = createWebApplicationContext(servletContext);}if (this.context instanceof ConfigurableWebApplicationContext) {configureAndRefreshWebApplicationContext((ConfigurableWebApplicationContext)this.context, 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;}}

这个方法的作用,主要是初始化Web应用的环境上下文context,ContextLoader持有这样一个成员变量,类型为WebApplicationContext。在这个方法中,首先判断是否已经存在了一个上下文加载器,如果已经存在而又去初始化加载器,就会抛出一个IllegalStateException异常,提示无法完成上下文的初始化,要求检查是否在web.xml中配置了多个上下文加载器。如果没有抛出这个异常,接下来就要做一些准备工作,比如打印日志,记录时间戳,但这些都不是这个方法的重点。

if (this.context == null) {this.context = createWebApplicationContext(servletContext); // ①稍后回来
}

上面的代码是创建Web应用上下文的关键点,我们先在这里打上一个标记。方法createWebApplicationContext的源码如下:

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() + "]");}ConfigurableWebApplicationContext wac =(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);return wac;
}

第一行代码用于决定使用何种方式创建环境上下文context,比如注解方式创建上下文或者XML配置方式创建上下文,这两种方式分别对应了AnnotationConfigWebApplicationContext和XmlWebApplicationContext类,它们都实现了ConfigurableWebApplicationContext接口。显然,我们需要关注determineContextClass(sc)究竟返回了一个什么样的创建方式对应的类。

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);}}
}

观察源码发现,上下文类的获取方式有两种,一是从ServletContext中获取初始化参数CONTEXT_CLASS_PARAM(取值为contextClass),这个可以通过在web.xml中配置context节点注入参数(存疑);另一种是从defaultStrategies获取默认的上下文类。我们先来关注下,默认的应用上下文是什么。defaultStrategies是Properties类的一个实例,Properties类则继承了HashMap。所以,Properties.getProperty相当于HashMap.get。同时defaultStrategies也是ContextLoader的一个静态成员变量。既然我们要从defaultStrategies中get一些属性,就需要确认defaultStrategies的初始化位置。

在ContextLoader声明defaultStrategies变量之后,紧接着就是对该变量的初始化代码,如下:

/*** Name of the class path resource (relative to the ContextLoader class)* that defines ContextLoader's default strategy names.*/
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 (IOException ex) {throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());}
}

从上面可以看出,defaultStrategies默认是使用了一个属性文件进行填充,至于ClassPathResource类和PropertiesLoaderUtils以及PropertiesLoaderUtils的loadProperties方法,这里就不继续跟下去了,否则又是一个没完没了的封装。我们直接来看看,是哪个属性文件保存着默认的上下文类。这里有一个DEFAULT_STRATEGIES_PATH变量,取值是ContextLoader.properties,它的位置其实已经通过注释给出了:class path资源文件的名称(相对于ContextLoader类),它定义了上下文加载器的默认策略。我们可以很轻松的在spring-web.jar中org.springframework.web.context包下找到这个ContextLoader.properties文件,打开可以看到它的内容如下:

# Default WebApplicationContext implementation class for ContextLoader.
# Used as fallback when no explicit context implementation has been specified as context-param.
# Not meant to be customized by application developers.org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext

所以,绕了这么大个圈子,spring默认就是使用XML配置的方式创建并初始化上下文环境。

再回到createWebApplicationContext方法中,由之前一系列跟踪我们知道,contextClass默认是XmlWebApplicationContext类,至于创建这个类的实例的工作,也就是创建真正的应用环境上下文的工作,就由下面的代码完成:

ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

​BeanUtils.instantiateClass是一个基于反射的工具类方法,它根据传入的类型和参数,构造该类型的一个实例。

至此,环境上下文就创建完成了,我们也可以回到之前标记的位置①处,继续跟踪其后的代码。

版权声明:本文为博主原创文章,未经博主允许不得转载。

转载于:https://my.oschina.net/treenewbee/blog/489934

跟踪Spring源码(一)相关推荐

  1. Spring源码剖析——Bean的配置与启动

    IOC介绍   相信大多数人在学习Spring时 IOC 和 Bean 算得上是最常听到的两个名词,IOC在学习Spring当中出现频率如此之高必然有其原因.如果我们做一个比喻的话,把Bean说成Sp ...

  2. 我该如何学习spring源码以及解析bean定义的注册

    如何学习spring源码 前言 本文属于spring源码解析的系列文章之一,文章主要是介绍如何学习spring的源码,希望能够最大限度的帮助到有需要的人.文章总体难度不大,但比较繁重,学习时一定要耐住 ...

  3. spring源码学习之路---深入AOP(终)

    作者:zuoxiaolong8810(左潇龙),转载请注明出处,特别说明:本博文来自博主原博客,为保证新博客中博文的完整性,特复制到此留存,如需转载请注明新博客地址即可. 上一章和各位一起看了一下sp ...

  4. Spring源码解析 -- SpringWeb请求参数获取解析

    Spring源码解析 – SpringWeb请求参数获取解析 简介 在文章:Spring Web 请求初探中,我们看到最后方法反射调用的相关代码,本篇文章就探索其中的参数是如何从请求中获取的 概览 方 ...

  5. Spring源码解析 -- SpringWeb请求映射Map初始化

    简介 在上篇文章中,大致解析了Spring如何将请求路径与处理方法进行映射,但映射相关的初始化对于我们来说还是一团迷雾 本篇文章就来探索下,请求路径和处理方法的映射,是如何进行初始化的 概览 基于上篇 ...

  6. Spring源码学习笔记:经典设计模式之代理模式

    1.博客内容均出自于咕泡学院架构师第三期 2.架构师系列内容:架构师学习笔记(持续更新) 0.代理模式(Proxy Pattern) 指为其他对象提供一种代理,以控制对这个对象的访问.代理对象在客户端 ...

  7. Spring 源码分析(三) —— AOP(五)创建代理

    2019独角兽企业重金招聘Python工程师标准>>> 创建代理 代理的定义其实非常简单,就是改变原来目标对象方法调用的运行轨迹.这种改变,首先会对这些方法进行拦截,从而为这些方法提 ...

  8. Spring 源码分析(三) —— AOP(二)Spring AOP 整体架构

    2019独角兽企业重金招聘Python工程师标准>>> Spring AOP 架构         先是生成代理对象,然后是拦截器的作用,最后是编织的具体实现.这是AOP实现的三个步 ...

  9. 转 Spring源码剖析——核心IOC容器原理

    Spring源码剖析--核心IOC容器原理 2016年08月05日 15:06:16 阅读数:8312 标签: spring 源码 ioc 编程 bean 更多 个人分类: Java https:// ...

最新文章

  1. python使用正则表达式统计字符串中出现次数最多的数字
  2. 解决Sublime包管理package control 报错 There are no packages available for installation
  3. 10、查看索引(SHOW INDEX)
  4. Redis基础知识点总结
  5. 时间序列的异常值处理
  6. Hat’s Words
  7. 没有基础学python_python没有基础好学吗
  8. 网页上线后音频不能自动播放
  9. 从底层分析c和类c语言
  10. 分布式任务队列--Celery的学习笔记
  11. margin和padding的区别和用法
  12. js校验明细列表字段是否存在相同值(js循环嵌套初始值问题)
  13. css 选择器的应用
  14. iframe中加载html页面,jQuery - 动态创建iframe并加载页面
  15. matlab解决线性规划问题
  16. 【Matlab元胞自动机】元胞自动机双边教室疏散【含源码 1208期】
  17. 腾讯云云服务器迁移服务相关问题
  18. 设计模式——策略模式( Strategy Pattern )
  19. 文心一格x网易「EVE宇宙航母预研」主题AI绘画大赛即将开启!
  20. 模电—初探MOSFET

热门文章

  1. java arraylist下标从几开始_ArrayList——JAVA成长之路
  2. 华为商城抢手机脚本_抢在华为前,三星发布了折叠屏手机!价格贵到哭…
  3. vot2016_toolkits测试工具包调试成功示例
  4. Cornerstone的无限期破解方法
  5. 向日葵android客户端,向日葵3.1客户端控制手机使用教程
  6. tplink软件升级有用吗_TP-LINK路由器软件升级图解教程
  7. 我们离得开Spring框架吗?
  8. linux上使用glx例子,c++ - X11 / GLX - 全屏模式? - 堆栈内存溢出
  9. 关于数据库的一些基本概念
  10. 手动更新mac_如何手动和自动调整Mac的屏幕亮度