概述
在Spring框架中,IoC(Inversion of Control,控制反转)是一个核心概念,它允许容器管理对象的生命周期和依赖关系。然而,在实际应用中,我们可能会遇到对象间的循环依赖问题。本文将深入探讨Spring如何解决IoC中的循环依赖问题,并通过手写源码的方式,让你对其底层原理有一个全新的认识。
功能点
- 循环依赖的定义:两个或多个Bean相互依赖,形成闭环。
- Spring的解决方案:通过三级缓存机制解决循环依赖问题。
- 源码实现:手写核心代码,展示Spring如何解决循环依赖。
背景
在Spring中,获取一个单例Bean需要经过从缓存中查询、创建对象、依赖注入、将对象放入单例Bean缓存等步骤。当没有循环依赖时,这个流程运行得很好。但一旦出现循环依赖,就会出现问题。例如,Bean C在依赖注入Bean B时,缓存中没有Bean B(因为此时Bean B还没有完成创建),只能创建Bean B,从而导致重复创建(因为此时Bean B已经在创建中)。
业务点
Spring通过引入三级缓存机制来解决循环依赖问题:
- 一级缓存(singletonObjects):存储创建完毕的单例Bean。
- 二级缓存(earlySingletonObjects):存储已实例化但未完成创建的单例Bean。
- 三级缓存(singletonFactories):存储单例Bean对应的ObjectFactory,用于生成Bean实例。
底层原理
三级缓存的作用
- singletonObjects:缓存完整的Bean对象。
- earlySingletonObjects:缓存早期的Bean对象,即Bean的生命周期还未完全走完,但已经可以提前暴露给其他Bean使用。
- singletonFactories:缓存ObjectFactory,用于在需要时创建Bean实例。
解决循环依赖的流程
- 创建Bean实例:通过反射调用构造方法创建Bean的原始对象。
- 判断是否存在循环依赖:如果Bean正在创建中,则存在循环依赖。
- 将Bean实例放入三级缓存:创建一个ObjectFactory并放入singletonFactories中,ObjectFactory的getObject方法会返回Bean的实例。
- 依赖注入:在依赖注入过程中,如果发现依赖的Bean不存在于一级缓存和二级缓存中,则从三级缓存中获取ObjectFactory并创建Bean实例。
- 将Bean实例放入二级缓存:如果Bean实例是通过ObjectFactory创建的,则将其放入earlySingletonObjects中。
- 完成Bean的生命周期:包括属性填充、AOP代理生成等步骤。
- 将Bean实例放入一级缓存:最终将完整的Bean对象放入singletonObjects中。
源码实现
下面是一个简化的源码实现,用于展示Spring如何解决循环依赖问题:
java复制代码 public class DefaultSingletonBeanRegistry { // 一级缓存 private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); // 二级缓存 private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16); // 三级缓存 private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); // 判断Bean是否正在创建中 private final Set<String> singletonsCurrentlyInCreation = new HashSet<>(16); public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) { Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { synchronized (this.singletonObjects) { singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && singletonFactory != null) { singletonObject = singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } return singletonObject; } public boolean isSingletonCurrentlyInCreation(String beanName) { return this.singletonsCurrentlyInCreation.contains(beanName); } public void beforeSingletonCreation(String beanName) { this.singletonsCurrentlyInCreation.add(beanName); } public void afterSingletonCreation(String beanName) { this.singletonsCurrentlyInCreation.remove(beanName); this.singletonObjects.put(beanName, this.earlySingletonObjects.remove(beanName)); } }
应用实践
示例1:基本循环依赖
java复制代码 @Component public class A { @Autowired private B b; } @Component public class B { @Autowired private A a; }
在这个例子中,A和B相互依赖,Spring通过三级缓存机制能够成功解决循环依赖问题。
示例2:AOP代理下的循环依赖
java复制代码 @Component public class A { @Autowired private B b; } @Component @Aspect public class B { @Autowired private A a; }
在这个例子中,B是一个切面,Spring会在创建B的代理对象时处理循环依赖问题。
优缺点
优点
- 简化依赖管理:通过IoC容器管理Bean的依赖关系,降低了代码耦合度。
- 提高可测试性:方便使用Mock对象进行单元测试。
- 解决循环依赖:通过三级缓存机制有效解决了循环依赖问题。
缺点
- 性能损耗:三级缓存机制增加了Bean的创建成本。
- 复杂性增加:对于开发者来说,理解三级缓存机制需要一定的学习成本。
总结
通过本文的深入剖析,相信你对Spring IoC循环依赖的底层源码有了全新的认识。Spring通过三级缓存机制巧妙地解决了循环依赖问题,使得开发者能够更加方便地管理Bean的依赖关系。同时,我们也看到了Spring在解决复杂问题时所展现出的智慧和优雅。