type
status
date
slug
summary
tags
category
icon
password
Imspring 源码介绍(二):IOC 实现
Imspring 项目地址点这里
项目结构
Imspring-core 项目结构如上所示,最外层是 BeanFactory 和 ApplicationContext 接口及其实现类。其中
ConfigurableXXX
开头的接口相当于扩展了原接口的功能。
AbstractXXX
开头的类是抽象类,实现了ConfigurableXXX
接口。和 Spring 的设计理念类似,抽象类实现了绝大部分的功能逻辑,把差异性的功能放到子类去实现。
DefaultListableBeanFactory
是BeanFactory
接口的最终实现类,AnnotationConfigApplicationContext
是ApplicationContext
接口的最终实现类。
目录细分功能如下:
- annotation:存放所有的注解。
- common:从 Spring 拷贝过来的一些常用类,该目录可以忽略。
- context:存放 Spring 的核心接口,包括 BeanPostProcessor、InitializingBean 等。
- env:存放 Environment 的相关内容。
- exception:Spring 异常类。
- resource:存放 Resource 的相关内容。
- support:核心接口的一些实现类。
- utils:工具类目录。
整体流程
Applicationcontext 的整体启动流程图如上所示,其中
AnnotationConfigApplicationContext
是 Spring 中使用 Annotation 配置方式启动容器的入口,也是我们学习项目代码的入口。这个类的继承结构如下所示:由此看出 ApplicationContext 接口直接继承了 BeanFactory 接口,所以说 ApplicationContext 是对 BeanFactory 的功能进行了增强扩展。
代码分析
1、容器启动
使用
AnnotationConfigApplicationContext
启动 Spring 容器,一般我们的启动代码会这样写。AnnotationConfigApplicationContext 是调用构造函数的时候会初始化两个非常重要的属性,分别对应以上的两种启动方式。
- AnnotatedBeanDefinitionReader:对应第一种使用 @Configuaration 配置类来启动容器的方式。读取配置类的 @Bean、@Import 等注解,将这些类解析成 BeanDefinition,注册到 Spring 容器中。
- ClassPathBeanDefinitionScanner:对应第二种扫描指定路径来启动容器的方式。扫描指定路径下的@Component 类,将这些类解析成 BeanDefinition,注册到 Spring 容器中。
不管是上面哪种启动方式,目的都是为了把类文件加载成 Beandefinition,最后都会调用到
refresh()
å这个方法。这个方法是 ApplicationContext 的核心方法。ApplicationContext 相较于 BeanFactory 的增强功能,例如自动注册 BeanPostProcessor、提前实例化所有 Bean 等,都在 refresh()
这个方法里面实现。这个方法下面再细说。2、资源扫描
两种扫描方式思路类似,以第二种扫描指定路径来启动容器的方式为例,ClassPathBeanDefinitionScanner 完成的工作如下:
- 调用 ClassPathBeanDefinitionScanner 构造函数的时候需要 BeanDefinitionRegistry 类型的参数,从上面的继承接口看出 AnnotationConfigApplicationContext 就实现了 BeanDefinitionRegistry 接口,所以直接把 AnnotationConfigApplicationContext 本身作为参数传递。
this.scanner = new ClassPathBeanDefinitionScanner(this);
。
- 使用默认资源加载器 DefaultResourceLoader 扫描指定的路径下的所有文件,过滤掉一些接口/抽象类等,把符合条件的文件加载成 Class。
- 从上面的结果集扫描带 @Component 的 Class,并把这些 Class 封装成待注册的 BeanDefinition。
- 待注册的 BeanDefinition 集合注册到 BeanDefinitionRegistry,也就是刚才传进来的 AnnotationConfigApplicationContext 实例。
3、容器 refresh
上面提到的
refresh()
方法主流程在 AbstractApplicationContext#refresh()
里面实现,Spring 源码的流程实现比较复杂,这里进行了部分简化。完成的功能如下:- 初始化 refresh 的上下文环境
- 初始化 BeanFactory
- 对 BeanFactory 进行功能增强
- 执行 BeanFactoryPostProcessor
- 注册 BeanPostProcessor
- 实例化所有非延迟加载的单例
4、Bean 实例化
上一步的最后一步实例化所有非延迟加载的单例,主要流程放在
AbstractBeanFactory#doGetBean
实现。上面调用
DefaultListableBeanFactory#getSingleton
从三级缓存获取实例。三级缓存是解决循环依赖的关键:singletonObjects
:一级缓存,存放完全初始化好的 bean,从该缓存中取出的 bean 可以直接使用。
earlySingletonObjects
:二级缓存,提前曝光的单例对象的 cache,存放原始的 bean 对象(尚未填充属性),用于解决循环依赖。
singletonFactories
:三级缓存,存放 bean 工厂对象,这个对象其实是一个函数式接口,接口实现是创建一个 bean 对象,用来解决 AOP 的循环依赖。
这个三级缓存是这样用的,先调用
getSingleton()
从三级缓存里面获取实例- 先从一级缓存获取实例
- 一级缓存拿不到,再从二级缓存获取实例
- 二级缓存拿不到,从三级缓存获取创建的函数式接口,如果拿到的话就使用其创建一个实例并放入二级缓存,然后清除三级缓存
如果三级缓存都没有获取到实例,说明是第一次初始化,就会调用
createBean()
创建实例。createBean()
是作为函数式接口的实现传进来的,在 createBean()
前后会有一些前后置处理,整个流程是- 设置 bean 正在初始化的标记
- 调用
createBean()
真正实例化bean
- 清除 bean 正在初始化的标记
- 清除二级和三级缓存,把 bean 添加到一级缓存
createBean()
的实现里面,调用 createBeanInstance()
创建完 Bean 实例之后,把一个函数式接口的实现放入三级缓存 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, def, finalBeanInstance));
getEarlyBeanReference
的作用是判断目前缓存的 BeanPostProcessor 集合里面有没有 InstantiationAwareBeanPostProcessor 类型的 BeanPostProcessor(AOP就属于该类型),如果有的话就调用该 BeanPostProcessor 的 getEarlyBeanReference
方法,提前创建一个代理对象;如果没有的话就返回原来的 bean 对象。注意此时三级缓存里面只有第三级缓存保存了一个函数式接口的实现,其他两级缓存都是空的。根据是否发生循环依赖有三种情况:
- 如果没有发生循环依赖,三级缓存根本不会被触发调用,上面的 createBean 按照正常的流程返回一个 bean 实例。此时二级是空的,只有三级缓存有内容。最后清除二级和三级缓存,把该 bean 实例放到一级缓存里面。
- 如果发生了循环依赖,没有发生 AOP。调用
getSingleton()
获取正在初始化中的实例的时候,就会触发三级缓存的调用,创建 bean 实例放入二级缓存并清空三级缓存,然后返回二级缓存的 bean 实例。最后清除二级和三级缓存,把该 bean 实例放到一级缓存里面。
- 如果发生了循环依赖,且发生 AOP。和第 2 点唯一的区别是,此时二级缓存的 bean 实例不是原始对象,而是代理对象。因为在触发三级缓存的调用时,调用 BeanPostProcessor 的
getEarlyBeanReference()
创建了代理对象。
5、Bean 初始化
在
createBean()
里面,initializeBean()
完成 Bean 的初始化流程,AOP 就是在这一步创建了代理对象。初始化的流程如下:- 检查 Aware 相关接口并设置依赖
- BeanPostProcessor 前置处理
- 调用 InitializingBean#afterPropertiesSet
- 调用 init-method
- BeanPostProcessor 后置处理
总结
上面介绍了 ApplicationContext 的核心流程。整个过程并不复杂,相比起 Spring 源码做了很多的简化操作。简单来说分为以下几个步骤:
- 容器启动,从指定路径或者配置文件扫描资源并加载为 BeanDefinition。
- 容器 refresh,完成自动注册 BeanFactory 等操作,最后提前实例化所有的 Bean 实例。
- Bean 实例化,依次完成创建实例、依赖注入、实例初始化等操作,中途使用三级缓存解决循环依赖的问题。
- 容器启动完成。
- Author:mcbilla
- URL:http://mcbilla.com/article/a240c5e0-66c4-456f-a39d-dafd557ca814
- Copyright:All articles in this blog, except for special statements, adopt BY-NC-SA agreement. Please indicate the source!
Relate Posts