Spring 循环引用(二)源码分析

Spring 系列目录(https://www.cnblogs.com/binarylei/p/10198698.html)

Spring 循环引用相关文章:

  1. 《Spring 循环引用(一)一个循环依赖引发的 BUG》:https://www.cnblogs.com/binarylei/p/10325698.html
  2. 《Spring 循环引用(二)源码分析》:https://www.cnblogs.com/binarylei/p/10326046.html

一、Spring 中单例 bean 的管理

Spring 对单例 bean 的管理都是在 DefaultSingletonBeanRegistry 中完成的,这里会涉及到其内部所使用的几个内部属性:

  1. // 1.1 保存最终创建成功的单例 beanName -> beanInstance
  2. private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
  3. // 1.2 中间变量,beanName -> Objectfactory
  4. private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
  5. // 1.3 中间变量,bean 还在创建的时候就可以获取,用于检测循环引用
  6. private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

这里涉及用于存储 bean 的不同的 Map,可能让人感到崩溃,简单解释如下:

  1. singletonObjects:用于保存 beanName 和创建 bean 实例之间的关系。beanName -> beanInstance
  2. singletonFactories:用于保存 beanName 和对象工厂的引用关系,一旦最终对象被创建(通过 objectFactory.getObject()),此引用信息将删除。beanName -> Objectfactory
  3. earlySingletonObjects:用于保存 beanName 和创建的原始 bean 的引用关系,注意这里是原始 bean,即使用工厂方法或构造方法创建出来的对象,一旦对象最终创建好,此引用信息将删除。 与 singletonObjects 的不同之处在于,此时 bean 还在创建过程中,而且之后还可以进行增强,也就是代理后这两个 bean 就不是同一个了。可以通过 getBean 方法获取到了,其目的是用来检测循环引用。

从上面的解释,可以看出,这 singletonFactories 和 earlySingletonObjects 都是一个临时的辅助状态。在所有的对象创建完毕之后,此两个对象的 size 都为 0。那么再来看下这两个对象如何进行协作:

(1) 方法1:创建单例对象

  1. public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
  2. synchronized (this.singletonObjects) {
  3. Object singletonObject = this.singletonObjects.get(beanName);
  4. if (singletonObject == null) {
  5. // 1. 将这个 bean 添加到 singletonsCurrentlyInCreation 集合中,这样就可以判断 bean 是否存在创建
  6. beforeSingletonCreation(beanName);
  7. // 2. 初始化 bean,委托给 ObjectFactory 完成
  8. singletonObject = singletonFactory.getObject();
  9. // 3. 从 singletonsCurrentlyInCreation 移除该 bean
  10. afterSingletonCreation(beanName);
  11. // 4. 创建完成进行注册,这样下次就可以从缓存中直接获取这个对象了
  12. addSingleton(beanName, singletonObject);
  13. }
  14. return (singletonObject != NULL_OBJECT ? singletonObject : null);
  15. }
  16. }

(2) 方法2:单例对象创建完成进行注册

  1. protected void addSingleton(String beanName, Object singletonObject) {
  2. synchronized (this.singletonObjects) {
  3. this.singletonObjects.put(beanName, singletonObject);
  4. this.singletonFactories.remove(beanName);
  5. this.earlySingletonObjects.remove(beanName);
  6. this.registeredSingletons.add(beanName);
  7. }
  8. }

(3) 方法3:将实例化后的对象暴露到容器中

Spring 在 bean 实例化后就会调用 addSingletonFactory 将这个对象提前暴露到容器中,这们就可以通过 getBean(A) 得到这个对象,即使这个对象仍正在创建。用于解决循环依赖。

  1. protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
  2. Assert.notNull(singletonFactory, "Singleton factory must not be null");
  3. synchronized (this.singletonObjects) {
  4. if (!this.singletonObjects.containsKey(beanName)) {
  5. this.singletonFactories.put(beanName, singletonFactory);
  6. this.earlySingletonObjects.remove(beanName);
  7. this.registeredSingletons.add(beanName);
  8. }
  9. }
  10. }

