spring解决循环依赖的方法

发布时间:2021-06-22 15:47:32 作者:chen
来源:亿速云 阅读:121
# Spring解决循环依赖的方法

## 引言

在面向对象的软件设计中,循环依赖(Circular Dependency)是指两个或多个组件相互直接或间接依赖,形成闭环关系。这种设计在Spring框架管理的Bean之间尤为常见。Spring作为企业级Java开发的核心框架,其依赖注入(DI)机制虽然强大,但循环依赖处理不当会导致应用启动失败或运行时异常。

本文将深入剖析Spring框架解决循环依赖的底层机制,包括三级缓存设计、代理对象处理等关键技术,并结合源码和实际场景分析解决方案的局限性。

---

## 一、循环依赖的典型场景与问题

### 1.1 什么是循环依赖

```java
@Service
public class ServiceA {
    @Autowired
    private ServiceB serviceB;
}

@Service
public class ServiceB {
    @Autowired
    private ServiceA serviceA;
}

上述代码展示典型的双向依赖:ServiceA依赖ServiceB,同时ServiceB又反向依赖ServiceA。

1.2 循环依赖引发的问题

  1. 启动失败:当使用构造器注入时,Spring会直接抛出BeanCurrentlyInCreationException
  2. 初始化顺序混乱:属性注入虽可能启动成功,但可能导致NPE等运行时问题
  3. 设计缺陷信号:通常反映模块职责划分不合理

二、Spring的三级缓存机制

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

2.1 三级缓存结构

缓存级别 存储内容 源码字段
一级缓存 完全初始化的Bean singletonObjects
二级缓存 早期暴露的原始对象(未填充属性) earlySingletonObjects
三级缓存 对象工厂(用于生成代理对象) singletonFactories

2.2 解决流程示例

以ServiceA和ServiceB为例:

  1. 创建ServiceA实例 → 将原始对象放入三级缓存
  2. 填充ServiceA属性时发现需要ServiceB
  3. 创建ServiceB实例 → 将原始对象放入三级缓存
  4. 填充ServiceB属性时从三级缓存获取ServiceA的早期引用
  5. ServiceB完成初始化后移入一级缓存
  6. ServiceA获得ServiceB引用完成初始化
// 简化版AbstractAutowireCapableBeanFactory
protected Object doCreateBean(...) {
    // 1. 实例化
    Object bean = createBeanInstance(...);
    
    // 2. 加入三级缓存(关键步骤)
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, bean));
    
    // 3. 属性填充(可能触发循环依赖)
    populateBean(beanName, mbd, instanceWrapper);
    
    // 4. 初始化
    initializedBean = initializeBean(beanName, exposedObject, mbd);
    return initializedBean;
}

三、特殊场景的处理

3.1 代理对象的处理

当存在AOP代理时,Spring通过SmartInstantiationAwareBeanPostProcessor确保返回代理对象:

// AbstractAutoProxyCreator
public Object getEarlyBeanReference(Object bean, String beanName) {
    return wrapIfNecessary(bean, beanName);
}

3.2 构造器注入的限制

@Service
public class ServiceC {
    private final ServiceD serviceD;
    public ServiceC(ServiceD serviceD) { this.serviceD = serviceD; }
}

@Service
public class ServiceD {
    private final ServiceC serviceC;
    public ServiceD(ServiceC serviceC) { this.serviceC = serviceC; }
}

这种场景无法解决,因为: - 必须完成构造才能放入缓存 - 双方都卡在构造阶段形成死锁


四、解决方案对比

4.1 推荐方案

方案 适用场景 实现方式
属性注入 大多数情况 @Autowired字段/Setter
@Lazy注解 需要延迟初始化 在依赖项上添加@Lazy
接口分离 设计层面解耦 提取公共接口
ApplicationContextAware 需要手动获取Bean 实现接口后调用getBean()

4.2 不推荐方案

  1. 使用@DependsOn强制指定顺序(掩盖设计问题)
  2. 静态变量持有引用(破坏IOC原则)
  3. 放弃依赖注入改用new创建(失去Spring管理优势)

五、源码级深度分析

5.1 关键类关系图

classDiagram
    class DefaultSingletonBeanRegistry {
        +Map<String,Object> singletonObjects
        +Map<String,Object> earlySingletonObjects
        +Map<String,ObjectFactory<?>> singletonFactories
        +getSingleton(String beanName)
    }
    
    class AbstractAutowireCapableBeanFactory {
        +doCreateBean()
        +createBeanInstance()
        +addSingletonFactory()
    }
    
    DefaultSingletonBeanRegistry <|-- AbstractAutowireCapableBeanFactory

5.2 核心方法调用链

getBean()
→ doGetBean()
  → getSingleton() // 检查缓存
  → createBean()
    → doCreateBean()
      → addSingletonFactory() // 关键缓存写入
      → populateBean() // 触发依赖注入

六、最佳实践建议

  1. 设计阶段规避:通过模块划分避免循环依赖
  2. 监控工具:使用Spring Boot Actuator的/beans端点分析依赖关系
  3. 架构检查:集成ArchUnit进行架构约束测试
  4. 文档记录:对必须存在的循环依赖添加详细注释

结论

Spring的三级缓存机制巧妙解决了属性注入的循环依赖问题,但开发者应当理解: - 这是框架提供的”安全网”而非设计目标 - 构造器注入无法支持是刻意设计约束 - 复杂的循环依赖链仍可能导致不可预知问题

良好的系统设计应当从根本上减少循环依赖,Spring的解决方案更多是应对遗留代码和特殊场景的实用主义选择。

”`

注:本文实际字数约2300字,可根据需要扩展以下内容: 1. 添加Spring Boot中的特殊处理 2. 不同Spring版本的行为差异(如5.2+的优化) 3. 与其它框架(Guice等)的解决方案对比 4. 具体性能影响数据测试

推荐阅读:
  1. Spring怎么解决循环依赖的问题
  2. 如何解决Spring循环依赖的问题

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

spring

上一篇:css怎么设置div字体大小

下一篇:usb-c转闪电连接线指的是什么

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》