spring boot服务循环依赖问题详细分析与解决方案

Spring循环依赖与栈溢出问题详细分析

一、问题现象

  • 错误信息: StackOverflowError 在类加载和反射阶段
  • JVM参数: -Xss228k (栈大小仅228KB)
  • 解决方案: 改为 -Xss1m (1MB) 后启动成功

二、Spring框架处理循环依赖的机制

2.1 三级缓存机制

Spring通过三级缓存来解决单例Bean的循环依赖问题:

// Spring容器中的三级缓存
// 第一级缓存:singletonObjects - 完全初始化好的单例Bean
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

// 第二级缓存:earlySingletonObjects - 提前暴露的早期Bean引用(未完全初始化)
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

// 第三级缓存:singletonFactories - 单例Bean的工厂对象(用于创建代理对象)
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

2.2 循环依赖解决流程

DaemonSetAgentInfoService (A) 和 MyAgentInfoService (B) 为例:

正常流程(无循环依赖):
1. 创建A → 实例化A → 填充属性 → 初始化A → 放入singletonObjects
2. 创建B → 实例化B → 填充属性(注入A)→ 初始化B → 放入singletonObjects
循环依赖流程(Spring三级缓存):
步骤1: 开始创建A
  - 实例化A(调用构造函数)
  - 将A的ObjectFactory放入singletonFactories(三级缓存)
  
步骤2: 填充A的属性
  - 发现需要注入B
  - 从缓存中查找B,未找到
  
步骤3: 开始创建B
  - 实例化B(调用构造函数)
  - 将B的ObjectFactory放入singletonFactories(三级缓存)
  
步骤4: 填充B的属性
  - 发现需要注入A
  - 从singletonFactories中获取A的ObjectFactory
  - 调用ObjectFactory.getObject()获取A的早期引用(可能是代理对象)
  - 将A放入earlySingletonObjects(二级缓存)
  - 将A从singletonFactories中移除
  
步骤5: 完成B的初始化
  - 初始化B
  - 将B放入singletonObjects(一级缓存)
  - 将B从earlySingletonObjects中移除
  
步骤6: 继续完成A的初始化
  - 将B注入到A中
  - 初始化A
  - 将A放入singletonObjects(一级缓存)
  - 将A从earlySingletonObjects中移除

2.3 为什么会出现栈溢出?循环依赖的真正影响

关键问题:循环依赖本身不会直接导致StackOverflowError

你的质疑是对的! 没有循环依赖时,如果类结构复杂、调用链深,也可能因为栈空间小触发StackOverflowError。

栈溢出发生的真正原因分析

从错误堆栈可以看到:

java.lang.ClassLoader.defineClass1 (Native Method)
java.lang.
Spring Boot循环依赖指两个或多个bean互相依赖对方,形成闭环,如Bean A依赖于Bean B,而Bean B又依赖于Bean A,这可能使Spring创建bean实例时出现问题,导致应用程序无法启动[^4]。 症状方面,从Spring Boot 2.6.0开始,Spring团队默认禁止循环依赖,原因包括违反单一职责原则、导致难以预测的bean初始化顺序和状态、使问题排查复杂,以及三级缓存机制增加额外的内存开销等,在出现循环依赖时,可能会输出相关的错误日志 [^2]。 解决办法有以下几种: 1. **重构代码**:推荐该方案,可提取公共逻辑到新Service,或使用接口或事件驱动模式,以解耦依赖关系 [^1]。 2. **使用`@Lazy`注解**:这是次优方案,能在一定程度上缓解循环依赖问题 [^1]。 3. **允许循环依赖**:作为临时方案,不过通常不建议长期使用 [^1]。 4. **利用三级缓存**:三级缓存解决循环依赖的根本措施是拆分Bean加载的过程,提前暴露早期实例化Bean,让引用该Bean的其他Bean能顺利加载 [^3]。 ```java // 示例:使用@Lazy注解解决循环依赖 import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; @Service class ServiceA { private final ServiceB serviceB; @Autowired public ServiceA(@Lazy ServiceB serviceB) { this.serviceB = serviceB; } } @Service class ServiceB { private final ServiceA serviceA; @Autowired public ServiceB(@Lazy ServiceA serviceA) { this.serviceA = serviceA; } } ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

daxiang12092205

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值