摘要: 本文结合《Spring源码深度解析》来分析Spring 5.0.6版本的源代码。若有描述错误之处,欢迎指正。

经过前面几章的分析,相信大家已经对 Spring 中的容器功能有了简单的了解,在前面的章节中我们一直以 BeanFactory 接口以及它的默认实现类 XmlBeanFactory 为例进行分析,但是, Spring还提供了另一个接口 ApplicationContext,用于扩展 BeanFactory 现有的功能。

ApplicationContext 和 BeanFactory两者都是用于加载 Bean 的,但是相比之下,ApplicationContext 提供了更多的扩展功能,简单一点说: ApplicationContext 包含 BeanFactory 的所有功能。通常建议比 BeanFactory 优先,除非在一些限制的场合,比如字节长度对内存有很大的影响时 ( Applet )。绝大多数“典型的”企业应用 和系统, ApplicationContext 就是你需要使用的。

那么究竟 ApplicationContext 比BeanFactory 多出了哪些功能呢?还需要我们进一步的探索。首先我们来看看使用两个不同的类去加载配置文件在写法上的不同。

  • 使用 BeanFactory 方式加载 XML。
BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("spring/spring-test.xml"));
  • 使用 ApplicationContext 方式加载 XML。
ApplicationContext context = new ClassPathXmlApplicationContext("spring/lookup-test.xml");

同样,我们还是以ClassPathXmlApplicationContext作为切入点,开始对整体功能进行分析。

public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
this(new String[] {configLocation}, true, null);
} public ClassPathXmlApplicationContext(
String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
throws BeansException { super(parent);
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}

设置路径是必不可少的步骤,ClassPathXmlApplicationContext可以将配置文件路径以数组的方式传入,ClassPathXmlApplicationContext可以对数组进行解析并进行加载。而对于解析及功能实现都在refresh()中实现。

一、设置配置路径

ClassPathXmlApplicationContext支持多个配置文件以数组的方式同时传入:

/**
* Set the config locations for this application context.
* <p>If not set, the implementation may use a default as appropriate.
*/
public void setConfigLocations(@Nullable String... locations) {
if (locations != null) {
Assert.noNullElements(locations, "Config locations must not be null");
this.configLocations = new String[locations.length];
for (int i = 0; i < locations.length; i++) {
// 解析给定路径
this.configLocations[i] = resolvePath(locations[i]).trim();
}
}
else {
this.configLocations = null;
}
}

此函数主要用于解析给定的路径数据,当然,如果数组中包含特殊符号,如${var},那么在resolvePath中会搜索匹配的系统变量并替换。

二、扩展功能

设置了路径之后,便可以根据路径做配置文件的解析以及各种功能的实现了。可以说 refresh 函数中包含了几乎 ApnlicationContext 中提供的全部功能,而且此函数中逻辑非常清晰明了,使我们很容易分析对应的层次及逻辑。

@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
// 准备刷新的上下文环境
prepareRefresh(); // Tell the subclass to refresh the internal bean factory.
// 初始化BeanFactory,并进行XML文件读取
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // Prepare the bean factory for use in this context.
// 对BeanFactory进行各种功能填充
prepareBeanFactory(beanFactory); try {
// Allows post-processing of the bean factory in context subclasses.
// 子类覆盖方法做额外的处理
postProcessBeanFactory(beanFactory); // Invoke factory processors registered as beans in the context.
// 激活各种BeanFactory处理器
invokeBeanFactoryPostProcessors(beanFactory); // Register bean processors that intercept bean creation.
// 注册拦截Bean创建Bean处理器,这里只是注册,真正的调用是在getBean的时候
registerBeanPostProcessors(beanFactory); // Initialize message source for this context.
// 为上下文初始化Message源,即不同语言的消息体,国际化处理
initMessageSource(); // Initialize event multicaster for this context.
// 初始化应用消息广播器,并放入applicationEventMulticaster bean中
initApplicationEventMulticaster(); // Initialize other special beans in specific context subclasses.
// 留给子类来初始化其他的Bean
onRefresh(); // Check for listener beans and register them.
// 在所有注册的bean中查找Listener bean,注册到消息广播器中
registerListeners(); // Instantiate all remaining (non-lazy-init) singletons.
// 初始化剩下的单实例(非惰性的)
finishBeanFactoryInitialization(beanFactory); // Last step: publish corresponding event.
// 完成刷新过程,通知生命周期处理器 lifecycleProcessor刷新过程,同时发出ContextRefreshEvent通知别人
finishRefresh();
} catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
} // Destroy already created singletons to avoid dangling resources.
destroyBeans(); // Reset 'active' flag.
cancelRefresh(ex); // Propagate exception to caller.
throw ex;
} finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}

