Spring 源码分析衍生篇二 : Spring中的循环依赖

一、前言

本文是 Spring源码分析:bean的加载过程 的衍生文章。主要是因为本人菜鸡,在分析源码的过程中还有一些其他的内容不理解,故开设衍生篇来完善内容以学习。

二、Spring的解决方案

  • 什么是循环依赖?
    循环依赖就是循环引用,就是两个或多个Bean相互之间持有对方,即A类中包含B类属性b, B类中也包含A类属性a,或者依次依赖,即成为循环依赖。

Spring 将循环依赖的处理分为了三种情况:

  • 构造器循环依赖
  • setter循环依赖
  • prototype范围的依赖处理
    对于 singleton 作用域的bean,可以通过 setAllowCircularReference(false) 来禁用循环引用。
    下面详解每一种情况。

1 构造器循环依赖

表示通过构造器注入构成的循环依赖,这种依赖是无法解决的只能抛出 BeanCurrentlyInCreationException异常表示循环依赖。

如在创建A类时,构造器需要创建B类,则回去创建B类,在创建B类过程中又发现要去创造C类,又去创建C类,而 创建C类有需要A类。则会形成一个闭环,无法解决。

因此Spring 将每一个正在创建的bean标识符放在了一个“当前创建bean池”(earlySingletonObjects 集合)。当bean标识符在创建过程中将一直保持在这个池中,,因此如果创建bean过程中发现自己已经在“当前创建bean池”中,则会抛出 BeanCurrentlyInCreationException异常,并将创建完毕的bean从“当前创建bean池”中清除。


如下的构造注入的方式在启动时会抛出异常

@Component
public class ACircular {
    
    
    private BCircular bCircular;
    public ACircular(BCircular bCircular) {
    
    
        this.bCircular = bCircular;
    }
}
...
@Component
public class BCircular {
    
    
    private ACircular aCircular;
    public BCircular(ACircular aCircular) {
    
    
        this.aCircular = aCircular;
    }
}

在这里插入图片描述

2 setter 循环依赖

表示通过setter 注入方式构成的循环依赖。对于setter 注入造成的依赖是通过Spring容器提前暴露刚完构造器注入但并未完成其他步骤(如setter注入,即仅仅自己完成了创建,但是对里面引用的属性还未创建完成) 的bean来完成的。而且只能解决单例作用域的bean循环依赖。通过提前暴露一个单例工厂方法,从而使其他bean能引用到该bean。
如下代码所示:

     addSingletonFactory(beanName, new ObjectFactory() {
    
    
            @Override
            public Object getObject() throws BeansException {
    
    
                return getEarlyBeanReference(beanName, mbd, bean);
            }
        });

具体步骤如下:

  1. Spring 容器创建单例 “testA” bean, 首先根据无参构造器创建bean,并暴露一个“ObjectFactory” 用于返回一个提前暴露一个创建中的bean,并将“testA” 标识符放到“当前创建bean池”,然后通过setter 注入 “testB”;
  2. Spring 容器创建单例“testB” bean,首先根据无参构造器创建bean,并暴露一个“ObjectFactory” 用于返回一个提前暴露一个创建中的bean,并将“testB” 标识符放到“当前创建bean池”,然后通过setter 注入 “testC”;
  3. Spring 容器创建单例“testC” bean,首先根据无参构造器创建bean,并暴露一个“ObjectFactory” 用于返回一个提前暴露一个创建中的bean,并将“testC” 标识符放到“当前创建bean池”,然后通过setter 注入 “testA”。进行诸如“testA”时由于提前暴露了“ObjectFactory” 工厂,从而使它返回提前暴露一个创建中的bean
  4. 最后再依赖注入“testB” 和“testA”,完成setter 注入。

如下方式则不会出现异常。

@Component
public class ACircular {
    
    
    private BCircular bCircular;
    @Autowired
    public void setbCircular(BCircular bCircular) {
    
    
        this.bCircular = bCircular;
    }
}
...
@Component
public class BCircular {
    
    
    private ACircular aCircular;
    @Autowired
    public void setaCircular(ACircular aCircular) {
    
    
        this.aCircular = aCircular;
    }
}