(4) 方法4:从缓存中获取 bean

这个方法也是专门用于解决循环依赖的问题,当不存在循环依赖时 earlySingletonObjects 总是 null。

  1. protected Object getSingleton(String beanName, boolean allowEarlyReference) {
  2. Object singletonObject = this.singletonObjects.get(beanName);
  3. if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
  4. synchronized (this.singletonObjects) {
  5. singletonObject = this.earlySingletonObjects.get(beanName);
  6. if (singletonObject == null && allowEarlyReference) {
  7. ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
  8. if (singletonFactory != null) {
  9. singletonObject = singletonFactory.getObject();
  10. this.earlySingletonObjects.put(beanName, singletonObject);
  11. this.singletonFactories.remove(beanName);
  12. }
  13. }
  14. }
  15. }
  16. return singletonObject;
  17. }

二、Spring 创建 bean 过程

我们从 BeanFactory#getBean(beanName) 调用说起,看一下这几个方法的调用顺序:

2.1 AbstractBeanFactory#doGetBean

这个方法先从缓存中获取 bean,没有再创建 bean,因此会调用方法 4 和方法 1,我们看一下调用过程。

(1) getSingleton(beanName, true)

doGetBean 首先从缓存中获取数据,Object sharedInstance = getSingleton(beanName),这个方法最终会调用 getSingleton(beanName, true)

(2) getSingleton(beanName, singletonFactory)

如果缓存中没有 bean,则会调用 addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) 来创建一个新 bean,代码如下:

  1. if (mbd.isSingleton()) {
  2. sharedInstance = getSingleton(beanName, () -> {
  3. try {
  4. return createBean(beanName, mbd, args);
  5. }
  6. catch (BeansException ex) {
  7. destroySingleton(beanName);
  8. throw ex;
  9. }
  10. });
  11. bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
  12. }

一旦调用 getSingleton(beanName, singletonFactory) 方法,这个方法创建开始时就会标记这个 bean 为正在创建,创建结束后移除对应的标记。直接创建 bean 的过程实际上是委托给了 createBean 方法。继续跟踪这个方法。

2.2 AbstractAutowireCapableBeanFactory#doCreateBean

doCreateBean 方法中完成了单例的 bean 有以下几个主要的步骤:

  1. createBeanInstance 实例化 bean 对象,一般是通过反射调用默认的构造器。
  2. populateBean bean 属性注入,在这个步骤会从 Spring 容器中查找对应属性字段的值,解决循环依赖问题。
  3. initializeBean 调用的 bean 定义的初始化方法。

(3) addSingletonFactory(beanName, singletonFactory)

在 createBeanInstance 后 populateBean 前 Spring 会将这个实例化的 bean 提前暴露到容器中,这样 populateBean 属性注入时就可以通过 getBean(A) 查找到。

  1. boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
  2. isSingletonCurrentlyInCreation(beanName));
  3. if (earlySingletonExposure) {
  4. addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
  5. }

(4) getSingleton(beanName, false)

在 bean 初始化完成还后还需要进行依赖的检查,这时因为提前暴露的这个 bean(即使用工厂方法或构造方法创建出来的对象) initializeBean 还可以进行增强,这样这两个 bean 就不是同一个了。Spring 默认是不允许这种情况发生的。

  1. if (earlySingletonExposure) {
  2. Object earlySingletonReference = getSingleton(beanName, false);
  3. if (earlySingletonReference != null) {
  4. if (exposedObject == bean) {
  5. exposedObject = earlySingletonReference;
  6. }
  7. else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
  8. String[] dependentBeans = getDependentBeans(beanName);
  9. Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
  10. for (String dependentBean : dependentBeans) {
  11. if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
  12. actualDependentBeans.add(dependentBean);
  13. }
  14. }
  15. if (!actualDependentBeans.isEmpty()) {
  16. throw new BeanCurrentlyInCreationException(beanName,
  17. "Bean with name '" + beanName + "' has been injected into other beans [" +
  18. StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
  19. "] in its raw version as part of a circular reference, but has eventually been " +
  20. "wrapped. This means that said other beans do not use the final version of the " +
  21. "bean. This is often the result of over-eager type matching - consider using " +
  22. "'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
  23. }
  24. }
  25. }
  26. }

