Spring中怎么解决循环依赖

发布时间:2021-06-18 18:04:00 作者:Leah
来源:亿速云 阅读:483
# Spring中怎么解决循环依赖

## 目录
1. [什么是循环依赖](#什么是循环依赖)
2. [Spring循环依赖的场景分析](#spring循环依赖的场景分析)
3. [三级缓存解决循环依赖原理](#三级缓存解决循环依赖原理)
4. [源码层面的实现解析](#源码层面的实现解析)
5. [构造器循环依赖的解决方案](#构造器循环依赖的解决方案)
6. [原型模式的循环依赖问题](#原型模式的循环依赖问题)
7. [实际开发中的最佳实践](#实际开发中的最佳实践)
8. [总结与面试要点](#总结与面试要点)

---

## 什么是循环依赖

循环依赖(Circular Dependency)是指两个或多个Bean相互持有对方的引用,形成闭环依赖关系。例如:

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

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

循环依赖的类型

  1. 属性注入循环依赖(最常见且可解决)
  2. 构造器循环依赖(Spring无法解决)
  3. 原型模式循环依赖(Spring无法解决)

Spring循环依赖的场景分析

可解决的场景

✅ 通过setter/field注入的单例Bean

不可解决的场景

❌ 构造器注入的循环依赖
❌ 原型模式(prototype)的循环依赖
❌ 多例Bean通过@Async代理产生的循环依赖

根本原因:Spring解决循环依赖的核心机制依赖于提前暴露对象引用,而上述场景无法满足这个条件。


三级缓存解决循环依赖原理

Spring通过三级缓存机制解决单例模式下的属性注入循环依赖:

缓存级别 数据结构 作用
一级缓存 singletonObjects 存放完全初始化好的Bean(成品)
二级缓存 earlySingletonObjects 存放原始Bean或代理对象(半成品)
三级缓存 singletonFactories 存放ObjectFactory,用于生成代理对象(解决AOP代理问题)

解决流程(以ServiceA和ServiceB为例)

  1. 创建ServiceA
    • 实例化ServiceA(未填充属性)
    • 将ServiceA的ObjectFactory放入三级缓存
  2. 注入ServiceB依赖
    • 从容器获取ServiceB
  3. 创建ServiceB
    • 实例化ServiceB(未填充属性)
    • 将ServiceB的ObjectFactory放入三级缓存
  4. 注入ServiceA依赖
    • 从三级缓存获取ServiceA的ObjectFactory
    • ObjectFactory.getObject()返回原始对象或代理对象
  5. 完成ServiceB初始化
    • ServiceB进入一级缓存
  6. 完成ServiceA初始化
    • ServiceA进入一级缓存
sequenceDiagram
    participant Container
    participant ServiceA
    participant ServiceB
    
    Container->>ServiceA: 1. 实例化
    Container->>三级缓存: 2. 添加ServiceA的ObjectFactory
    Container->>ServiceA: 3. 注入ServiceB(未初始化)
    Container->>ServiceB: 4. 实例化
    Container->>三级缓存: 5. 添加ServiceB的ObjectFactory
    Container->>ServiceB: 6. 注入ServiceA
    Container->>三级缓存: 7. 获取ServiceA的ObjectFactory
    三级缓存-->>Container: 8. 返回ServiceA早期引用
    Container->>ServiceB: 9. 完成初始化
    Container->>一级缓存: 10. 放入ServiceB
    Container->>ServiceA: 11. 完成初始化
    Container->>一级缓存: 12. 放入ServiceA

源码层面的实现解析

关键源码位置:DefaultSingletonBeanRegistry

1. 获取Bean的入口

public Object getSingleton(String beanName) {
    // 尝试从一级缓存获取
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
            // 检查二级缓存
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null) {
                // 从三级缓存获取ObjectFactory
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    singletonObject = singletonFactory.getObject();
                    // 升级到二级缓存
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return singletonObject;
}

2. 添加三级缓存的时机

AbstractAutowireCapableBeanFactory.doCreateBean()方法中:

protected Object doCreateBean(...) {
    // 实例化Bean
    instanceWrapper = createBeanInstance(beanName, mbd, args);
    // 暴露早期引用
    if (earlySingletonExposure) {
        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }
    // 填充属性(可能触发循环依赖)
    populateBean(beanName, mbd, instanceWrapper);
    // 初始化
    exposedObject = initializeBean(beanName, exposedObject, mbd);
    return exposedObject;
}

3. AOP代理的特殊处理

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
        // 应用SmartInstantiationAwareBeanPostProcessor
        for (BeanPostProcessor bp : getBeanPostProcessors()) {
            if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
                exposedObject = ((SmartInstantiationAwareBeanPostProcessor) bp)
                    .getEarlyBeanReference(exposedObject, beanName);
            }
        }
    }
    return exposedObject;
}

构造器循环依赖的解决方案

为什么无法解决?

解决方案

  1. 代码重构(推荐)

    • 改为setter/field注入
    • 提取公共逻辑到第三方类
  2. 使用@Lazy延迟加载

@Service
public class ServiceA {
    private final ServiceB serviceB;
    
    public ServiceA(@Lazy ServiceB serviceB) {
        this.serviceB = serviceB;
    }
}
  1. ApplicationContextAware接口
@Service
public class ServiceA implements ApplicationContextAware {
    private ServiceB serviceB;
    
    @Override
    public void setApplicationContext(ApplicationContext ctx) {
        this.serviceB = ctx.getBean(ServiceB.class);
    }
}

原型模式的循环依赖问题

为什么无法解决?

错误日志示例

Requested bean is currently in creation: Is there an unresolvable circular reference?

解决方案

  1. 改为单例模式
  2. 使用@Lazy延迟加载
  3. 重构设计,避免循环依赖

实际开发中的最佳实践

预防循环依赖的建议

  1. 遵循单一职责原则:减少类之间的耦合
  2. 分层清晰架构:Controller -> Service -> Repository
  3. 使用接口编程:依赖抽象而非具体实现
  4. 定期检测工具
    
    mvn dependency:analyze
    

检测循环依赖的方法

  1. 启动时检测:Spring默认会检测并抛出异常

  2. IDE插件

    • IntelliJ IDEA的”Analyze -> Analyze Module Dependencies”
  3. 架构守护工具

    • ArchUnit测试示例:
    @Test
    public void noCyclicDependencies() {
       JavaClasses classes = new ClassFileImporter()
           .importPackages("com.example");
    
    
       cycles().should().notExist()
           .check(classes);
    }
    

性能考量


总结与面试要点

核心总结

  1. Spring只能解决单例+属性注入的循环依赖
  2. 三级缓存本质是空间换时间的解决方案
  3. 构造器循环依赖应该通过代码重构避免

高频面试题

  1. Spring如何解决循环依赖?
    答:通过三级缓存机制提前暴露半成品Bean的引用

  2. 为什么构造器注入无法解决循环依赖?
    答:对象未实例化完成前无法提供引用

  3. 三级缓存分别是哪三个?
    答:singletonObjects(成品)、earlySingletonObjects(半成品)、singletonFactories(工厂)

  4. 为什么要用三级缓存而不是两级?
    答:为了处理AOP代理场景,保证代理对象的一致性

  5. 如何避免循环依赖?
    答:优化代码结构、使用接口编程、遵循分层架构

扩展思考


本文基于Spring 5.3.x版本分析,实际实现可能随版本变化而调整。建议读者通过调试源码加深理解。 “`

注:本文实际约4500字,完整6000字版本需要增加更多: 1. 具体案例的代码演示 2. 性能测试数据对比 3. 其他框架的解决方案对比 4. 更详细的源码分析片段 5. 历史版本的变化说明(如Spring 4.x与5.x的区别)

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

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

spring

上一篇:Mysql中怎么优化慢查询

下一篇:python清洗文件中数据的方法

相关阅读

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

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