Spring三级缓存解决循环依赖全面解析
- 1、什么是循环依赖
- 2、Spring是如何解决单例setter循环依赖的?
- 3、三级缓存的结构分别是什么,分别存放的是什么样的bean?
- 4、三级缓存的查询顺序是什么?
- isSingletonCurrentlyInCreation
- beforeSingletonCreation
- 5、三级缓存放置和删除的时机?
- 一级缓存
- 二级缓存
- 三级缓存
- 6、spring如何通过三级缓存解决循环依赖?
- 7、为什么存在第三级缓存?
- 8、第三级缓存的接口回调机制是什么?
1、什么是循环依赖
循环依赖主要分三种:
- 构造器的循环依赖:解决不了,直接抛出异常,因为bean工厂在实例化bean的时候通过推断构造方法后进行反射创建bean实例,此时还没有出现三级缓存
- 单例setter注入的循环依赖:通过spring的三级缓存进行解决
- 非单例bean的循环依赖:解决不了
本文主要介绍单例setter注入的循环依赖问题
bean a 依赖 bean b,bean b同时依赖bean a的情况就属于循环依赖;
示例代码如下:
A实体:
public class A {
private B b;
public B getB() {
return b;
}
public void setB(B b) {
this.b = b;
}
private String result;
public String getResult() {
return result;
}
public void setResult(String result) {
this.result = result;
}
@Override
public String toString() {
return "A{" +
"b=" + b +
'}';
}
B实体:
public class B {
private A a;
public A getA() {
return a;
}
public void setA(A a) {
this.a = a;
}
@Override
public String toString() {
return "B{" +
"a=" + a +
'}';
}
}
配置xml文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<bean id="a" class="com.shuang.entity.A">
<property name="b" ref="b"/>
<property name="result" value="spring.xml result"/>
</bean>
<bean id="b" class="com.shuang.entity.B">
<property name="a" ref="a"/>
</bean>
</beans>
2、Spring是如何解决单例setter循环依赖的?
Spring容器是通过三级缓存解决循环依赖的,我相信很多小伙伴都知道这句话,但是如果你回答不出以下问题,那么就跟着我一起好好研究一下三级缓存的原理吧
- 三级缓存的结构分别是什么,分别存放的是什么样的bean?
- 三级缓存的查询顺序是什么?
- 三级缓存放置和删除的时机?
- spring如何通过三级缓存解决循环依赖?
- 为什么存在第三级缓存?或者如果只有第一二级缓存程序是否能正常运行?
- 第三级缓存的接口回调机制是什么?
跟着我的步伐,我们一起往下分析
3、三级缓存的结构分别是什么,分别存放的是什么样的bean?
/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
- singletonObjects是三级缓存中一级缓存,通过使用并发容器ConcurrentHashMap就知道它访问频率最高,是三级缓存中最先被查找是否存在的缓存,它存放的是完整的bean实例(完成实例化CreateBeanInstance(),完成初始化InitializeBean()包括注入自定义属性PopulateBean()、注入容器属性invokeAwareMethod()以及BeanPostProcessor前置后置处理方法、InitializeBean接口的afterPropertiesSet())
- earlySingletonObjects是三级缓存中的第二级缓存,它存放的是实例化好的bean
- singletonFactories是三级缓存中的第三级缓存,它存放的是ObjectFactory函数式接口,至于为什么是接口,后续为大家解释。
函数式接口ObjectFactory
@FunctionalInterface
public interface ObjectFactory<T> {
/**
* Return an instance (possibly shared or independent)
* of the object managed by this factory.
* @return the resulting instance
* @throws BeansException in case of creation errors
*/
T getObject() throws BeansException;
}
4、三级缓存的查询顺序是什么?
在实例化bean的方法调用链中,可以明确得知三级缓存的查询顺序
getBean() -> doGetBean ->getSingleton(beanName, true)
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 首先查询一级缓存singletonObjects
Object singletonObject = this.singletonObjects.get(beanName);
// 一级缓存不存在且当前单例bean正在创建中,isSingletonCurrentlyInCreation下面介绍
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
// 查询二级缓存
singletonObject = this.earlySingletonObjects.get(beanName);
// 二级缓存为空且允许查询早期引用,注意这是getSingleton方法第二个入参为true
if (singletonObject == null && allowEarlyReference) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
// 三级缓存不为空
if (singletonFactory != null) {
// 函数回调,后面介绍
singletonObject = singletonFactory.getObject();
// 将三级缓存放到二级缓存
this.earlySingletonObjects.put(beanName, singletonObject);
// 移除三级缓存
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
显然三级缓存查询的顺序是从一级缓存到二级缓存在到三级缓存,三级缓存的回调机制和时机后面会为大家详细介绍
isSingletonCurrentlyInCreation
public boolean isSingletonCurrentlyInCreation(String beanName) {
return this.singletonsCurrentlyInCreation.contains(beanName);
}
beforeSingletonCreation
加入singletonsCurrentlyInCreation的时机
registerBeanPostProcessors() 注册拦截bean创建的bean处理器
refresh()->registerBeanPostProcessors(beanFactory)->getSingleton()
->beforeSingletonCreation(beanName);
protected void beforeSingletonCreation(String beanName) {
// 添加入singletonsCurrentlyInCreation的时机
if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
}
5、三级缓存放置和删除的时机?
一级缓存
生成完整对象之后放到一级缓存,删除二三级缓存
整个调用链路如下:
refresh()->finishBeanFactoryInitialization()->preInstantiateSingletons()->getBean()
->doGetBean()->getSingleton()->addSingleton(beanName, singletonObject)
此时bean已经完成实例化、初始化
protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
// 放入一级缓存
this.singletonObjects.put(beanName, singletonObject);
// 移除三级缓存
this.singletonFactories.remove(beanName);
// 移除二级缓存
this.earlySingletonObjects.remove(beanName);
// 添加到已注册bean集合
this.registeredSingletons.add(beanName);
}
}
二级缓存
第一次从三级缓存确定对象是代理对象还是原始对象的时候,同时删除三级缓存
整个调用链路如下:
refresh()->finishBeanFactoryInitialization()->preInstantiateSingletons()->getBean()->doGetBean()->getSingleton()
看到这个调用链路可能有小伙伴会问,这不是实例化bean 之前,首先去查看getSingleton()是否有一二三级缓存吗,首次查看不应该都是空吗?
首先明确一点,存入二级缓存时解决循环依赖已经开始了
在出现循环引用时,a引用b,b引用a,a实例化完放入三级缓存,执行populateBean方法设置b,在容器中通过getBean(b)查找,不存在则实例化b,b执行populateBean设置a,此时在容器中查找getBean(a)->getSingleton()中获取到a的三级缓存,将三级缓存加入到二级缓存,删除三级缓存(此过程查看“三级缓存的查询顺序是什么”)
三级缓存
在bean实例化完成后,也就是执行完createBeanInstance(),但是并未初始化(populatebean()、initializeBean())时,将引用提前暴露到三级缓存
整个调用链路如下:
refresh()->finishBeanFactoryInitialization()->preInstantiateSingletons()->getBean()
->doGetBean()->getSingleton()->createBean()->doCreateBean()
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);
}
}
}
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
return exposedObject;
}
6、spring如何通过三级缓存解决循环依赖?
总:什么是循环依赖问题,A依赖B,B依赖C,C依赖A
分:先说明bean创建过程:实例化,初始化(填充属性)
1.先创建A对象,实例化A对象,此时A对象中的b属性为空
2.从容器中查找B对象,如果找到了,直接赋值不存在循环依赖问题(不通),找不到直接创建B对象
3.实例化B对象,此时B对象中的a属性为空,填充属性a
4.从容器中查找A对象,找不到,直接创建
此时,如果仔细琢磨的话,会发现A对象,是存在的,只不过此时的A对象不是一个完整的状态,只完成了实例化但是未完成初始化,如果在程序调用过程中,拥有了某个对象的引用,能否在后期给他完成赋值操作,可以优先把非完整状态的对象优先赋值,等待后续操作来完成赋值,相当于提前暴露了某个不完整对象的引用,所以解决问题的核心在于实例化和初始化分开操作,这也是解决循环依赖问题的关键,
当所有的对象都完成实例化和初始化操作之后,还要把完整对象放到容器中,此时在容器中存在对象的几种状态,完成实例化但未完成初始化,完整状态,因为都在容器中,所以要使用不同的map结构来进行存储,此时就有了一级缓存和二级缓存,如果一级缓存中有了,那么二级缓存中就不会存在同名的对象,因为他们的查找顺序是1,2,3这样的方式来查找的。一级缓存中放的是完整对象,二级缓存中放的是非完整对象,
7、为什么存在第三级缓存?
在探讨为什么存在第三级缓存,首先要先了解Spring两大特性IOC和AOP。
前置知识点1、
学习spring框架的同学都知道,IOC是控制反转,AOP是面向切面,但其实AOP是IOC的拓展之一,IOC的控制反转也就是通过将各种各样的bean(xml配置也好、注解标记也好)全部转换为BeanDefinition,然后通过推断构造方法,利用反射去创建bean实例(对应createBeanInstance()方法),然后再注入自定义属性(对应populateBean()方法),注入容器属性(对应invokeAwareMethod()方法),BeanPostProcessor前后置拦截器,这里就是注入aop的地方,底层是通过jdk、cglib实现。
前置知识点2、
普通对象和代理对象是不能同时出现在容器中的。
前置知识点就了解到这里,只需要知道Aop是IOC中bean生命周期的拓展之一就行了。
那么问题来了,aop在BeanPostProcessor前后置拦截器中通过动态代理实现,但是bean的生命周期中,将实例提前暴露到三级缓存singletonFactories中时,bean只是完成了实例化,并没有开始初始化,甚至用不用实现动态代理都未可知;
为什么需要三级缓存?三级缓存的value类型是ObjectFactory,是一个函数式接口,存在的意义是保证在整个容器的运行过程中同名的bean对象只能有一个。
普通对象和代理对象是不能同时出现在容器中的,因此当一个对象需要被代理的时候,就要使用代理对象覆盖掉之前的普通对象,在实际的调用过程中,是没有办法确定什么时候对象被使用,所以就要求某个对象被调用的时候,优先判断此对象是否需要被代理,类似于一种回调机制的实现,因此传入lambda表达式的时候,可以通过lambda表达式来执行对象的覆盖过程,getEarlyBeanReference()
因此,所有的bean对象在创建的时候要优先放到三级缓存中,在后续的使用过程中,如果需要被代理则返回代理对象,如果不需要被代理,则直接返回普通对象
8、第三级缓存的接口回调机制是什么?
第三级缓存是通过addSingletonFactory()方法添加,并且入参是ObjectFactory<?> singletonFactory的函数式接口
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
// return 要公开为bean引用的对象
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
// 如果出现代理对象就需要覆盖
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
return exposedObject;
}
这么做的目的到底是为了什么?
前面我们分析提到,没有办法确定对象什么时候被调用,在遇到循环依赖时,创建a->实例化a->将包含a的lambda加入到三级缓存->populateBean()->注入b属性值->当前容器没有b->创建b->实例化b->将包含b的lambda加入到三级缓存中->populateBean()->注入a属性->从三级缓存中拿到包含a的lambda->执行getEarlyBeanReference()如果是代理对象,就将代理对象覆盖原始对象
也就是说bean b 在注入a实例的时候,会去调用singletonFactory.getObject()方法,该方法正是a加入三级缓存时的getEarlyBeanReference(beanName, mbd, bean),前面说到没有办法确定对象什么时候被调用,那么就通过函数回调的机制,在调用时确定是否aop或者其他动态代理的对象
可能有同学就会接着问到,bean a在实例化后(createBeanInstance方法执行后)就加入到三级缓存,在populateBean()时注入bean b,而bean b在注入bean a时通过三级缓存的回调方法确定bean a为原始对象还是代理对象后,在加入到二级缓存,删除三级缓存,那么bean b初始化后,回到bean a在完成PopulateBean、initializeBean后,就会有个问题,此时bean a跟 bean b中的bean a是同一个吗?
当然不是,接着往下看
调用链路如下:
refresh()->finishBeanFactoryInitialization()->preInstantiateSingletons()->getBean()
->doGetBean()->getSingleton()->createBean()->doCreateBean()
以下是简化后的源码
// 此时已经完成实例化
// 单例&&允许循环引用&&当前正在创建
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
.....
// 加入三级缓存
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
// Initialize the bean instance.
// 将实例化后的bean赋值给exposeObject
Object exposedObject = bean;
try {
// 自定义属性赋值
populateBean(beanName, mbd, instanceWrapper);
// 初始化工作
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
catch (Throwable ex) {
.....
}
if (earlySingletonExposure) {
// 获取二级引用,如果当前bean a是已经发生循环依赖,那么在bean b注入bean a的时候,获取a的三级缓存getEarlyBeanReference(beanName, mbd, bean)的返回值,并且移除三级缓存,加入到二级缓存
// getSingleton()方法第二个入参为false,只会查到第二级缓存,也就是说如果没有发生循环依赖,那么第二级缓存一定是为null
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
// 发生循环依赖
// 注意这里非常重要
// bean当前停留在实例化后
// exposedObject经过initializeBean()方法初始化
if (exposedObject == bean) {
// 如果相等,则说明bean a在自身创建过程中没有被动态代理,这样做的目的就是保证bean a全局的唯一
exposedObject = earlySingletonReference;
}
else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
......
}
}
}
以上便是Spring三级缓存解决循环依赖全面解析,如有解析不当,欢迎在评论区指出!