概述:

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. from提交表单后 数据提交到后台 但不跳转页面 可用iframe

    可以页面事先加载被隐藏的iframe标签,或者等到需要的时候通过js生成,再提交,提交之前,form的target指向iframe(我是要实现新页面生成的时候程半透明状态,所以用了后者的方法) 代码如 ...

  2. Spring Cloud简介以及版本选择

    什么是SpringCloud 官方的说法就是spring Cloud 给开发者提供一套按照一定套路快速开发 分布式系统 的工具. 具体点就是Spring boot实现的微服务架构开发工具.它为微服务架 ...

  3. 简单爬虫 -- 以爬取NASA AOD数据(TIFF文件)为例

    目录: 网站分析 爬取下载链接 爬取TIFF图片 1.网站分析 主页面:https://neo.sci.gsfc.nasa.gov/view.php?datasetId=MYDAL2_M_AER_OD ...

  4. kvm之十二:虚拟机迁移

    虚拟机迁移该方式要确保虚拟机是关机状态.virsh shutdown snalevirsh dumpxml snale > /etc/libvirt/qemu/snale_qy.xml  // ...

  5. CSS奇思妙想图形(心形、气泡三角形、切角、梯形、饼图等)

    今天看到一篇不错文章,在原来CSS3图形创建基础上扩展了很多. 这里记录总结下 心形 原理:利用 圆形 和 正方形实现 HTML: <div class="heartShaped&qu ...

  6. C#编程语言之委托与事件(二)—— C#事件

    前面已经大致讲述了C#委托的一些基础知识点,本文接下来的内容是C#中的事件(Event),在此我提个建议,如果是刚接触C#的委托类型的朋友可以先看到这里,等熟悉了委托的使用之后(大约1-2天)再来了解 ...

  7. VS2013创建Windows服务 || VS2015+Windows服务简易教程

    转自:https://www.cnblogs.com/no27/p/4849123.htmlhttps://blog.csdn.net/ly416/article/details/78860522 V ...

  8. 【前端】wangEditor(富文本编辑器) 简易使用示例

    转载请说明作者或者注明出处,谢谢 说到前端常用的编辑器,自然也少不了富文本编辑器(RichText Editor) 笔者在此之前也看了一些相关的在线编辑器,其中包括了当百度搜索“富文本编辑器”字样时出 ...

  9. 20162323周楠《Java程序设计与数据结构》第五周总结

    20162323周楠 2016-2017-2 <程序设计与数据结构>第五周学习总结 教材学习内容总结 1.面向对象软件设计的基本部分是确定程序中应该创建哪些类: 2.面向对象程序设计的核心 ...

  10. django搭建web (二) urls.py

    URL模式: 在app下的urls.py中 urlpatterns=[ url(正则表达式,view函数,参数,别名,前缀)] urlpatterns=[ url(r'^hello/$',hello. ...