下面概括一下ClassPathXmlApplicationContext初始化的步骤,并从中解释一下它为我们提供的功能。

(1)初始化前的准备工作,例如对系统属性或者环境变最进行准备及验证。

在某种情况下项目的使用需要读取某些系统变量,而这个变量的设置很可能会影响着系统的正确性,那么ClassPathXmlApplicationContext为我们提供的这个准备函数就显得非常必要,它可以在Spring 启动的时候提前对必须的变量进行存在性验证。

( 2 )初始化BeanFactory,并进行XML文件读取。

之前有提到 ClassPathXmlApplicationContext包含着 BeanFactory所提供的一切特征,那么在这一步骤中将会复用BeanFactory 中的配置文件读取解析及其他功能,这一步之后, ClassPathXmlApplicationContext实际上就已经包含了 BeanFactory所提供的功能,也就是可以进行 Bean 的提取等基础操作了。

(3)对 BeanFactory 进行各种功能填充。

@Qualifier与@Autowired 应该是大家非常熟悉的注解,那么这两个注解正是在这一步骤中增加的支持。

(4)子类覆盖方法做额外的处理。

Spring 之所以强大,为世人所推崇,除了它功能上为大家提供了便利外,还有一方面是它的完美架构,开放式的架构让使用它的程序员很容易根据业务需要扩展已经存在的功能。这种开放式的设计在 Spring 中随处可见,例如在本例中就提供了一个空的函数实现 postProcessBeanFactory 来方便程序员在业务上做进一步扩展。

(5)激活各种BeanFactory处理器。

(6)注册拦截 bean 创建的 bean 处理器,这甩只是注册,真正的调用是在 getBean 时候。

(7)为上下文初始化 Message 源,即对不同语言的消息体进行国际化处理。

(8)初始化应用消息广播器,并放入applicationEventMulticaster bean 中。

(9)留给子类来初始化其他的 bean 。

(10)在所有注册的bean中查找Listener bean,注册到消息广播器中。

(11)初始化剩下的单实例(非惰性的)。

(12)完成刷新过程,通知生命周期处理器 lifecycleProcessor刷新过程,同时发出ContextRefreshEvent通知别人。

