什么是循环依赖
循环依赖指bean之间相互依赖,但是注意只能是set注入属性类型(单例)的循环依赖能够被spring解决,如果是在构造函数中则会抛出异常BeanCurrentlyInCreationException
什么是三级缓存
/** 一级缓存 这个就是我们大名鼎鼎的单例缓存池 用于保存我们所有的单实例bean */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/** 三级缓存 该map用户缓存 key为 beanName value 为ObjectFactory(包装为早期对象) */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
/** 二级缓存 ,用户缓存我们的key为beanName value是我们的早期对象(对象属性还没有来得及进行赋值) */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
所谓的三级缓存就是这三个Map,分别存放不同形态的bean(2、3可能一样的),一级缓存存放的已经完成初始化的bean。三级缓存存放的是早期暴露的包装过的工厂。如果没有特殊处理, 那该工厂将会返回一个未完成初始化的bean。二级缓存存放的也是早期对象,但是不是包装的工厂对象,里面的对象属性还未进行赋值。
Spring如何利用三级缓存解决循环依赖问题
bean的实例化流程概述
该方法中调用preInstantiateSingletons()方法,实例化单例。过程如下
- 获取我们容器中所有的bean名称
- 合并bean定义
- 判断是否为工厂bean,调用getBean方法
- 进入到真正获取bean的逻辑中,doGetBean()
- 首先会进行第一次的从缓存中获取bean,getSingleton(beanName)。该方法下面再进行详解
- 如果没拿到,会进行一次判断,是否为正在创建的原型bean。如果是,则抛出BeanCurrentlyInCreationException。spring只能解决setter注入的循环依赖,而不能解决构造器的
- 随后进行父工厂的逻辑处理,一般只有spring整合spring mvc的时候才会出现子父容器。
- 经过之后的一些判断处理,来到创建单例bean的逻辑中,这里再次尝试从缓存中获取,只是参数出现变化,比起上面的第一次,这次多了一个lambda表达式
- 最终会在lambda中createBean进入到doCreateBean方法,进行bean的实例化。createInstance方法,这里是会调用无参或者有参构造。
- 实例化完成之后,会进行早期暴露的条件中,而允许早期暴露的条件有:单例、allowCircularReferences(默认true)和是否处于正在创建中isSingletonCurrentlyInCreation(这个后面细讲)
- 允许早期暴露的话,会将这个bean包装为一个singleFactory对象,并提供一个可以返回该bean的方法。
- 进入属性填充阶段,也就是产生spring能解决的循环依赖的地方populateBean方法。该方法比较复杂,简单理解就是这里会完成该bean的所有属性注入。那么当遇到对象属性时,会调用doGetBean,重新走刚刚的步骤。直到该对象属性也要进行属性赋值的时候。发现他依赖于刚刚第一次创建的bean,调用doGetBean方法,这里就能在第一次从缓存中获取对象拿到早期暴露的bean对象。然后该属性对象就完成了bean的整个创建流程,方法弹栈。回到第一个bean的属性填充,这里就能拿到属性对象,进而解决循环依赖的问题。
进入源码再过一遍
AbstractApplicationContext
refresh()
finishBeanFactoryInitialization(beanFactory); //实例化bean
preInstantiateSingletons()
DefaultListableBeanFactory
public void preInstantiateSingletons() throws BeansException {
if (logger.isDebugEnabled()) {
logger.debug("Pre-instantiating singletons in " + this);
}
//获取我们容器中所有bean定义的名称
List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);
//循环我们所有的bean定义名称
for (String beanName : beanNames) {
//合并我们的bean定义
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
/**
* 根据bean定义判断是不是抽象的&& 不是单例的 &&不是懒加载的
*/
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
//是不是工厂bean
if (isFactoryBean(beanName)) {
//是的话 给beanName+前缀&符号
Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
if (bean instanceof FactoryBean) {
final FactoryBean<?> factory = (FactoryBean<?>) bean;
boolean isEagerInit;
if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
isEagerInit = AccessController.doPrivileged((PrivilegedAction<Boolean>)
((SmartFactoryBean<?>) factory)::isEagerInit,
getAccessControlContext());
}
else {
isEagerInit = (factory instanceof SmartFactoryBean &&
((SmartFactoryBean<?>) factory).isEagerInit());
}
//调用真正的getBean的流程
if (isEagerInit) {
getBean(beanName);
}
}
}
else {//非工厂Bean 就是普通的bean
getBean(beanName);
}
}
}
//该方法这只截取一般,后半部分放在下面
进入getBean
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
/**
* 在这里 传入进来的name 可能是 别名, 也有可能是工厂bean的name,所以在这里需要转换
*/
final String beanName = transformedBeanName(name);
Object bean;
//尝试去缓存中获取对象
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
if (logger.isDebugEnabled()) {
if (isSingletonCurrentlyInCreation(beanName)) {
logger.debug("Returning eagerly cached instance of singleton bean '" + beanName +
"' that is not fully initialized yet - a consequence of a circular reference");
}
else {
logger.debug("Returning cached instance of singleton bean '" + beanName + "'");
}
}
/**
* /*
* 如果 sharedInstance 是普通的单例 bean,下面的方法会直接返回。但如果
* sharedInstance 是 FactoryBean 类型的,则需调用 getObject 工厂方法获取真正的
* bean 实例。如果用户想获取 FactoryBean 本身,这里也不会做特别的处理,直接返回
* 即可。毕竟 FactoryBean 的实现类本身也是一种 bean,只不过具有一点特殊的功能而已。
*/
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}
else {
/**
* spring 只能解决单例对象的setter 注入的循环依赖,不能解决构造器注入
*/
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
/**
* 方法参数 typeCheckOnly ,是用来判断调用 #getBean(...) 方法时,表示是否为仅仅进行类型检查获取 Bean 对象
* 如果不是仅仅做类型检查,而是创建 Bean 对象,则需要调用 #markBeanAsCreated(String beanName) 方法,进行记录
*/
if (!typeCheckOnly) {
markBeanAsCreated(beanName);
}
//创建单例bean
if (mbd.isSingleton()) {
//把beanName 和一个singletonFactory 并且传入一个回调对象用于回调
sharedInstance = getSingleton(beanName, () -> {
try {
//进入创建bean的逻辑
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
//创建bean的过程中发生异常,需要销毁关于当前bean的所有信息
destroySingleton(beanName);
throw ex;
}
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
}
return (T) bean;
}
这里对doGetBean进行了阉割,只留下核心的部分。第一次创建bean的时候,从缓存中肯定是拿不到的,直接到48行,创建单例bean(原型已经被处理过了,这里我将这部分代码删掉了)。
进入createBean
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {
try {
/**
* 该步骤是我们真正的创建我们的bean的实例对象的过程
*/
Object beanInstance = doCreateBean(beanName, mbdToUse, args);
if (logger.isDebugEnabled()) {
logger.debug("Finished creating instance of bean '" + beanName + "'");
}
return beanInstance;
}
catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) {
// A previously detected exception with proper bean creation context already,
// or illegal singleton state to be communicated up to DefaultSingletonBeanRegistry.
throw ex;
}
catch (Throwable ex) {
throw new BeanCreationException(
mbdToUse.getResourceDescription(), beanName, "Unexpected exception during bean creation", ex);
}
}
该方法进行了一些解析判断,这里略去。重点是第9行,进入真正的实例化Bean。
进入doCreateBean真正实例化对象
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
throws BeanCreationException {
//BeanWrapper 是对 Bean 的包装,其接口中所定义的功能很简单包括设置获取被包装的对象,获取被包装 bean 的属性描述器
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {
//从没有完成的FactoryBean中移除
instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) {
//使用合适的实例化策略来创建新的实例:工厂方法、构造函数自动注入、简单初始化 该方法很复杂也很重要
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
//从beanWrapper中获取我们的早期对象
final Object bean = instanceWrapper.getWrappedInstance();
Class<?> beanType = instanceWrapper.getWrappedClass();
if (beanType != NullBean.class) {
mbd.resolvedTargetType = beanType;
}
/**
* 该对象进行判断是否能够暴露早期对象的条件
* 单实例
* this.allowCircularReferences 默认为true
* isSingletonCurrentlyInCreation(表示当前的bean对象正在创建singletonsCurrentlyInCreation包含当前正在创建的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");
}
//把我们的早期对象包装成一个singletonFactory对象 该对象提供了一个getObject方法,该方法内部调用getEarlyBeanReference方法
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
// Initialize the bean instance.
Object exposedObject = bean;
try {
//给我们的属性进行赋值(调用set方法进行赋值)
populateBean(beanName, mbd, instanceWrapper);
//进行对象初始化操作(在这里可能生成代理对象)
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
catch (Throwable ex) {
if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
throw (BeanCreationException) ex;
}
else {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
}
}
//允许早期对象的引用
if (earlySingletonExposure) {
/**
* 去缓存中获取到我们的对象 由于传递的allowEarlyReferce 是false 要求只能在一级二级缓存中去获取
* 正常普通的bean(不存在循环依赖的bean) 创建的过程中,压根不会把三级缓存提升到二级缓存中
*/
Object earlySingletonReference = getSingleton(beanName, false);
//能够获取到
if (earlySingletonReference != null) {
//经过后置处理的bean和早期的bean引用还相等的话(表示当前的bean没有被代理过)
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
//处理依赖的bean
else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
String[] dependentBeans = getDependentBeans(beanName);
Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
for (String dependentBean : dependentBeans) {
if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
actualDependentBeans.add(dependentBean);
}
}
if (!actualDependentBeans.isEmpty()) {
throw new BeanCurrentlyInCreationException(beanName,
"Bean with name '" + beanName + "' has been injected into other beans [" +
StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
"] in its raw version as part of a circular reference, but has eventually been " +
"wrapped. This means that said other beans do not use the final version of the " +
"bean. This is often the result of over-eager type matching - consider using " +
"'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
}
}
}
}
// Register bean as disposable.
try {
//注册销毁的bean的销毁接口
registerDisposableBeanIfNecessary(beanName, bean, mbd);
}
catch (BeanDefinitionValidationException ex) {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
}
return exposedObject;
}
此处第12行,会调用bean的构造函数进行实例化对象。其实到这里能发现,spring通过构造函数实例化对象,当构造函数中存在循环依赖的时候,无法通过三级缓存进行解决。因为都无法完成实例化,更别说早期暴露对象了,对象都莫得。
OK,这里从26行开始,进行早期暴露对象的逻辑。关键在于35行的代码
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
这里就将该bean和这个SingletonFactory绑定起来。可以看一下这个函数接口中的方法
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
//判读我们容器中是否有InstantiationAwareBeanPostProcessors类型的后置处理器
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
//获取我们所有的后置处理器
for (BeanPostProcessor bp : getBeanPostProcessors()) {
//判断我们的后置处理器是不是实现了SmartInstantiationAwareBeanPostProcessor接口
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
//进行强制转换
SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
//挨个调用SmartInstantiationAwareBeanPostProcessor的getEarlyBeanReference
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
return exposedObject;
}
在第16行返回的就是我们放入的bean实例,如果我们没有进行一些增强的话,那么if逻辑是不会进入的。也就是说,通常情况下,该方法返回的就是我们放入的bean。没有任何变化。只是我们将bean封装到这样一个工厂中去了。
属性填充populateBean
属性填充的代码逻辑有些复杂,这里直接进入到对象属性的赋值部分,此处会调用doGetBean去获取对象属性
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
throws BeanCreationException {
//BeanWrapper 是对 Bean 的包装,其接口中所定义的功能很简单包括设置获取被包装的对象,获取被包装 bean 的属性描述器
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {
//从没有完成的FactoryBean中移除
instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) {
//使用合适的实例化策略来创建新的实例:工厂方法、构造函数自动注入、简单初始化 该方法很复杂也很重要
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
//从beanWrapper中获取我们的早期对象
final Object bean = instanceWrapper.getWrappedInstance();
Class<?> beanType = instanceWrapper.getWrappedClass();
if (beanType != NullBean.class) {
mbd.resolvedTargetType = beanType;
}
/**
* 该对象进行判断是否能够暴露早期对象的条件
* 单实例
* this.allowCircularReferences 默认为true
* isSingletonCurrentlyInCreation(表示当前的bean对象正在创建singletonsCurrentlyInCreation包含当前正在创建的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");
}
//把我们的早期对象包装成一个singletonFactory对象 该对象提供了一个getObject方法,该方法内部调用getEarlyBeanReference方法
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
// Initialize the bean instance.
Object exposedObject = bean;
try {
//给我们的属性进行赋值(调用set方法进行赋值)
populateBean(beanName, mbd, instanceWrapper);
//进行对象初始化操作(在这里可能生成代理对象)
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
catch (Throwable ex) {
if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
throw (BeanCreationException) ex;
}
else {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
}
}
//允许早期对象的引用
if (earlySingletonExposure) {
/**
* 去缓存中获取到我们的对象 由于传递的allowEarlyReferce 是false 要求只能在一级二级缓存中去获取
* 正常普通的bean(不存在循环依赖的bean) 创建的过程中,压根不会把三级缓存提升到二级缓存中
*/
Object earlySingletonReference = getSingleton(beanName, false);
//能够获取到
if (earlySingletonReference != null) {
//经过后置处理的bean和早期的bean引用还相等的话(表示当前的bean没有被代理过)
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
//处理依赖的bean
else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
String[] dependentBeans = getDependentBeans(beanName);
Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
for (String dependentBean : dependentBeans) {
if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
actualDependentBeans.add(dependentBean);
}
}
if (!actualDependentBeans.isEmpty()) {
throw new BeanCurrentlyInCreationException(beanName,
"Bean with name '" + beanName + "' has been injected into other beans [" +
StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
"] in its raw version as part of a circular reference, but has eventually been " +
"wrapped. This means that said other beans do not use the final version of the " +
"bean. This is often the result of over-eager type matching - consider using " +
"'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
}
}
}
}
return exposedObject;
}
我们再把这段代码拿过来,还是一样的,这里为了方便说明,我们将第一个bean命名为X,第二个为Y。现在就是在为X的属性赋值,对X的对象属性Y进行获取。也就是当前doGetBean的beanName就是Y。这里第一次从缓存中获取肯定还是为null。
上面也说了过程同X一致,直到对Y的属性进行赋值的时候。对Y的X属性进行赋值,则会尝试去获取X。
猜一猜这里能不能在doGetBean中拿到X呢?或者说是尝试第一次送缓存中获取,能拿到X吗?
答案是能拿到,不然就一直循环下去了。这里我们再来看看他是如何拿到的
getSingleton方法
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
/**
* 第一步:我们尝试去一级缓存(单例缓存池中去获取对象,一般情况从该map中获取的对象是直接可以使用的)
* IOC容器初始化加载单实例bean的时候第一次进来的时候 该map中一般返回空
*/
Object singletonObject = this.singletonObjects.get(beanName);
/**
* 若在第一级缓存中没有获取到对象,并且singletonsCurrentlyInCreation这个list包含该beanName
* IOC容器初始化加载单实例bean的时候第一次进来的时候 该list中一般返回空,但是循环依赖的时候可以满足该条件
*/
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
/**
* 尝试去二级缓存中获取对象(二级缓存中的对象是一个早期对象)
* 何为早期对象:就是bean刚刚调用了构造方法,还来不及给bean的属性进行赋值的对象
* 就是早期对象
*/
singletonObject = this.earlySingletonObjects.get(beanName);
/**
* 二级缓存中也没有获取到对象,allowEarlyReference为true(参数是有上一个方法传递进来的true)
*/
if (singletonObject == null && allowEarlyReference) {
/**
* 直接从三级缓存中获取 ObjectFactory对象 这个对接就是用来解决循环依赖的关键所在
* 在ioc后期的过程中,当bean调用了构造方法的时候,把早期对象包裹成一个ObjectFactory
* 暴露到三级缓存中
*/
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
//从三级缓存中获取到对象不为空
if (singletonFactory != null) {
/**
* 在这里通过暴露的ObjectFactory 包装对象中,通过调用他的getObject()来获取我们的早期对象
* 在这个环节中会调用到 getEarlyBeanReference()来进行后置处理
*/
singletonObject = singletonFactory.getObject();
//把早期对象放置在二级缓存,
this.earlySingletonObjects.put(beanName, singletonObject);
//ObjectFactory 包装对象从三级缓存中删除掉
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
上面的步骤简单的概括一下,首先从一级缓存中拿,拿不到就去二级缓存拿,还是拿不到就去三级缓存中拿。ok拿到了就将他放到二级缓存中,并从三级缓存中剔除。
其实到这里循环依赖就已经解决,可以发现spring是通过早期对象暴露的方式+三级缓存,解决循环依赖的问题。
填坑
关于SingletonCurrentlyInCreation
/** 该集合用户缓存当前正在创建bean的名称 */
private final Set<String> singletonsCurrentlyInCreation =
Collections.newSetFromMap(new ConcurrentHashMap<>(16));
它其实是DefaultSingletonBeanFactory中的一个属性。用于保存当前正在创建的bean。出现在我们进行是否能够早期暴露的条件中。
要能够进行早期暴露,要满足该bean正在进行创建过程。那么是在什么时候将bean添加到该集合中的呢?
是在第一次从单例池中获取bean实例的时候,肯定为null嘛,接下来就要对它进行创建,所以这里就进行了标记beforeSingletonCreation(beanName);,也就是调用createBean之前。
protected void beforeSingletonCreation(String beanName) {
//若singletonsCurrentlyInCreation 没添加成功 添加到正在创建的集合中
if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
}
为什么是三级缓存呢?两个缓存已经够解决循环依赖了
首先,刚刚流程走下来,我们发现,其实二级缓存并没有什么卵用。真的没用吗?我们回想刚刚的getSingleton方法,获取bean的过程,先从一级拿,然后二级最后三级。那么我们在三级缓存中拿的时候会去调用这个工厂的getRefference方法,里面包含着一块关于代理的逻辑。假设这里我们还有一个bean Z依赖于X,那么前面Y已经将X添加到二级缓存后,Z就直接能够从二级缓存中拿到,而不用去再走一遍三级缓存的中的代理逻辑。这是其一。
其二,两个缓存真的够解决循环依赖吗? 而且现在也感受不到三级缓存改成一个工厂的原因。我们不妨假设Y需要的是一个完成初始化的X。那么如果说我们三级缓存并不是一个工厂,那么这里保存的始终是bean的早期对象。没有自己生产bean的能力。关于两个缓存是否能够解决循环依赖的问题,网上大牛们也是各执一词,这里我就简单的提一下我认为合理的观点。
总结
最后总结一下: