单例在Spring里的获取方式
今天讲一下 Spring 中针对单例 bean 的循环依赖问题,本着追本溯源的学习理念,我们要先知道单例在 Spring 中怎么管理的。spring获取实例都通过 beanFactory 的 getBean方法获取实例,顺着代码而下,在 doGetBean 方法(AbstractBeanFactory)中,单例总是通过 getSingleton() 方法获取实例。
protected <T> T doGetBean(
final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)
throws BeansException {
......
// Create bean instance.
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, () -> {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
destroySingleton(beanName);
throw ex;
}
});
beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
......
}
getSingleton 和 createBean 方法大致流程(单例):
先从缓存拿(从第一层到第三层缓存中依次获取)
创建对象准备工作(beforeSingletonCreation):把 beanName 标记为正在创建中(singletonsCurrentlyInCreation.add)
对象实例化(createBeanInstance):通过其定义里的 class 找到构造器方法反射创建实例
提前暴露对象解决循环引用问题(Eagerly cache singletons to be able to resolve circular references):如果此 beanName 为正在创建中,则把其对象工厂放入第三层缓存(addSingletonFactory)
有一个bean拓展点,getEarlyBeanReference 是一个延迟动作,等其它类依赖获取这个对象的时候会触发(SmartInstantiationAwareBeanPostProcessor,本质也是BeanPostProcessor)。
对象初始化:
populateBean 装配对象变量:获取此 bean 中有@Autowired等注解的成员变量,从所有bean定义中找出此类型的 beanName ,又通过BeanFactory#getBean 方法获取实例,然后反射设值成员变量.
initializeBean:我们熟知的 applyBeanPostProcessorsBeforeInitialization (BeanPostProcessor) 和 InitializingBean 和 applyBeanPostProcessorsAfterInitialization (BeanPostProcessor)等拓展点.
循环依赖检查
创建对象结束工作(afterSingletonCreation):移除正在创建中的标记(singletonsCurrentlyInCreation.remove),把实例放入第一层缓存,移除第二、三层中的缓存(addSingleton),最后返回实例
不熟悉源码的同学可能看着云里雾里,其实这里得分成两块来看:标黑的是创建bean的主流程,其他的是发生循环依赖(bean相互引用)时的处理。
三级缓存
Spring给了三个map,也就是三级缓存:
一级缓存 : singletonObjects 初始化完后的单例都会存在这个 map 中,一般 ioc容器初始完后,你再用getBean取实例都可以从这里获取到。
二级缓存 :earlySingletonObjects 提前曝光的实例,这时候 bean 还处于创建中(bean的创建分为 实例化 和 初始化),没有完全创建好。
三级缓存 :singletonFactories 跟二级缓存要放在一起看,这里存的只是一个工厂类,需要通过 getObject 获取实例,实例获取到以后会放到二级缓存,只会执行一遍。
/** 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 ConcurrentHashMap<>(16);
再来看看Spring对三级缓存的获取逻辑,这段代码的含义就是 先从一级缓存中获取实例,取不到,再去取二级缓存,再没有,那就三级中取。 注意: 当从三级中取到实例时,会删除三级缓存中的值,然后放入二级缓存,二/三级缓存是一体的,三级缓存的工厂类是为了延迟执行(怎么延迟执行后面说),然后二级缓存是为了存放三级缓存getObject的结果,所以每个对象的三级缓存只会执行一遍,所以其实我们就把三级缓存当成两级缓存(一级缓存和二/三级缓存),这样对整体机制的理解有帮助,减少理解的成本。
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
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);
this.singletonFactories.remove(beanName);
}
}
}
}
return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
我们还要知道 Spring 对一个 bean 的拓展点有哪些,这对我们理解 Spring 循环依赖处理有帮助。
从上文我们知道一个bean在创建过程中因为拓展点的存在,可能会产生两个对象(狸猫换太子):
一个是循环依赖时需要设值依赖的对象(getEarlyBeanReference)
一个是初始化后的对象(initializeBean)
如果现在要对bean做增强,比如实现切面,则需要生成代理类,所以 Spring 在上述两个方法中通过 BeanPostProcessor 类提供了拓展点。
当我们铺垫完上面的基础源码流程,我们开始引出本文的核心:什么是循环依赖和 Spring 循环依赖解决方案。
循环依赖的问题和Spring的解决方案
上一章我们讲了单例如何从三个缓存中去取,可是为什么要设计这个缓存我们听的云里雾里,这里我们就抛出开发中存在的问题:循环依赖。
我们知道,当 A 有一个 属性 B , B 有一个属性A时,就形成了循环依赖,按照单例bean的创建逻辑,A 没创建完,就去拿 B,B 还没创建完,又去拿 A,请问 A 在哪?A 还没创建完呢 ! 所以解决问题的关键在哪?Spring 给设计了一个多级缓存,当 A 完成实例化(createBeanInstance)时,这个实例已经有了自己的内存地址,只是对象不够完善,Spring 先把这种对象放进一个缓存。然后装配属性的时候需要 B 时 ,B 开始自己的创建之旅,途中 B 需要 A 了,它就可以从缓存中顺利拿到实例 A,即使这时候的 A 并不完善。 然后 B 把自己创建完成后,又轮到 A 继续完善自己,直到 A 和 B 都创建完毕,这样就没有了循环依赖的问题。
下面用图反映spring bean的大致创建流程 与 多级缓存之间的配合:
当实例化完一个初始 bean 时,会先放入三级缓存,记住,这里的 A 只是个半成品,被放入ObjectFactory里的匿名函数延迟执行。
然后经历装配 bean 属性的过程。期间属性涉及到 循环依赖 的时候,就可以通过第三缓存拿到对象。拿到后删除三级缓存,放入二级缓存。这里注意,假如没有循环依赖,就压根没有前两步什么事。
实例创建初始化完毕后会删除二,三级缓存,放入一级缓存。一级缓存就是 Spring单例对象的完全体,后面程序可以通过 beanFactory 随时取用。
AOP和循环依赖
AOP属于代理模式,对于存在循环依赖情况的对象,需要提前暴露的对象,代理的动作也需要提前跟进,所以AOP跟getEarlyBeanReference 也有很大的关系,具体的原理可以看我AOP的文章 Spring之我见-从IOC谈到AOP实现原理 . 我这里直接说一下结论,有两个地方: 一个是BeanPostProcessors 的 postProcessAfterInitialization逻辑里,还有一个就在 getEarlyBeanReference 方法中, 这两个方法同属于 AbstractAutoProxyCreator 抽象类(继承BeanPostProcessor),调用主逻辑完全一样(都是调用wrapIfNecessary),在创建 bean 的过程中会先执行 getEarlyBeanReference ,再执行postProcessAfterInitialization,两个方法都是创建代理对象的方法,但是会通过一个cache map避免重复执行,具体执行哪一个要视具体情况来定。
@Override
public Object getEarlyBeanReference(Object bean, String beanName) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
this.earlyBeanReferences.put(cacheKey, bean);
return wrapIfNecessary(bean, beanName, cacheKey);
}
.......
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (this.earlyBeanReferences.remove(cacheKey) != bean) {
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
所以,为什么是三层缓存
在前面说了那么多的情况下,可能大家还是没理解为什么要用三层缓存,甚至觉得 Spring是过度设计。
不知道大家有没有理解 getEarlyBeanReference,按照注释,它是为了循环依赖导致对象提前暴露设计的,那么对于Spring IOC 的主体流程中,getEarlyBeanReference 被设计成需要的时候才执行,什么时候是需要的?那当然是存在相互依赖的时候!并且getEarlyBeanReference有对AOP的执行逻辑,如果一个类既存在循环依赖,也存在AOP也需要提前执行,所以 Spring 设计了一个可以延迟执行的 ObjectFactory ,并且这个ObjectFactory具备拓展性,可以提前执行AOP的逻辑(因为里面的逻辑是SmartInstantiationAwareBeanPostProcessor)。所以 Spring 的三层缓存才被设计出来。
// 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.isTraceEnabled()) {
logger.trace("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
循环依赖解决方案并非万能!
在了解了解决循环依赖的基本原理后,我们要知道这种情况只能解决最理想的循环依赖情况(没有经历拓展点的Bean),但是Spring预留了两处拓展点,拓展点意味着 bean 的返回值有可能跟原始 bean 不再一样,比如原始 bean 是 A,那么 A 跟其它对象的依赖的 A 必须要内存地址一致才行,这时候看看 Spring 怎么检查循环依赖的。
我们重点关注一下下面的代码,从doCreateBean方法中看到如下示例代码,这段代码在装配完bean(populateBean)后开始执行,会看到其中调用了getSingleton(beanName, false),这里其实是 这个bean是否存在被其它bean依赖的一次检查
:
这里有三个对象我们需要留意,一个是earlySingletonReference
,一个是exposedObject
,一个是 bean
。
bean是最原始的对象,不会被任何流程修改。
earlySingletonReference,代表这个bean是否存在被其它bean依赖的情况。因为第二个参数为false,看里面的实现可以知道,false 的话是不会执行工厂类的方法的,换句话说,一定是哪里产生了循环依赖,才会执行了工厂类的 getObject 方法,earlySingletonReference 的值才不为空。
当发现这个bean是存在被其它bean依赖的情况(earlySingletonReference!=null),Spring 就要开始提高警惕了,Spring 会再判断 exposedObject == bean,exposedObject 是可能被 initializeBean 这个拓展点修改的,如果相等,那么一切都好说,Spring通过把 exposedObject 的引用指向了二级缓存中的对象,从而保证不管是 A本身也好,还是其它对象依赖的 A 也好,都能保证一致。
如果不相等,那就麻烦了!这代表着其它对象依赖的 A 与 exposedObject 是不一样的对象, 代码会开始找哪些对象会依赖 B(根据beanName查找),最终发现还真不少(actualDependentBeans不为空),然后就会开始报错,项目启动失败。
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.");
}
}
}
}
总结
总结三层缓存的话
第一层缓存:存放的是成品bean,项目启动完成后获取bean实例时,就会从这里取。
第二级缓存:创建bean过程中用于处理循环依赖的临时缓存,搭配第三层缓存,保证第三层缓存的ObjectFactory只执行一次
第三层缓存:创建bean过程中用于处理循环依赖的临时缓存,只有在存在循环依赖的情况下才会触发ObjectFactory的getObject方法(延迟触发),且获取对象会作为当前bean的最终对象
修订
2024.06.12 依据 【超级干货】为什么spring一定要弄个三级缓存?重新修订了本文章,并引用重新组织了部分内容,感谢原作者!!
2025.04.19 重新编排文章,更加通俗易懂!