(5) addSingleton(beanName, singletonObject)

在 bean 创建结束后还有一步是在 getSingleton(beanName, singletonFactory) 中完成的,调用 addSingleton(beanName, singletonObject),即注册最终的 bean,同时清空中间的辅助状态。

这样单例 bean 的创建过程就完成了,下面就需要分析循环引用下 singletonFactories、earlySingletonObjects 这两个集合的状态。

三、循环引用下 Bean 状态分析

3.1 正常情况

在正常的情况下,调用顺序如下:以下有无,表示是否持有对指定 Bean 的引用

过程 方法 singletonFactories earlySingletonObjects singletonObjects
缓存中获取 getSingleton(beanName, true)
创建 bean getSingleton(beanName, singletonFactory)
提前暴露到容器中 addSingletonFactory(beanName, singletonFactory)
依赖检查 getSingleton(beanName, false)
注册 addSingleton(beanName, singletonObject)

可以看到正常情况下,单例 bean 暴露的对象只会出现在 singletonFactories 集合中,不可能出现在 earlySingletonObjects 集合中,除非在创建 bean 的过程中又调用了 getSingleton(beanName, true) 方法,也就是此时出现了循环引用。

3.2 循环引用

但是出现循环引用之后呢,就会出现这种情况:

过程 方法 singletonFactories earlySingletonObjects singletonObjects
缓存中获取 A getSingleton(A, true) A无B无 A无B无 A无B无
创建 A getSingleton(A, singletonFactory) A无B无 A无B无 A无B无
暴露 A 到容器中 addSingletonFactory(A, singletonFactory) A有B无 A无B无 A无B无
populateBean(A, mbd, instanceWrapper) A 注入 B 时又依赖了 A,此时由 B 准备解析 A……
缓存中获取 A getSingleton(A, true) A无B有 A有B无 A无B无
注册 B addSingleton(B, singletonObject) A无B无 A有B无 A无B有
populateBean(A, mbd, instanceWrapper) 完成属性注入
A- = initializeBean(beanName, exposedObject, mbd) 在 initializeBean 之后 A 变为 A-
依赖检查 getSingleton(beanName, false) A无B无 A有B无 A无B有
注册 A getSingleton(beanName, false) A无B无 A无B无 A有B有

在上面这个过程中,在对 A 进行验证时,就会从 earlySingletonObjects 中取得一个 A,但是这个 A 和后面的 A- 可能不是同一个对象,这是因为有了 beanPostProcessor 存在,它可以改变 bean 的最终值,比如对原始 bean 进行封装,代理等。在这个过程中,出现了 3 个对象 A, A-, B,而 B 中所持有的 A 对象为原始的 A。如果这里的 A 和 A- 不是同一个对象,即产生了 beanA 有了 beanB 的引用,但 beanB 并没有 beanA 的引用,而是另一个 beanA 的引用。


每天用心记录一点点。内容也许不重要,但习惯很重要!