Spring源码分析(十九)容器的功能扩展概览的更多相关文章

  1. 【spring源码分析】IOC容器初始化(十)

    前言:前文[spring源码分析]IOC容器初始化(九)中分析了AbstractAutowireCapableBeanFactory#createBeanInstance方法中通过工厂方法创建bean ...

  2. 【spring源码分析】IOC容器初始化(总结)

    前言:在经过前面十二篇文章的分析,对bean的加载流程大致梳理清楚了.因为内容过多,因此需要进行一个小总结. 经过前面十二篇文章的漫长分析,终于将xml配置文件中的bean,转换成我们实际所需要的真正 ...

  3. 【spring源码分析】IOC容器初始化(二)

    前言:在[spring源码分析]IOC容器初始化(一)文末中已经提出loadBeanDefinitions(DefaultListableBeanFactory)的重要性,本文将以此为切入点继续分析. ...

  4. 【spring源码分析】IOC容器初始化(三)

    前言:在[spring源码分析]IOC容器初始化(二)中已经得到了XML配置文件的Document实例,下面分析bean的注册过程. XmlBeanDefinitionReader#registerB ...

  5. 【spring源码分析】IOC容器初始化(四)

    前言:在[spring源码分析]IOC容器初始化(三)中已经分析了BeanDefinition注册之前的一些准备工作,下面将进入BeanDefinition注册的核心流程. //DefaultBean ...

  6. 【spring源码分析】IOC容器初始化(七)

    前言:在[spring源码分析]IOC容器初始化(六)中分析了从单例缓存中加载bean对象,由于篇幅原因其核心函数 FactoryBeanRegistrySupport#getObjectFromFa ...

  7. 【spring源码分析】IOC容器初始化——查漏补缺(一)

    前言:在[spring源码分析]IOC容器初始化(十一)中提到了初始化bean的三个步骤: 激活Aware方法. 后置处理器应用(before/after). 激活自定义的init方法. 这里我们就来 ...

  8. Spring源码分析专题 —— IOC容器启动过程(上篇)

    声明 1.建议先阅读<Spring源码分析专题 -- 阅读指引> 2.强烈建议阅读过程中要参照调用过程图,每篇都有其对应的调用过程图 3.写文不易,转载请标明出处 前言 关于 IOC 容器 ...

  9. 【spring源码分析】IOC容器初始化——查漏补缺(五)

    前言:我们知道在Spring中经常使用配置文件的形式对进行属性的赋值,那配置文件的值是怎么赋值到属性上的呢,本文将对其进行分析. 首先了解一个类:PropertySourcesPlaceholderC ...

  10. 【spring源码分析】IOC容器初始化——查漏补缺(二)

    前言:在[spring源码分析]IOC容器初始化(八)中多次提到了前置处理与后置处理,本篇文章针对此问题进行分析.Spring对前置处理或后置处理主要通过BeanPostProcessor进行实现. ...

随机推荐

  1. Effective C++ 避免数组多态

    #include <iostream> #include <cstdlib> using namespace std; class Base { public: int idx ...

  2. Node.js从入门到实战ECMAScript6一页纸总结(很大的一页纸)

    一.ES5/ES6和babel ECMAScript5,即ES5,是ECMAScript的第五次修订,于2009年完成标准化,现在的浏览器已经相当于完全实现了这个标准.ECMAScript6,即ES6 ...

  3. jQuery基础(Ajax,load(),getJSON(),getScript(),post(),ajax(),同步/异步请求数据)

    1.使用load()方法异步请求数据   使用load()方法通过Ajax请求加载服务器中的数据,并把返回的数据放置到指定的元素中,它的调用格式为:   load(url,[data],[callba ...

  4. 关于WebSocket协议

    WebSocket是单个TCP连接上进行全双工通信的协议 在WebSocket的API中,客户端与服务器只需要进行一次握手就可以保持持久的连接,并可以双向传输数据 与HTTP不同的是,WebSocke ...

  5. 浏览器根对象navigator之客户端检测

    Navigator的5个主要属性: appName:Web浏览器的名称 appVersion:浏览器的版本号和其他版本信息 userAgent:浏览器在它的USER-AGENT HTTP标题中发送的字 ...

  6. css动画和jq动画的简单区分

    有很多不怎么用css3写动画的同学经常会对其中css3的transform,transition,translate,animation,@keyframes等等动画属性混淆错乱,经常使用了发现没有效 ...

  7. 深入解析 ERP 计划的各个层次

    ERP 生产计划管理按照 ERP 计划的层次主要分为:经营规划.销售和运作规划.主生产计划.物料需求计划.能力需求计划.执行能力计划.执行物料计划等. 经营规划是企业的战略规划,用于确定企业经营目标和 ...

  8. CSS 样式初始化

    去除浏览器对html的附加样式,避免不同浏览器之间的样式差异,给前端开发提供统一的样式基础.附加样式: .clearfix - 清除浮动 .wordsBreak - 允许文本在任意位置的换行 .ell ...

  9. 【眼见为实】自己动手实践理解数据库READ UNCOMMITED && SERIALIZABLE

    目录 准备工作 ①准备测试表和测试数据 ②关闭数据库事务自动提交 ③设置InnoDB存储引擎隔离级别 [READ UNCOMMITTED] [READ UNCOMMITTED]能解决的问题 [READ ...

  10. 树莓派raspberry Pi2 介绍

    Compared to the Raspberry Pi 1 it has: A 900MHz quad-core ARM Cortex-A7 CPU 1GB RAM Like the (Pi 1) ...