spring 循环依赖的认识
本文出自:https://mp.weixin.qq.com/s/JVhRgiEcaNf6KLQehQwx1Q
何为循环依赖,代码说话:
@Service
public class A {
@Autowired
private B b;
}
@Service
public class B {
@Autowired
private A a;
}
//或者自己依赖自己
@Service
public class A {
@Autowired
private A a;
}
A服务依赖B服务,B服务依赖A服务,
那么问题来了,A 要依赖 B,发现 B 还没创建,于是开始创建 B ,创建的过程发现 B 要依赖 A, 而 A 还没创建好呀,因为它要等 B 创建好;
完了,芭比Q了,卡bug了。
--------------------------------------------------------------------------------------------------------------------------------------------------------------------
【一起来看 Spring 是如何解决这个循环依赖的问题】
Spring解决循环依赖的关键就是:提前暴露未完全创建完毕的 Bean。
在 Spring 中,只有同时满足以下两点才能解决循环依赖的问题:
- 依赖的 Bean 必须都是单例
- 依赖注入的方式,必须不全是构造器注入,且 beanName 字母序在前的不能是构造器注入
注:原型模式 - 重复的创建对象,单例模式 - 创建一个可重复使用的对象
【问题来了,为什么依赖的Bean都必须是单例呢?】
如果从源码来看的话,循环依赖的 Bean 是原型模式,会直接抛错:
// Fail if we're already creating this bean instance:
// We're assumably within a circular reference.
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
所以 Spring 只支持单例的循环依赖,但是为什么呢?
具体做法就是:先创建 A,此时的 A 是不完整的(没有注入 B),用个 map 保存这个不完整的 A,再创建 B ,B 需要 A。
所以从那个 map 得到“不完整”的 A,此时的 B 就完整了,然后 A 就可以注入 B,然后 A 就完整了,B 也完整了,且它们是相互依赖的。
【问题又来了,为什么不能全是构造器注入呢?】
在 Spring 中创建 Bean 分三步:
- 实例化,createBeanInstance,就是 new 了个对象
- 属性注入,populateBean, 就是 set 一些属性值
- 初始化,initializeBean,执行一些 aware 接口中的方法,initMethod,AOP代理等
bean的创建工厂类:
/** * Abstract bean factory superclass that implements default bean creation, * with the full capabilities specified by the {@link RootBeanDefinition} class. * Implements the {@link org.springframework.beans.factory.config.AutowireCapableBeanFactory} * interface in addition to AbstractBeanFactory's {@link #createBean} method. * * <p>Provides bean creation (with constructor resolution), property population, * wiring (including autowiring), and initialization. Handles runtime bean * references, resolves managed collections, calls initialization methods, etc. * Supports autowiring constructors, properties by name, and properties by type. * * <p>The main template method to be implemented by subclasses is * {@link #resolveDependency(DependencyDescriptor, String, Set, TypeConverter)}, * used for autowiring by type. In case of a factory which is capable of searching * its bean definitions, matching beans will typically be implemented through such * a search. For other factory styles, simplified matching algorithms can be implemented. * * <p>Note that this class does <i>not</i> assume or implement bean definition * registry capabilities. See {@link DefaultListableBeanFactory} for an implementation * of the {@link org.springframework.beans.factory.ListableBeanFactory} and * {@link BeanDefinitionRegistry} interfaces, which represent the API and SPI * view of such a factory, respectively. * * @author Rod Johnson * @author Juergen Hoeller * @author Rob Harrop * @author Mark Fisher * @author Costin Leau * @author Chris Beams * @author Sam Brannen * @since 13.02.2004 * @see RootBeanDefinition * @see DefaultListableBeanFactory * @see BeanDefinitionRegistry */ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory {
明确了上面这三点,再结合我上面说的“不完整的”,我们来理一下。
如果全是构造器注入,比如A(B b),那表明在 new 的时候,就需要得到 B,此时需要 new B 。
但是 B 也是要在构造的时候注入 A ,即B(A a),这时候 B 需要在一个 map 中找到不完整的 A ,发现找不到。
为什么找不到?因为 A 还没 new 完呢,所以找到不完整的 A,因此如果全是构造器注入的话,那么 Spring 无法处理循环依赖。
【那么问题又来了:一个set注入,一个构造器注入一定能成功?】
例一:成功的例子
假设我们 A 是通过 set 注入 B,B 通过构造函数注入 A,此时是成功的。
我们来分析下:实例化 A 之后,可以在 map 中存入 A,开始为 A 进行属性注入,发现需要 B。
此时 new B,发现构造器需要 A,此时从 map 中得到 A ,B 构造完毕。
B 进行属性注入,初始化,然后 A 注入 B 完成属性注入,然后初始化 A。
整个过程很顺利,没毛病。
例二:失败的例子
假设 A 是通过构造器注入 B,B 通过 set 注入 A,此时是失败的。
我们来分析下:实例化 A,发现构造函数需要 B, 此时去实例化 B。
然后进行 B 的属性注入,从 map 里面找不到 A,因为 A 还没 new 成功,
所以 B 也卡住了,然后就 芭比Q了。
看到这里,仔细思考的小伙伴可能会说,可以先实例化 B 啊,往 map 里面塞入不完整的 B,这样就能成功实例化 A 了啊。
确实,思路没错但是 Spring 容器是按照字母序创建 Bean 的,A 的创建永远排在 B 前面。
--------------------------------------------------------------------------------------------------------------------------------------------------------------------
现在我们总结一下:
- 如果循环依赖都是构造器注入,则失败
- 如果循环依赖不完全是构造器注入,则可能成功,可能失败,具体跟BeanName的字母序有关系。
--------------------------------------------------------------------------------------------------------------------------------------------------------------------
问题都搞清楚了,那么再来描述一下 【Spring 解决循环依赖全流程】
明确了 Spring 创建 Bean 的三步骤之后,我们再来看看它为单例搞的三个 map【spring的三级缓存】:
- 一级缓存,singletonObjects,存储所有已创建完毕的单例 Bean (完整的 Bean)
- 二级缓存,earlySingletonObjects,存储所有仅完成实例化,但还未进行属性注入和初始化的 Bean
- 三级缓存,singletonFactories,存储能建立这个 Bean 的一个工厂,通过工厂能获取这个 Bean,延迟化 Bean 的生成,工厂生成的 Bean 会塞入二级缓存
【这三个 map 是如何配合的呢?】
步骤1.首先,获取单例 Bean 的时候会通过 BeanName 先去 singletonObjects(一级缓存) 查找完整的 Bean,如果找到则直接返回,否则进行步骤 2。
步骤2.看对应的 Bean 是否在创建中,如果不在直接返回找不到,如果是,则会去 earlySingletonObjects (二级缓存)查找 Bean,如果找到则返回,否则进行步骤 3
步骤3.去 singletonFactories (三级缓存)通过 BeanName 查找到对应的工厂,如果存着工厂则通过工厂创建 Bean ,并且放置到 earlySingletonObjects 中。
步骤4.如果三个缓存都没找到,则返回 null。
从上面的步骤我们可以得知,如果查询发现 Bean 还未创建,到第二步就直接返回 null,不会继续查二级和三级缓存。
返回 null 之后,说明这个 Bean 还未创建,这个时候会标记这个 Bean 正在创建中,然后再调用 createBean 来创建 Bean,而实际创建是调用方法 doCreateBean。
doCreateBean 这个方法就会执行上面我们说的三步骤:实例化、属性注入、初始化
在实例化 Bean 之后,会往 singletonFactories 塞入一个工厂,而调用这个工厂的 getObject 方法,就能得到这个 Bean。
/** * Actually create the specified bean. Pre-creation processing has already happened * at this point, e.g. checking {@code postProcessBeforeInstantiation} callbacks. * <p>Differentiates between default bean instantiation, use of a * factory method, and autowiring a constructor. * @param beanName the name of the bean * @param mbd the merged bean definition for the bean * @param args explicit arguments to use for constructor or factory method invocation * @return a new instance of the bean * @throws BeanCreationException if the bean could not be created * @see #instantiateBean * @see #instantiateUsingFactoryMethod * @see #autowireConstructor */ protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) { // Instantiate the bean. BeanWrapper instanceWrapper = null; if (mbd.isSingleton()) { instanceWrapper = this.factoryBeanInstanceCache.remove(beanName); } if (instanceWrapper == null) { instanceWrapper = createBeanInstance(beanName, mbd, args); } final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null); Class<?> beanType = (instanceWrapper != null ? instanceWrapper.getWrappedClass() : null); // Allow post-processors to modify the merged bean definition. synchronized (mbd.postProcessingLock) { if (!mbd.postProcessed) { applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName); mbd.postProcessed = true; } } // Eagerly cache singletons to be able to resolve circular references // even when triggered by lifecycle interfaces like BeanFactoryAware. 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"); } addSingletonFactory(beanName, new ObjectFactory<Object>() { @Override public Object getObject() throws BeansException { return getEarlyBeanReference(beanName, mbd, bean); } }); } // Initialize the bean instance. Object exposedObject = bean; try { populateBean(beanName, mbd, instanceWrapper); if (exposedObject != null) { 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) { Object earlySingletonReference = getSingleton(beanName, false); if (earlySingletonReference != null) { if (exposedObject == bean) { exposedObject = earlySingletonReference; } else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) { String[] dependentBeans = getDependentBeans(beanName); Set<String> actualDependentBeans = new LinkedHashSet<String>(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 { registerDisposableBeanIfNecessary(beanName, bean, mbd); } catch (BeanDefinitionValidationException ex) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex); } return exposedObject; }
要注意,此时 Spring 是不知道会不会有循环依赖发生的,但是它不管,反正往 singletonFactories 塞这个工厂,这里就是提前暴露。
然后就开始执行属性注入,这个时候 A 发现需要注入 B,所以去 getBean(B),此时又会走一遍上面描述的逻辑,到了 B 的属性注入这一步。
此时 B 调用 getBean(A),这时候一级缓存里面找不到,但是发现 A 正在创建中的,于是去二级缓存找,发现没找到,于是去三级缓存找,然后找到了。
并且通过上面提前在三级缓存里暴露的工厂得到 A,然后将这个工厂从三级缓存里删除,并将 A 加入到二级缓存中。
然后结果就是 B 属性注入成功。
紧接着 B 调用 initializeBean 初始化,最终返回,此时 B 已经被加到了一级缓存里 。
这时候就回到了 A 的属性注入,此时注入了 B,接着执行初始化,最后 A 也会被加到一级缓存里,且从二级缓存中删除 A。
Spring 解决依赖循环就是按照上面所述的逻辑来实现的。
重点就是在对象实例化之后,都会在三级缓存里加入一个工厂,提前对外暴露还未完整的 Bean,这样如果被循环依赖了,对方就可以利用这个工厂得到一个不完整的 Bean,破坏了循环的条件。
--------------------------------------------------------------------------------------------------------------------------------------------------------------------
【为什么循环依赖需要三级缓存,二级不够吗?】
doCreateBean()方法中,执行添加三级缓存的代码:
// Eagerly cache singletons to be able to resolve circular references // even when triggered by lifecycle interfaces like BeanFactoryAware. 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"); } addSingletonFactory(beanName, new ObjectFactory<Object>() { @Override public Object getObject() throws BeansException { return getEarlyBeanReference(beanName, mbd, bean); } }); }
其中 addSingletonFactory 中的 getEarlyBeanReference(beanName, mbd, bean); 方法调用的源码:
/** * Obtain a reference for early access to the specified bean, * typically for the purpose of resolving a circular reference. * @param beanName the name of the bean (for error handling purposes) * @param mbd the merged bean definition for the bean * @param bean the raw bean instance * @return the object to expose as bean reference */ protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) { Object exposedObject = bean; if (bean != null && !mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) { for (BeanPostProcessor bp : getBeanPostProcessors()) { if (bp instanceof SmartInstantiationAwareBeanPostProcessor) { SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp; exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName); if (exposedObject == null) { return exposedObject; } } } } return exposedObject; }
重点就在中间的判断,如果 false,返回就是参数传进来的 bean,没任何变化。
如果是 true 说明有 InstantiationAwareBeanPostProcessors 。
且循环的是 smartInstantiationAware 类型,如有这个 BeanPostProcessor 说明 Bean 需要被 aop 代理。
我们都知道如果有代理的话,那么我们想要直接拿到的是代理对象。
也就是说如果 A 需要被代理,那么 B 依赖的 A 是已经被代理的 A,所以我们不能返回 A 给 B,而是返回代理的 A 给 B。
这个工厂的作用就是判断这个对象是否需要代理,如果否则直接返回,如果是则返回代理对象。
看到这明白的小伙伴肯定会问,那跟三级缓存有什么关系,我可以在要放到二级缓存的时候判断这个 Bean 是否需要代理,如果要直接放代理的对象不就完事儿了。
是的,这个思路看起来没任何问题,问题就出在时机,这跟 Bean 的生命周期有关系。
正常代理对象的生成是基于后置处理器,是在被代理的对象初始化后期调用生成的,所以如果你提早代理了其实是违背了 Bean 定义的生命周期。
所以 Spring 先在一个三级缓存放置一个工厂,如果产生循环依赖,那么就调用这个工厂提早得到代理对象。
如果没产生依赖,这个工厂根本不会被调用,所以 Bean 的生命周期就是对的。
至此,我想你应该明白为什么会有三级缓存了。
也明白,其实破坏循环依赖,其实只有二级缓存就够了,但是碍于生命周期的问题,提前暴露工厂延迟代理对象的生成。
--------------------------------------------------------------------------------------------------------------------------------------------------------------------
对了,不用担心三级缓存因为没有循环依赖,数据堆积的问题,最终单例 Bean 创建完毕都会加入一级缓存,此时会清理下面的二、三级缓存。
源码如下:
/** * Add the given singleton object to the singleton cache of this factory. * <p>To be called for eager registration of singletons. * @param beanName the name of the bean * @param singletonObject the singleton object */ protected void addSingleton(String beanName, Object singletonObject) { synchronized (this.singletonObjects) { this.singletonObjects.put(beanName, (singletonObject != null ? singletonObject : NULL_OBJECT)); this.singletonFactories.remove(beanName); this.earlySingletonObjects.remove(beanName); this.registeredSingletons.add(beanName); } }