概述:

Spring 对于Java 开发来说,以及算得上非常基础并且核心的框架了,在有一定开发经验后,阅读源码能更好的提高我们的编码能力并且让我们对其更加理解。俗话说知己知彼,百战不殆。当你对Spring 掌握熟透之后,也就没什么能过阻拦你在开发路上前进了。

IOC 总体来说有两处地方最重要,一个是创建Bean容器,一个是初始化。在本文中,主要为大家讲解了 IOC Bean 容器创建过程。后续将会补上初始化部分的知识。

为了保持文章的严谨性,如果读者发现我哪里说错了请一定不吝指出,非常希望可以听到读者的声音。同时能过纠正自己的误解。

本文主要采用了ClassPathXmlApplication作为Spring IOC 容器,从 ClassPathXmlApplication 分析容器创建的过程,首先来看一下ClassPathXmlApplication 的依赖关系:

首先我们先来观察一下核心主干:

我们可以通过对ClassPathXmlApplicationContext 的父类名称,了解其主要功能:

  • DefaultResourceLoader:提供获取配置文件方法 getResource(),返回资源信息 Resource
  • AbstractApplicationContext:提供主要创建容器,初始化对象方法 refresh()
  • AbstractRefreshableApplicationContext: 提供刷新 refreshBeanFactory() 方法,进行BeanFactory 的初始化。
  • AbstractRefreshableConfigApplicationContext:保存配置文件信息。
  • AbstractXmlApplicationContext:提供 loadBeanDefinitions() 读取BeanDefinitions方法,将资源转换为配置。
  • ClassPathXmlApplicationContext:具体实现类,用于定位资源文件。

通过上述主要类的分析,相信大家对Spring IOC 的初始化有了大概的认识。

简单来说,Spring IOC 容器创建的具体流程如下:

我们将图片对应到类的调用,具体步骤为:

1、ClassPathXmlApplicationContext -> 通过构造器,读取XML配置文件地址信息

2、AbstractApplicationContext -> refresh() 初始化容器

3、AbstractRefreshableApplicationContext -> BeanFactory 初始化

4、AbstractXmlApplicationContext -> loadBeanDifinition 读取资源信息(加载Bean)

5、XmlBeanDefinitionReader -> 将资源文件解析成 BeanDefinition

6、DefaultBeanDefinitionDocumentReader -> 将BeanDefinition 注册到BeanFactory 中

由于Spring IOC部分源码主要包括了:容器创建以及Bean 初始化两部分。在本文内容中,主要介绍一下,容器创建的过程!

1、容器实例

ApplicationContext.xml 配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="User" class="com.charles.business.model.User">
<property name="username" value="jaycekon"/>
<property name="phone" value="1881412***"/>
</bean>
</beans>

代码启动入口:

public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("ApplicationContext.xml");
User user = (User) applicationContext.getBean("User");
Assert.notNull(user, "容器初始化异常!");
logger.info("初始化结果:{}", JSONObject.toJSONString(user));
}

接下来我们根据ClassPathXmlApplication 的依赖图进行逐一分析每一个过程。

2、ClassPathXmlApplicationContext

核心方法:

org.springframework.context.support.ClassPathXmlApplicationContext#ClassPathXmlApplicationContext(java.lang.String[], boolean, org.springframework.context.ApplicationContext)

从上图可以看出,ClassPathXmlApplicationContext 类中并没有提供什么特别的方法,除构造器外,只有一个获取Resource 的方法(与之相似的 AbstractRefreshableConfigApplicationContext 中提供类获取资源定位的方法),主要用于保存资源信息。

ClassPathXmlApplicationContext 中的主要构造方法:

public ClassPathXmlApplicationContext(
String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
throws BeansException {
// 调用父类构造方法
super(parent);
// AbstractRefreshableConfigApplicationContext 具体实现,保存资源定位信息
setConfigLocations(configLocations);
if (refresh) {
// AbstractApplicationContext 具体实现,初始化容器与Bean
refresh();
}
}

可以看到,ClassPathXmlApplicationContext 这个类相当简单,主要作用:

  • Resource[] configResources:将配置文件作为资源都存放到这个数组中
  • setConfigLocations():保存配置文件定位信息(AbstractRefreshableConfigApplicationContext)
  • refresh():启动容器初始化核心方法(AbstractApplicationContext)

3、AbstractApplicationContext

核心方法:

org.springframework.context.support.AbstractApplicationContext#refresh

AbstractApplicationContext 是Spring IOC 容器中核心类,对父类,接口提供了具体实现,其中的方法非常丰富,在这里我们主要介绍其中的核心方法 refresh()。 这里简单说下为什么是 refresh(),而不是 init() 这种名字的方法。因为 ApplicationContext 建立起来以后,其实我们是可以通过调用 refresh() 这个方法重建的,这样会将原来的 ApplicationContext 销毁,然后再重新执行一次初始化操作。

public void refresh() throws BeansException, IllegalStateException {
//对容器初始化进行加锁操作,比免在创建的同时,重复操作。
synchronized (this.startupShutdownMonitor) {
// 设置容器初始化世界,表示容器现在isActive,初始化上下文环境中的任何占位符属性源
prepareRefresh(); // 核心步骤,主要功能是:让子类进行BeanFactory 初始化,并且将Bean信息 转换为BeanFinition,最后注册到容器中
// 当然,这里说的 Bean 还没有初始化,只是配置信息都提取出来了
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // 设置 BeanFactory 的类加载器,添加几个 BeanPostProcessor,手动注册几个特殊的 bean
prepareBeanFactory(beanFactory); try {
// 后续步骤将在下次进行分析,本文主要核心为上面几个步骤
...
}
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();
}
}
}

