Spring源码分析:Spring IOC容器初始化
概述:
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
核心方法:
经历了那么长的路程,才终于到了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 解析,将会在下述方法进行解析:
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容器初始化的更多相关文章
- 【spring源码分析】IOC容器初始化(总结)
前言:在经过前面十二篇文章的分析,对bean的加载流程大致梳理清楚了.因为内容过多,因此需要进行一个小总结. 经过前面十二篇文章的漫长分析,终于将xml配置文件中的bean,转换成我们实际所需要的真正 ...
- 【spring源码分析】IOC容器初始化(二)
前言:在[spring源码分析]IOC容器初始化(一)文末中已经提出loadBeanDefinitions(DefaultListableBeanFactory)的重要性,本文将以此为切入点继续分析. ...
- 【spring源码分析】IOC容器初始化(三)
前言:在[spring源码分析]IOC容器初始化(二)中已经得到了XML配置文件的Document实例,下面分析bean的注册过程. XmlBeanDefinitionReader#registerB ...
- 【spring源码分析】IOC容器初始化(四)
前言:在[spring源码分析]IOC容器初始化(三)中已经分析了BeanDefinition注册之前的一些准备工作,下面将进入BeanDefinition注册的核心流程. //DefaultBean ...
- 【spring源码分析】IOC容器初始化(七)
前言:在[spring源码分析]IOC容器初始化(六)中分析了从单例缓存中加载bean对象,由于篇幅原因其核心函数 FactoryBeanRegistrySupport#getObjectFromFa ...
- 【spring源码分析】IOC容器初始化(十)
前言:前文[spring源码分析]IOC容器初始化(九)中分析了AbstractAutowireCapableBeanFactory#createBeanInstance方法中通过工厂方法创建bean ...
- 【spring源码分析】IOC容器初始化——查漏补缺(一)
前言:在[spring源码分析]IOC容器初始化(十一)中提到了初始化bean的三个步骤: 激活Aware方法. 后置处理器应用(before/after). 激活自定义的init方法. 这里我们就来 ...
- 【spring源码分析】IOC容器初始化——查漏补缺(五)
前言:我们知道在Spring中经常使用配置文件的形式对进行属性的赋值,那配置文件的值是怎么赋值到属性上的呢,本文将对其进行分析. 首先了解一个类:PropertySourcesPlaceholderC ...
- 【spring源码分析】IOC容器初始化——查漏补缺(二)
前言:在[spring源码分析]IOC容器初始化(八)中多次提到了前置处理与后置处理,本篇文章针对此问题进行分析.Spring对前置处理或后置处理主要通过BeanPostProcessor进行实现. ...
- 【spring源码分析】IOC容器初始化(一)
前言:spring主要就是对bean进行管理,因此IOC容器的初始化过程非常重要,搞清楚其原理不管在实际生产或面试过程中都十分的有用.在[spring源码分析]准备工作中已经搭建好spring的环境, ...
随机推荐
- Docker + webpack 打包前端项目
码云代码地址: https://gitee.com/caonimashi/docker_deployment_front_end 构建基础镜像: 1.下载一个 Apline Linux 操作系统 ...
- 【XML】 XML格式一些记录
XML XML格式常用于网络通讯,本身不会有作为而是作为纯文本传输,可以说它是一种独立于应用和硬件的数据传输工具.虽然看起来XML比HTML要更加简单,也知道的更加晚一点,但是需要知道的是,XML才是 ...
- ElasticSearch之 控制相关度原理讲解
控制相关度 相关度评分背后的理论 如何计算评分的 Lucene 使用布尔模型(Boolean model) 查找匹配文档 并主要的借鉴了 词频/逆向文档频率(term frequency/invers ...
- ssm中iReport报表使用json数据源过程体会
前言:做这个一定要有耐心,因为报表本就是数据杂糅到规整的过程,这篇心得会细讲每一步操作,如果只想着一眼到位,建议close tab 在公司中遇到项目,大概是一个这样的需求,有一个列表和一个标题,需要把 ...
- C#/AutoCAD 2018/ObjectArx/二次开发添加删除实体的工具函数(四)
1.添加删除实体 C# ObjectARX二次开发添加删除实体是非常容易主要代码如下: 添加实体: objId = btr.AppendEntity(entity); trans.AddNewlyCr ...
- 【前端】wangEditor(富文本编辑器) 简易使用示例
转载请说明作者或者注明出处,谢谢 说到前端常用的编辑器,自然也少不了富文本编辑器(RichText Editor) 笔者在此之前也看了一些相关的在线编辑器,其中包括了当百度搜索“富文本编辑器”字样时出 ...
- 使用git将文件上传到Coding
1,首先在Coding上新建项目. 2,填写项目的相关内容. 3,建立项目后复制下面鼠标所选内容. 4,在自己的电脑中建立文件夹. 5,进入该文件夹后,点击鼠标右键,然后再点Git Clone. 6 ...
- 第一次作业:扑通扑通 我的IT
让我掉下眼泪的不止昨夜的酒,还有这满屏的代码. 第一部分:结缘计算机 你为什么选择计算机专业?你认为你的条件如何?和这些博主比呢? 在炎炎的夏日,伴随这高三的结束,我也面临大学专业的选择,我看着书里密 ...
- ExecutorService实际上是一个线程池的管理工具
在Java5之后,并发线程这块发生了根本的变化,最重要的莫过于新的启动.调度.管理线程的一大堆API了.在Java5以后,通过Executor来启动线程比用 Thread的start()更好.在新特征 ...
- bzoj千题计划271:bzoj4869: [六省联考2017]相逢是问候
http://www.lydsy.com/JudgeOnline/problem.php?id=4869 欧拉降幂+线段树,每个数最多降log次,模数就会降为1 #include<cmath&g ...