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容器是通过三级缓存解决循环依赖的,我相信很多小伙伴都知道这句话,但是如果你回答不出以下问题,那么就跟着我一起好好研究一下三级缓存的原理吧

  1. 三级缓存的结构分别是什么,分别存放的是什么样的bean?
  2. 三级缓存的查询顺序是什么?
  3. 三级缓存放置和删除的时机?
  4. spring如何通过三级缓存解决循环依赖?
  5. 为什么存在第三级缓存?或者如果只有第一二级缓存程序是否能正常运行?
  6. 第三级缓存的接口回调机制是什么?

跟着我的步伐,我们一起往下分析

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或者其他动态代理的对象

Spring 使用三级缓存来解决循环依赖问题 spring 三级缓存 循环依赖_三级缓存


可能有同学就会接着问到,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三级缓存解决循环依赖全面解析,如有解析不当,欢迎在评论区指出!