3. prototype 范围的依赖处理

对于 prototype 作用域的bean,Spring容器无法完成依赖注入,因为Spring容器中无法缓存prototype 作用域的bean,因此无法暴露一个创建中的bean。

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class ACircular {
    
    
    private BCircular bCircular;
    @Autowired
    public void setbCircular(BCircular bCircular) {
    
    
        this.bCircular = bCircular;
    }
}
...
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class BCircular {
    
    
    private ACircular aCircular;
    @Autowired
    public void setaCircular(ACircular aCircular) {
    
    
        this.aCircular = aCircular;
    }
}

调用时会抛出异常。注意这里是调用时,并非是服务启动时,因为原型模式的特性并不需要启动时就创建好bean。在调用的时候才会尝试创建,所以这里在使用bean 的时候才会抛出异常。
在这里插入图片描述

三、详细流程分析

本来想以源码形式分析,不过感觉不够直观,所以直接用白话文来讲解。我们以上述的 ACircular 和 BCircular的创建过程为例,以下简称AB;

  1. Spring 创建bean A
  2. 尝试从缓存中获取,逻辑上依次是从 singletonObjectsearlySingletonObjectssingletonFactories 中依次获取。这里我们只需关注 singletonFactories 。由于是第一次创建,必然无法从缓存中拿到,于是Spring 开始创建A。
  3. A 的创建这里模糊分为两步,A初始化完成和 A属性注入完成阶段。A初始化完成即A对象已经创建,但是内部属性尚未赋值,即内部的B变量依旧为空。A属性注入完成即已经给B变量赋值,这时候A的创建已经完成。
  4. 首先 A 完成了初始化阶段,开始进行属性注入阶段(这里划分为两阶段并不代表只有两阶段,实际上的过程是非常复杂的,在属性注入阶段后还有一堆后续工作要处理,但并非此次重点,因此忽略)。在属性注入开始之前,也就是 populateBean 之前。Spring 会先创建一个ObjectFactory<A> 的实例,并保存到缓存singletonFactories 中, 这时候singletonFactories 中有A类缓存(以ObjectFactory形式存在)。完成缓存操作后,Spring开始对A类进行属性注入。
  5. 注入过程中发现 A类中有B类属性,于是去获取B的实例。
  6. 由于B也是第一次创建,所以B的流程和上面类似,也是缓存获取,B初始化完成,同样在B属性注入前,也会创建一个 ObjectFactory<B> 的实例,并保存到singletonFactories 中。singletonFactories 此时具有A、B两个类的缓存,不过需要注意的是,这里的A,B类属性都未注入,也就是创建不完全的Bean。
  7. 随后B类开始属性注入,调用populateBean 方法去 注入A属性,同样和之前的步骤一样,从缓存中获取,但是和之前不同的是,这时候的 singletonFactories 缓存中有A的缓存,将A的实例缓存。B即完成属性注入。
  8. 别忘了这时候的逻辑是 A.createBean -> A.populateBean -> B.createBean -> B.populate 。所以当B创建完成后,A.populateBean 的工作也完成了,随后整个A属性的创建工作就完成了。
  9. 至此解决了单例模式下的循环依赖的问题。

从上面的流程我们可以理解单例模式下循环依赖的解决:

A创建 -> A缓存 -> A属性注入 -> B创建 -> B缓存 -》B属性注入

同时举一反三我们可以得知:
1 . 构造器依赖无法解决循环依赖 : 因为A创建的过程即需要B参与,创建B的过程也要A的参与,但A尚未完成初始化阶段,无法缓存,导致B创建失败,从而A创建失败。
2. 原型模式无法解决循环依赖:因为原型模式无法缓存bean,缓存bean的前提是单例模式下,原型模式下的bean即多例的,无法进行缓存。


以上:内容部分参考
《Spring源码深度解析》
如有侵扰,联系删除。 内容仅用于自我记录学习使用。如有错误,欢迎指正

猜你喜欢

转载自blog.csdn.net/qq_36882793/article/details/105756945
今日推荐