这是我参与更文挑战的第2天。
一、什么是循环依赖?
在搞清楚spring是怎么解决循环依赖问题之前,我们需要先了解老生常谈的循环依赖指的是什么?这里举一个例子:
@Service
public class A{
@Autowired
private B b;
}
@Service
public class B{
@Autowired
private A a;
}
复制代码
上面的例子中我们声明了两个Bean,分别是A和B,但是在Bean A中注入了B,在Bean B中又注入了A。
我们知道spring容器就是最大的功能就是替我们创建和管理对象,spring通过java的反射机制进行对象的实例化,接着会对属性赋值完成对象的初始化。所以上面的例子中
- 先创建A对象,完成对象的实例化。此时A对象中的属性b为空,填充属性b
- 从容器中查找B对象,此时B还没完成初始化,那么spring就会创建B对象
- 创建B对象,完成B的实例化。但此时B对象中的a属性为空,那么此时又会查找创建A
上述场景如果不做针对性的设计,那么就会造成循环依赖的问题。
二、spring是如何解决循环依赖的问题?
其实如果我们仔细想想,会发现当代码走到上面第三步的时候,A对象已经完成了实例化,但是未完成初始化。如果在程序调用的过程中,拥有了某个对象的引用,那么我们是可以在后面再给这个对象完成赋值操作。这是我们可以先把这个未完成状态的A对象先赋值给B,等待后续操作完成对A的赋值。相当于暴露了A这个未完成对象的引用,所以解决问题的核心在于实例化和初始化分开操作,而这也是解决循环依赖问题的核心。
那么我们再想想,这么做spring容器中就会有两种状态的对象,一种是实例化完成但是还没初始化的对象,可以把它当成一个半成品对象。还有一种是实例化也初始化完成的对象,这个也是我们真正需要的bean。
所以spring为了解决循环依赖的问题,做了三级缓存的设计。
三级缓存
我们都知道spring容器的本质其实就是用map存储我们的对象。spring的一级缓存放的就是我们真正的对象,二级缓存放的是我们已经实例化但是还没有初始化的对象。如果一级缓存查到了bean,那么二级缓存就不会存在同名的对象。
按上述的思路,其实我们只需要两个map来保存两种不同状态的bean对象就可以了,那为什么会有第三级缓存呢?
//三级缓存 存放bean工厂对象用于解决循环依赖
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
复制代码
源码中可以看到spring设计的第三级缓存的value是一个工厂对象,是一个函数式接口,它存在的意义是整个容器在运行过程中同名的bean对象只能有一个。
普通对象和代理对象是不能同时出现在容器当中的,因此当一个对象需要被代理的时候,就要覆盖掉之前普通的对象,可是在实际的调用当中,是没有办法确定对象什么时候被调用。因此就要求当某个对象被调用时,优先判断此对象是否需要被代理,类似一种回调机制的实现。
传入lambda表达式的时候,可以通过lambda表达式来执行对象的覆盖过程。
// 加入三级缓存
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
复制代码
综上,所有的bean对象在创建的时候都要优先放到三级缓存中,在后续的使用过程中,需要被代理的则返回代理对象,不需要的则返回普通对象。
源码
关于三级缓存,我们可以到源码中去验证一波
AbstractBeanFactory#doGetBean方法中会有一个getSingleton方法
Object sharedInstance = getSingleton(beanName);
复制代码
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
//判断单例池中的对象是否为空,并且判断这个对象是否在创建过程当中
// 获取不到,并且当前需要获取的bean正在创建中
// 第一次容器初始化触发getBean(A)的时候,这个isSingletonCurrentlyInCreation判断一定为false
// 这个时候就会去走创建bean的流程,创建bean之前会先把这个bean标记为正在创建
// 然后A实例化之后,依赖注入B,触发B的实例化,B再注入A的时候,会再次触发getBean(A)
// 此时isSingletonCurrentlyInCreation就会返回true了
// 当前需要获取的bean正在创建中时,代表出现了循环依赖(或者一前一后并发获取这个bean)
// 这个时候才需要去看二、三级缓存
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
// 从三级缓存删除
// 也就是说对于一个单例bean,ObjectFactory#getObject只会调用到一次
// 获取到早期bean实例之后,就把这个bean实例从三级缓存升级到二级缓存了
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
复制代码
可以看到我们的二级缓存earlySingletonObjects就是在这个方法中put进去的,那么我们可以推断所有bean对象是先放入三级缓存中。
我们来看下加入三级缓存的地方,在doCreateBean方法中:
//判断是否允许循环依赖
// 重点是这里了,如果是单例bean&&允许循环依赖&&当前bean正在创建
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isDebugEnabled()) {
logger.debug("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
//第四次调用后置处理器,判断是否需要aop
// 加入三级缓存
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, 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);
}
}
}
复制代码
以上,感谢阅读。























![[桜井宁宁]COS和泉纱雾超可爱写真福利集-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/4d3cf227a85d7e79f5d6b4efb6bde3e8.jpg)
![[桜井宁宁] 爆乳奶牛少女cos写真-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/d40483e126fcf567894e89c65eaca655.jpg)
