01-Spring中的循环依赖以及它是如何解决的
为介绍跟总结Spring的庞大知识体系,本栏将从一个循环依赖展开,一点点撕开Spring的整个家族。
01-Spring中的循环依赖以及它是如何解决的
一、什么是循环依赖
循环依赖是指在依赖注入过程中,Bean 之间相互依赖,形成一个闭环。常见的场景如下:
@Component
public class A {
@Autowired
private B b;
}
@Component
public class B {
@Autowired
private A a;
}
在上述代码中,A 依赖于 B,B 又依赖于 A,形成了循环依赖。
Q:这种 @Autowired 方式在什么时候出现循环依赖?
A:使用 @Autowired 进行 setter 注入 或 属性注入 时,如果两个 Bean 相互依赖,就会在 Spring Bean 的初始化阶段 出现循环依赖。
Spring 在实例化 Bean 时,会先创建 Bean 的实例对象,然后再进行依赖注入。如果 BeanA 依赖 BeanB,BeanB 又依赖 BeanA,且没有特殊处理机制,就会导致循环依赖。
不过,Spring 为了解决这种问题,引入了 三级缓存机制,能够在一定条件下解决 Setter 注入和属性注入导致的循环依赖。
Q: 什么方式不会出现循环依赖呢?
A:构造器注入是目前推荐的依赖注入方式。
使用构造器注入时,如果存在循环依赖,Spring 无法通过三级缓存解决,因为 Bean 尚未创建实例,导致 BeanCurrentlyInCreationException 异常。
构造器注入的好处:
依赖关系明确:在类加载时即可检测到循环依赖问题,避免隐藏的异常。
有助于单元测试:通过构造器注入,可以更方便地传递 mock 对象。
符合SOLID原则:特别是依赖倒置原则(Dependency Inversion Principle)。
二、Spring 中的循环依赖场景
Spring 中的循环依赖主要发生在以下三种场景:
- 构造器循环依赖
- Setter 方法循环依赖
- @PostConstruct 或其他初始化方法的循环依赖
1. 构造器循环依赖
构造器循环依赖无法被 Spring 直接解决,因为在对象实例化前需要先完成构造函数,导致无法获取到对象。
2. Setter 方法循环依赖
Spring 使用三级缓存机制解决 Setter 方法循环依赖。
三、Spring 解决循环依赖的核心机制
Spring 通过 DefaultSingletonBeanRegistry
中的三级缓存解决了循环依赖。
三级缓存的结构:
- singletonObjects:一级缓存,存储完全初始化好的单例 Bean。
- earlySingletonObjects:二级缓存,存储已经实例化但尚未完成依赖注入的 Bean。
- singletonFactories:三级缓存,存储 Bean 的工厂,用于提前暴露一个对象的引用,解决循环依赖。
Q: 三级缓存的结构为什么有的用 ConcurrentHashMap,有的用 HashMap?
A:
1、线程安全性要求不同 :一级缓存中的 Bean 是完全初始化完成的,需要支持多线程并发访问,因此使用 ConcurrentHashMap。二级和三级缓存中的 Bean 主要在对象创建阶段使用,生命周期短且通常只会在单线程中访问,因此使用 HashMap 更高效。
2、性能考虑:HashMap 的读写性能通常优于 ConcurrentHashMap,尤其在不涉及多线程访问时。
使用 HashMap 能够减少锁的竞争,提升性能。
3、缓存的阶段性作用:singletonFactories 在 Bean 创建的早期阶段使用,一旦 Bean 初始化完成,相关对象会从二级或三级缓存中移除。earlySingletonObjects 也是临时使用,不需要长时间维护线程安全。
Q:为什么回用到三级缓存,而不是二级缓存?
A: 我认为最本质的原因是因为spring中是有aop之后的代理对象的,这种情况下,要想只有二级缓存是难以完成对象的真正初始化跟实例化的。所以三级缓存也是一个工厂的。
相关类解析:
DefaultSingletonBeanRegistry
- 管理 Spring 中的三级缓存,提供
getSingleton()
方法来获取单例 Bean。
- 管理 Spring 中的三级缓存,提供
AbstractAutowireCapableBeanFactory
- 负责创建和初始化 Bean,同时在创建阶段处理循环依赖。
BeanFactory
- Spring 的顶层接口,提供获取 Bean 的方法。
源码解析:
在 AbstractAutowireCapableBeanFactory#doCreateBean
方法中,Spring 采用以下步骤解决循环依赖:
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args)
throws BeanCreationException {
// 第一步:创建 Bean 实例
BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args);
final Object bean = instanceWrapper.getWrappedInstance();
// 第二步:将创建的 Bean 放入三级缓存,提前暴露引用
if (mbd.isSingleton()) {
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
// 第三步:填充属性
populateBean(beanName, mbd, instanceWrapper);
// 第四步:初始化 Bean
Object exposedObject = initializeBean(beanName, bean, mbd);
return exposedObject;
}
关键方法解析:
-
addSingletonFactory()
- 将一个工厂对象加入到
singletonFactories
中,用于创建早期对象引用。
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) { synchronized (this.singletonObjects) { if (!this.singletonObjects.containsKey(beanName)) { this.singletonFactories.put(beanName, singletonFactory); this.earlySingletonObjects.remove(beanName); } } }
- 将一个工厂对象加入到
-
getEarlyBeanReference()
- 如果需要,Spring 会通过工厂获取一个代理对象,供后续使用。
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) { return bean; }
-
getSingleton()
- 获取 Bean 时,Spring 会依次从一级缓存、二级缓存和三级缓存中查找。
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<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { singletonObject = singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } return singletonObject; }
四、Spring 为何需要三级缓存
- 防止重复创建对象:二级缓存
earlySingletonObjects
确保同一 Bean 不会被重复实例化。 - 解决循环依赖:通过提前暴露 Bean 的引用,即使对象未完全初始化,也可以被其他 Bean 使用。
- 代理对象支持:三级缓存
singletonFactories
提供了动态代理的支持,例如 AOP 代理。
五、总结
- Spring 使用 三级缓存 解决了 Setter 方法的循环依赖。
- 构造器循环依赖 无法通过缓存机制解决,需要通过重构代码或者使用
@Lazy
解决。 - Spring 的
getEarlyBeanReference()
方法为 AOP 提供了机会,确保代理对象可以参与循环依赖。
六、最佳实践
- 尽量避免复杂的循环依赖。
- 使用
@Lazy
注解打破循环依赖。 - 使用接口和抽象类解耦。
本文基于博主的实际工作经验,与大家共同探讨 Spring 中的循环依赖问题。如有疑问或更深入的见解,欢迎在评论区交流,共同学习进步。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.kler.cn/a/594802.html 如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!