作者:天堂调度长 | 来源:互联网 | 2023-10-12 13:24
1 循环依赖介绍
循环依赖是指两个或两个以上bean互相持有对方最终形成闭环。比如A依赖B,B依赖C,C依赖A

循环依赖包括构造器依赖和属性依赖
2 三级缓存解决循环依赖
2.1 spring创建Bean步骤
spring创建bean主要有3个步骤
1 createBeanInstance(实例化bean)
2 populateBean(装配bean)
3 initializeBean(初始化bean)
发生循环依赖的时候主要是在第2步
2.2 三级缓存介绍
spring管理的对象默认是单例的,那么肯定有一个地方来缓存这些对象。spring是通过三级缓存来缓存对象的
/** 一级缓存:bean name和bean实例的缓存 */
private final Map singletOnObjects= new ConcurrentHashMap<>(256);/** 三级缓存:对象工厂缓存 */
private final Map> singletOnFactories= new HashMap<>(16);/** 二级缓存:提前早期对象的缓存,还没完成初始化 */
private final Map earlySingletOnObjects= new HashMap<>(16);
实例化bean之后会先走如下代码
protected void addSingletonFactory(String beanName, ObjectFactory> singletonFactory) {Assert.notNull(singletonFactory, "Singleton factory must not be null");synchronized (this.singletonObjects) {if (!this.singletonObjects.containsKey(beanName)) {this.singletonFactories.put(beanName, singletonFactory);this.earlySingletonObjects.remove(beanName);this.registeredSingletons.add(beanName);}}
}
这段代码的作用是把实例化的bean从二级缓存移除,放到三级缓存
获取bean的代码
protected Object getSingleton(String beanName, boolean allowEarlyReference) {// 从一级缓存中获取对象Object singletOnObject= this.singletonObjects.get(beanName);if (singletOnObject== null && isSingletonCurrentlyInCreation(beanName)) {// 如果一级缓存没有并且这个对象正在创建则从二级缓存获取synchronized (this.singletonObjects) {// 从二级缓存获取beansingletOnObject= this.earlySingletonObjects.get(beanName);if (singletOnObject== null && allowEarlyReference) {// 如果二级缓存也没有则从三级缓存获取ObjectFactory> singletOnFactory= this.singletonFactories.get(beanName);if (singletonFactory != null) {// 如果从三级缓存获取成功则把bean从三级缓存移到二级缓存singletOnObject= singletonFactory.getObject();this.earlySingletonObjects.put(beanName, singletonObject);this.singletonFactories.remove(beanName);}}}}return singletonObject;
}
当bean初始化完全后会放入一级缓存
2.3 三级缓存解决循环依赖
假如有一个场景A引用B,B引用A,三级缓存工作原理如下
1 实例化A对象
2 把还未完全初始化的A暴露到三级缓存singletonFactories
3 装配bean
4 发现依赖B,就get B
5 B还没有被创建,则进行创建
6 B依赖A,所以get A,由于第二步已经把A放到三级缓存,所以顺利地拿到A,完成初始化,把自己放到一级缓存中
7 A顺利地拿到B,完成初始化。
三级缓存只能解决通过属性注入的循环依赖,不能解决通过构造函数注入的循环依赖。因为把bean放入三级缓存的前提是执行构造函数
2.4 三级缓存的必要性
如果只解决循环依赖问题,一级缓存足矣。但是如果仅仅使用一级缓存,那么该缓存里存放的对象既有完全初始化的,又有不完全初始化的。
如果拿到不完全初始化的对象很容易出现NPE
有人说,那好,我再建一个缓存,一级缓存用来存完全初始化的bena,二级缓存用来存不完全初始化的bean,可以了吧。这样是可以的,但是如果
一个对象被做成切面,那么该对象就会生成一个代理对象。这样依赖注入的bean仍是原始的bean,spring会抛异常。
为了解决代理对象注入的问题,加个三级缓存,里面不存具体的bean,里面存一个工厂对象。通过工厂对象,是可以拿到最终形态的代理后的bean
如果想深入了解三级缓存的必要性可以参考这位大佬的文章:spring解决循环依赖为何用三级缓存