springboot中循环依赖问题

循环依赖发生在两个或两个以上的bean互相持有对方,形成闭环。Spring框架允许循环依赖存在,并通过三级缓存解决大部分循环依赖问题:

  1. 一级缓存:单例池,缓存已完成初始化的bean对象。

  2. 二级缓存:缓存尚未完成生命周期的早期bean对象。

  3. 三级缓存:缓存ObjectFactory,用于创建bean对象。

解决循环依赖的流程如下:

  1. 实例化A对象,并创建ObjectFactory存入三级缓存。

  2. A在初始化时需要B对象,开始B的创建逻辑。

  3. B实例化完成,也创建ObjectFactory存入三级缓存。

  4. B需要注入A,通过三级缓存获取ObjectFactory生成A对象,存入二级缓存。

  5. B通过二级缓存获得A对象后,B创建成功,存入一级缓存。

  6. A对象初始化时,由于B已创建完成,可以直接注入B,A创建成功存入一级缓存。

  7. 清除二级缓存中的临时对象A。

  1. 构造方法出现了循环依赖怎么解决?

由于构造函数是bean生命周期中最先执行的,Spring框架无法解决构造方法的循环依赖问题。可以使用@Lazy懒加载注解,延迟bean的创建直到实际需要时。

1. 循环依赖的常见场景

  • 场景示例
     

    java

    @Service
    public class A {
        @Autowired
        private B b;
    }
    
    @Service
    public class B {
        @Autowired
        private A a;
    }
    • A依赖BB又依赖A,形成循环依赖。

2. Spring解决循环依赖的机制

​**(1) 三级缓存**

Spring容器通过三个缓存区管理Bean的创建过程:

  1. 一级缓存(singletonObjects)​:存放完全初始化后的单例Bean。
  2. 二级缓存(earlySingletonObjects)​:存放早期暴露的单例Bean(已实例化但未完成属性注入)。
  3. 三级缓存(singletonFactories)​:存放Bean工厂对象,用于生成早期Bean引用。
​**(2) 解决流程**

AB循环依赖为例:

  1. 创建Bean A

    • 实例化A对象(调用构造函数)。
    • 将A的工厂对象(用于生成早期引用)放入三级缓存。
    • 开始注入A的属性,发现需要Bean B。
  2. 创建Bean B

    • 实例化B对象。
    • 将B的工厂对象放入三级缓存。
    • 开始注入B的属性,发现需要Bean A。
  3. 获取Bean A的早期引用

    • 从三级缓存中获取A的工厂对象,生成A的早期引用。
    • 将A的早期引用放入二级缓存,并移除三级缓存中的A工厂。
  4. 完成Bean B的初始化

    • 将A的早期引用注入到B中。
    • B完成初始化后,放入一级缓存。
  5. 完成Bean A的初始化

    • 从一级缓存获取B的实例,注入到A中。
    • A完成初始化后,放入一级缓存,移除二级缓存。

3. 解决循环依赖的前提条件

  • 必须满足以下条件
    1. Bean必须是单例(Singleton)​:原型(Prototype)作用域的Bean无法解决循环依赖。
    2. 依赖注入方式必须为Setter或字段注入:构造器注入无法解决循环依赖(因实例化和注入同时进行)。

4. 构造器注入导致的循环依赖

若使用构造器注入,Spring无法提前暴露Bean的引用,导致循环依赖无法解决。

示例

 

java

@Service
public class A {
    private final B b;
    public A(B b) { this.b = b; } // 构造器注入
}

@Service
public class B {
    private final A a;
    public B(A a) { this.a = a; } // 构造器注入
}

解决方案

  • 改用Setter或字段注入。
  • 使用@Lazy延迟加载其中一个Bean:
     

    java

    @Service
    public class A {
        private final B b;
        public A(@Lazy B b) { this.b = b; } // 延迟加载B
    }

5. 原型(Prototype)作用域的循环依赖

原型Bean每次请求都会创建新实例,Spring无法通过三级缓存解决其循环依赖,会抛出异常:

 

java

BeanCurrentlyInCreationException: Error creating bean with name 'a':
Requested bean is currently in creation: Is there an unresolvable circular reference?

解决方案

  • 避免在原型Bean中使用循环依赖。
  • 使用@Lazy延迟加载其中一个Bean:
     

    java

    @Scope("prototype")
    @Service
    public class A {
        @Autowired
        @Lazy
        private B b;
    }

6. 使用@Lazy注解延迟加载

@Lazy注解可延迟Bean的初始化,直到首次使用时才创建实例,从而打破循环依赖。

示例

 

java

@Service
public class A {
    @Autowired
    @Lazy
    private B b;
}

7. 强制禁止循环依赖

在Spring Boot中可通过配置关闭循环依赖支持:

 

properties

spring.main.allow-circular-references=false

8. 最佳实践

  1. 优先使用Setter/字段注入:避免构造器注入导致无法解决的循环依赖。
  2. 检查设计合理性:循环依赖可能意味着代码耦合度高,建议通过重构解耦。
  3. 利用IDE工具分析依赖:如IntelliJ IDEA的"Analyze -> Analyze Module Dependencies"功能。

总结

Spring通过三级缓存机制和提前暴露未完全初始化的Bean引用,解决了单例Bean的循环依赖问题。开发者需注意:

  • 避免在构造器注入中形成循环依赖。
  • 原型Bean的循环依赖需通过@Lazy或重构解决。
  • 合理设计代码结构,减少不必要的依赖。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值