从AbstractApplicationContext 中的refresh()我们可以粗略的领会到,Spring IOC 的核心流程,基本在这里完成的,从容器创建,资源解析,Bean创建等一系列步骤。都是由这个方法进行控制。

在本文中,我们主要关心的一个点就是:

ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

通过上图,我们可以直观的了解到ApplicationContext 与BeanFactory 之间的关系。这里创建的 ConfigurableListableBeanFactory 实际包含了BeanFactory 三个分支的绝大部分功能。

1、ApplicationContext 继承了 ListableBeanFactory,这个 Listable 的意思就是,通过这个接口,我们可以获取多个 Bean,最顶层 BeanFactory 接口的方法都是获取单个 Bean 的。

2、ApplicationContext 继承了 HierarchicalBeanFactory,Hierarchical 单词本身已经能说明问题了,也就是说我们可以在应用中起多个 BeanFactory,然后可以将各个 BeanFactory 设置为父子关系。

3、AutowireCapableBeanFactory 这个名字中的 Autowire 大家都非常熟悉,它就是用来自动装配 Bean 用的,但是仔细看上图,ApplicationContext 并没有继承它,不过不用担心,不使用继承,不代表不可以使用组合,如果你看到 ApplicationContext 接口定义中的最后一个方法 getAutowireCapableBeanFactory() 就知道了。

4、ConfigurableListableBeanFactory 也是一个特殊的接口,看图,特殊之处在于它继承了第二层所有的三个接口,而 ApplicationContext 没有。这点之后会用到。

我们来看一下 obtainFreshBeanFactory() 方法的主要内容:

protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
//AbstractRefreshableApplicationContext 中实现,主要用于刷新BeanFactory,加载 Bean 定义、注册 Bean 等等
refreshBeanFactory();
//获取上述步骤中 创建好的 BeanFactory
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (logger.isDebugEnabled()) {
logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
}
return beanFactory;
}

4、AbstractRefreshableApplicationContext

核心方法:

org.springframework.context.support.AbstractRefreshableApplicationContext#refreshBeanFactory

其实在阅读源码的过程中,读懂类的命名能过为我们提供很大的帮助,例如 AbstractRefreshableApplicationContext 这个类,可以看出她继承了AbstractApplicationContext 并且主要实现了其中的refresh 功能,当然,这里的refresh 并不是指 refresh() 方法。而是指的:refreshBeanFactory()

@Override
protected final void refreshBeanFactory() throws BeansException {
//判断当前ApplicationContext是否已经有 BeanFactory ,如果有,销毁所有 Bean,关闭 BeanFactory
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
// 初始化一个 DefaultListableBeanFactory
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId()); // 设置 BeanFactory 的两个配置属性:是否允许 Bean 覆盖、是否允许循环引用
customizeBeanFactory(beanFactory); // 读取配置文件信息,加载 Bean 到 BeanFactory 中
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}

可能大家之前会之前会认为ApplicationContext 与 BeanFactory 之间是继承关系,但是在实际应用中,但是它不应该被理解为 BeanFactory 的实现类,ApplicationContext 是持有 BeanFactory 的一个实例,并且以后所有的 BeanFactory相关的操作其实是给这个实例来处理的。

简单介绍一下 customizeBeanFactory 方法:

  • allowBeanDefinitionOverriding:设置Bean 是否可以被覆盖
  • allowCircularReferences:设置是否可以循环依赖
protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {
if (this.allowBeanDefinitionOverriding != null) {
beanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
if (this.allowCircularReferences != null) {
beanFactory.setAllowCircularReferences(this.allowCircularReferences);
}
}

5、AbstractXmlApplicationContext

核心方法:

org.springframework.context.support.AbstractXmlApplicationContext#loadBeanDefinitions(org.springframework.beans.factory.support.DefaultListableBeanFactory)

经历了那么长的路程,才终于到了XML -> Resource -> BeanDefinition 这个步骤:

@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
//给这个 BeanFactory 实例化一个 XmlBeanDefinitionReader BeanFactory.
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory); // 设置默认环境配置
beanDefinitionReader.setEnvironment(this.getEnvironment());
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this)); // 初始化 BeanDefinitionReader
initBeanDefinitionReader(beanDefinitionReader);
// 将配置文件读取,解析成BeanDefinition
loadBeanDefinitions(beanDefinitionReader);
} protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
Resource[] configResources = getConfigResources();
if (configResources != null) {
reader.loadBeanDefinitions(configResources);
}
String[] configLocations = getConfigLocations();
if (configLocations != null) {
reader.loadBeanDefinitions(configLocations);
}
}

可以看到,上面主要分成了两个步骤,一个是通过Resource 进行解析,一个是通过configLocations 进行解析(这个步骤,主要多了将资源定位符抓换为Resource 的过程)。

后续的Bean 解析,将会在下述方法进行解析:

org.springframework.beans.factory.xml.XmlBeanDefinitionReader#loadBeanDefinitions(org.springframework.core.io.support.EncodedResource)

6、总结

由于篇幅原因,本文主要介绍了Spring IOC 容器创建的源码过程,关于Bean 解析,以及后续步骤的源码分析,将会在后续补上。最后我们来回顾一下主流程:

  • ClassPathXmlApplicationContext.ClassPathXmlApplicationContext() 初始化
  • AbstractApplicationContext.refresh() 核心方法
  • AbstractRefreshableApplicationContext.refreshBeanFactory() 初始化BeanFactory
  • AbstractXmlApplicationContext.loadBeanDefinitions() 解析 Resource
  • 未完待续。。。

参考资料:

  • https://docs.spring.io/spring/docs/5.0.5.BUILD-SNAPSHOT/spring-framework-reference/core.html#spring-core
  • http://www.importnew.com/27469.html
  • http://www.importnew.com/19243.html

Spring源码分析:Spring IOC容器初始化的更多相关文章

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    前言:spring主要就是对bean进行管理,因此IOC容器的初始化过程非常重要,搞清楚其原理不管在实际生产或面试过程中都十分的有用.在[spring源码分析]准备工作中已经搭建好spring的环境, ...

随机推荐

  1. 排序算法Java实现(基数排序)

    算法思想:依次按个位.十位...来排序,每一个pos都有分配过程和收集过程,array[i][0]记录第i行数据的个数. package sorting; /** * 基数排序 * 平均O(d(n+r ...

  2. Android 使用shape定义不同控件的的颜色、背景色、边框色

    Android 使用shape定义不同控件的的颜色.背景色.边框色 设置按钮的右边框和底边框颜色为红色,边框大小为3dp: 在drawable新建一个 buttonstyle.xml的文件,内容如下: ...

  3. 关于 Git使用的全面总结 —— 致敬Git之父Linux

    p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 17.0px ".PingFang SC"; color: #454545 } p.p2 ...

  4. 源码实现 --> strcpy

    拷贝字符串到目标字符串 函数 char *strcpy(char *strDestination, const char *strSource); 复制源串strSource到目标串strDestin ...

  5. C语言作业第二次总结

    1.作业亮点 1.1作业整体概况 本次作业全体同学能够按时完成作业,且大部分同学的作业体现了自己的思路和方法,具备了一定变成能力. 1.2推荐博客 林岳-代码注释清晰,详细.->博文 王艺斌-算 ...

  6. Beta第二天

    听说

  7. Alpha阶段报告-hywteam

    一.Alpha版本测试报告 1. 在测试过程中总共发现了多少Bug?每个类别的Bug分别为多少个? BUG名 修复的BUG 不能重现的BUG 非BUG 没能力修复的BUG 下个版本修复 文件路径的表示 ...

  8. C语言-最后一次作业

    1.当初你是如何做出选择计算机专业的决定的? 经过一个学期,你的看法改变了么,为什么? 你觉得计算机是你喜欢的领域吗,它是你擅长的领域吗? 为什么? 我当初选择计算机专业是因为我是真的很向往计算机这方 ...

  9. Alpha冲刺Day9

    Alpha冲刺Day9 一:站立式会议 今日安排: 经过为期5天的冲刺,基本完成企业人员模块的开发.因第三方机构与企业存在委托的关系.第三方人员对于风险的自查.风险列表的展示以及自查风险的统计展示(包 ...

  10. PTA題目的處理(三)

    题目7-1 高速公路超速處罰 1.實驗代碼 #include <stdio.h> //#include <stdlib.h> int main() { int csp,lsp; ...