Spring 循环引用(二)源码分析的更多相关文章

  1. Spring 循环引用(三)源码深入分析版

    @ 目录 前言 正文 分析 doGetBean 为什么Prototype不可以 createBean doCreateBean getEarlyBeanReference getSingleton b ...

  2. spring boot 2.0 源码分析(四)

    在上一章的源码分析里,我们知道了spring boot 2.0中的环境是如何区分普通环境和web环境的,以及如何准备运行时环境和应用上下文的,今天我们继续分析一下run函数接下来又做了那些事情.先把r ...

  3. Spring中Bean命名源码分析

    Spring中Bean命名源码分析 一.案例代码 首先是demo的整体结构 其次是各个部分的代码,代码本身比较简单,不是我们关注的重点 配置类 /** * @Author Helius * @Crea ...

  4. Spring Cloud 学习 之 Spring Cloud Eureka(源码分析)

    Spring Cloud 学习 之 Spring Cloud Eureka(源码分析) Spring Boot版本:2.1.4.RELEASE Spring Cloud版本:Greenwich.SR1 ...

  5. springboot bean的循环依赖实现 源码分析

    springboot bean的循环依赖实现 源码分析 本文基于springboot版本2.5.1 <parent> <groupId>org.springframework. ...

  6. spring boot 2.0 源码分析(一)

    在学习spring boot 2.0源码之前,我们先利用spring initializr快速地创建一个基本的简单的示例: 1.先从创建示例中的main函数开始读起: package com.exam ...

  7. Spring JPA实现逻辑源码分析总结

    1.SharedEntityManagerCreator: entitymanager的创建入口 该类被EntityManagerBeanDefinitionRegistrarPostProcesso ...

  8. Spring Boot 自动配置 源码分析

    Spring Boot 最大的特点(亮点)就是自动配置 AutoConfiguration 下面,先说一下 @EnableAutoConfiguration ,然后再看源代码,到底自动配置是怎么配置的 ...

  9. spring boot 2.0 源码分析(二)

    在上一章学习了spring boot 2.0启动的大概流程以后,今天我们来深挖一下SpringApplication实例变量的run函数. 先把这段run函数的代码贴出来: /** * Run the ...

  10. 设计模式(二十一)——解释器模式(Spring 框架中SpelExpressionParser源码分析)

    1 四则运算问题 通过解释器模式来实现四则运算,如计算 a+b-c 的值,具体要求 1) 先输入表达式的形式,比如 a+b+c-d+e,  要求表达式的字母不能重复 2) 在分别输入 a ,b, c, ...

随机推荐

  1. OpenCV之Vec3f

    Vec3f表示的是3通道float类型的 Vect,就相当于3通道float类型的图像(这是其中一个具体化),解释可以从源代码中看出来. 下面给出一个具体的例子: Vec3f point = Vec3 ...

  2. CGLIB代理基础

    本文意在讲解CGLIB的基础使用及基本原理. 一.CGLIB的基本原理: 依赖ASM字节码工具,通过动态生成实现接口或继承类的类字节码,实现动态代理. 针对接口,生成实现接口的类,即implement ...

  3. 进制转换&数据类型(1)

    一: 进制转换 在计算机中, 数据都是以0和1来表示的 进制: 进位制 十进制: 数字由0~9这10个数字来表示, 逢10进1位 0 1 2 3 4 5 6 7 8 9 10 二进制: 数字由0和1这 ...

  4. ssh 使用 aws

    使用 PuTTY 从 Windows 连接到 Linux 实例 启动您的实例之后,您可以连接到该实例,然后像使用您面前的计算机一样来使用它. 注意 启动实例后,需要几分钟准备好实例,以便您能连接到实例 ...

  5. tomcat部署war包

    部署步骤 1.下载tomcat 直接在网上下载即可,随便把包下到一个地方 下面文中的xxx均代表tomcat的安装目录   2.将java工程导出war包 在intellij idea的执行左侧选中t ...

  6. C# 写App.config配置文件的方法

    private static void AccessAppSettings() { //获取Configuration对象 Configuration config = ConfigurationMa ...

  7. Win7下VB6.0不能加载mscomctl.ocx的解决办法

    下载这个:http://pan.baidu.com/s/1sjJgrbJ 然后在命令框下注册这个组件: regsvr32 mscomctl.ocx 即可

  8. idea使用maven打包jar包

    1.在pom.xml中加入以下内容: <?xml version="1.0" encoding="UTF-8"?> <project xmln ...

  9. UA判断跳转

    <script type="text/javascript"> UA = navigator.userAgent.toLowerCase(); url = window ...

  10. 五:python 对象类型详解二:字符串(上)

    一:常量字符串 常量字符串用起来相对简单,也许最复杂的事情就是在代码中有如此多的方法来编写它们. eg:单引号:'spam"m'   , 双引号: “spa'm” , 三引号:‘’‘... ...