spring的循环依赖问题

Spring循环依赖问题是指在Spring IoC容器管理的Bean(即组件)之间存在的一种相互依赖关系,具体表现为两个或多个Bean在初始化时形成一个闭环,每个Bean在完成自身实例化或依赖注入的过程中都需要依赖另一个Bean,而这个被依赖的Bean又反过来依赖于原始Bean。这种循环依赖结构可能导致容器无法正常完成Bean的创建和初始化,进而影响应用程序的运行。

以下是循环依赖问题的详细说明:

1. 循环依赖的类型

Spring中的循环依赖主要分为两种类型:

a. 构造器循环依赖

这是指Bean之间的依赖关系通过构造函数注入实现,即一个Bean的构造函数需要另一个Bean作为参数。例如:

public class A {
    private final B b;

    public A(B b) {
        this.b = b;
    }
}

public class B {
    private final A a;

    public B(A a) {
        this.a = a;
    }
}

在这里,Bean A的构造函数需要B的实例,而Bean B的构造函数又需要A的实例,形成了一个构造器循环依赖。

b. 属性(setter)循环依赖

这种循环依赖发生在通过setter方法或字段注入时。两个或多个Bean通过@Autowired等注解或XML配置文件中的<property>标签互相依赖对方的属性。例如:

public class A {
    @Autowired
    private B b;
}

public class B {
    @Autowired
    private A a;
}

在此例中,Bean A依赖于Bean B的属性,而Bean B反过来依赖于Bean A的属性,形成了属性循环依赖。

2. 循环依赖问题的影响

循环依赖会导致Spring容器在创建和初始化Bean时陷入困境:

  • 构造器循环依赖:由于构造函数必须在对象实例化阶段完成调用,而每个Bean的实例化都需要其依赖项已经实例化,因此在构造器循环依赖的情况下,Spring无法通过常规手段创建任何一个Bean的实例,最终导致Bean无法初始化,通常会抛出BeanCurrentlyInCreationException异常。
  • 属性循环依赖:虽然属性注入可以在对象实例化之后进行,但若不采取特殊处理,Spring仍会在依赖注入阶段遇到同样的问题:在为一个Bean注入依赖时,发现依赖对象尚未完成初始化,进而尝试初始化依赖对象,却又发现依赖于原Bean,形成死循环。

3. Spring如何解决属性循环依赖

Spring通过其独特的三级缓存机制有效地解决了属性循环依赖问题:

a. 三级缓存结构

  • singletonObjects(一级缓存):存放完全初始化好的单例Bean。
  • earlySingletonObjects(二级缓存):存放提前曝光的原始对象(未完成全部初始化过程),用于解决循环依赖。
  • singletonFactories(三级缓存):存放Bean工厂对象(ObjectFactory<?>),可用来生产尚未完全初始化的Bean实例。

b. 解决过程

  1. 实例化:当Spring开始创建Bean A时,首先实例化Bean A的原始对象(调用无参构造函数),并将其放入三级缓存。
  2. 依赖注入:接着,Spring尝试为Bean A注入属性。在处理Bean B的依赖时,发现Bean B尚在创建中。这时,Spring会尝试从缓存中查找Bean B:
    • 如果在一级缓存中找到Bean B(已完全初始化),则直接注入。
    • 如果在二级缓存中找到Bean B(已提前曝光),则直接注入。
    • 如果在三级缓存中找到Bean B的工厂对象,利用工厂创建Bean B的原始对象并放入二级缓存,然后注入到Bean A。
  1. 提前曝光:如果在三级缓存中也未找到Bean B,说明Bean B的创建尚未开始。此时,Spring会将Bean A的原始对象提前曝光到二级缓存中,供后续Bean B创建过程中使用。
  2. 继续初始化:完成Bean A的依赖注入后,Spring继续对Bean A进行其余的初始化操作(如@PostConstruct方法调用等),最终将完全初始化好的Bean A放入一级缓存。
  3. Bean B的创建:当Spring开始创建Bean B时,重复上述过程。由于Bean A已经在二级缓存中提前曝光,因此在处理Bean B的依赖时,可以从二级缓存中获取到Bean A的原始对象,完成Bean B的依赖注入。后续Bean B完成初始化后,也会被放入一级缓存。

通过上述过程,Spring巧妙地打破了属性循环依赖的死锁,使得相互依赖的Bean能够按需获取对方的原始对象,完成各自的初始化过程。

4. 构造器循环依赖无法解决

值得注意的是,Spring 无法解决构造器循环依赖。因为构造器注入要求依赖对象在创建Bean实例时就必须可用,而循环依赖情况下,两个Bean都无法在对方创建之前完成自身的实例化。因此,对于构造器循环依赖,Spring IoC容器会抛出异常,提示开发者需要调整依赖关系以避免循环。

总结来说,Spring通过三级缓存机制有效解决了属性(setter)循环依赖问题,允许相互依赖的Bean在初始化过程中打破循环,顺利完成创建。而对于构造器循环依赖,Spring无能为力,开发人员需要自行避免此类依赖结构的设计。

  • 20
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值