前提:使用setter注入,对docreate流程有基本了解。即实例化(createBeanInstance)注入(populateBean)初始化(initializeBean)
首先第一级缓存,单例对象singletonObjects
一级缓存作用就是存储所有创建好了的单例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);
//三级删除
this.singletonFactories.remove(beanName);
//二级删除
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
然后是第三级缓存,工厂对象singletonFactories
这里有两个点,第一个是缓存本身,第二个是为什么使用工厂。假设有AB两个单例循环依赖,先创建A。
第一 为什么使用缓存
首先我们肯定需要某种缓存来临时存放A的早期对象(即还未依赖B未注入属性时的对象),才能解决循环依赖问题,那么很容易想出来如下流程(不是实际流程):
- 创建A,实例化A
- 将实例化但还没注入属性的早期A放入缓存(addSingletonFactory)
- 注入A的属性,发现A依赖B
- 创建B,实例化B,注入B的属性,发现B依赖A
- 尝试获取A,将缓存中的早期A注入B中,B创建完成返回给A
- A注入B,A创建完成。
// Eagerly cache singletons to be able to resolve circular references
// even when triggered by lifecycle interfaces like BeanFactoryAware.
// (bean是单例 && 是否允许循环依赖 && bean是否在正在创建的列表中)
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));
}
可以看到只需要一个缓存就可以解决循环依赖问题,也即addSingletonFactory方法的一部分。并且在第2步,由于还没有接触注入流程,A自身并不知道是否参与了循环依赖,所以为了解决循环依赖,A一定要把加入缓存的步骤固定到流程里去,也即addSingletonFactory方法是固定在docreate方法中(earlySingletonExposure 正常为true)
第二 为什么使用工厂
此处有一个前提:spring开发人员希望我们遵循一个固定流程,即bean在正常实例化,注入之后,最后一步再通过applyBeanPostProcessorsAfterInitialization实现aop,可能这是某种标准化的体现。
现在如果AB循环依赖,且A实现了AOP代理,那么我们注入B的时候就需要注入A的代理类而非A本身。那么就会产生这种情况:A在实例化时不知道有没有循环依赖,所以为了保证解决问题,就需要在放入缓存(addSingletonFactory)时就生成代理对象,以供可能出现的B使用,那么结合前面提到的前提。这样做不符合开发人员的想法,他们是希望在出现循环依赖的情况下,由于B需要注入A的代理类,所以才不得不将aop的操作提前到addSingletonFactory处,而不是只要aop就提前。为了实现这个目的引入了工厂,也即addSingletonFactory实际上不是放入的早期A的bean实例,而是它对应的工厂!此时B在第5步时,通过工厂来获取A,默认是获取普通的早期A,而如果实现了AOP,则通过相应的类来创建早期A的代理类并获取,即通过工厂延迟了早期A的获取,从而实现了仅在出现循环依赖的情况下,才提前AOP操作。
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
//有aop相关的玩意儿就走aop的,具体在getEarlyBeanReference由相应类实现
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
}
}
//默认直接返回
return exposedObject;
}
最后是第二级缓存,早期对象earlySingletonObjects
可以看到实际上一三级缓存就已经解决了aop+循环依赖的问题,但是还会带来一些别的问题。
首先前面的流程是简化的,实际上获取bean的方法是AbstractBeanFactory里的doGetBean,其第一步为getSingleton,该方法为检查获取缓存区的bean,一二三级按顺序来,其中boolean参数决定是否检查第三级缓存,如果有就直接返回,没有才走创建流程。部分如下
//先查1级
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
//查2级
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null) {
//查3级
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
//通过三级工厂的getEarlyBeanReference拿到早期对象
singletonObject = singletonFactory.getObject();
//放2级里
this.earlySingletonObjects.put(beanName, singletonObject);
//三级工厂删了
this.singletonFactories.remove(beanName);
}
}
}
第一 性能问题
试想如果不止AB循环依赖,还有CD也依赖A。那么CD创建时同样需要通过工厂走一遍getEarlyBeanReference方法,如果缓存到第二级,那么就可以二级时直接获取到,不用跑一遍方法。
第二 一致性问题
所有对象创建时,都会产生三级缓存,而在bean A创建过程中,如果三级缓存没了变到2级里去了。就可以说明bean A在创建过程中一定被别人调用过了(如上图),起到一个标识的作用。
// Initialize the bean instance.
Object exposedObject = bean;
try {
populateBean(beanName, mbd, instanceWrapper);
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
***
略
***
if (earlySingletonExposure) {
//获取早期bean,false代表不看第三级缓存
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
//获取到了,检查跟bean相不相同
if (exposedObject == bean) {
//相同就把早期bean当标准输出返回了
exposedObject = earlySingletonReference;
}
//不相同就把不相同的地方检查了组成字符串报出来
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) + "字符串"
}
}
}
}
看上述代码,首先第一步把exposedObject设为bean(最初的A),然后initializeBean方法会处理exposedObject,完事后面再重新getSingleton获取自己的早期对象earlySingletonReference(bcd拿到的自己),如果获取到了,一定是二级拿到的,说明自己已经被调用过了,那么检查早期的自己和最终走完流程准备出场的exposedObject是不是相同,不相同就抛出报错。
这里有一个问题,就是为什么非要一致,也即BCD注入的早期A为什么要和被initializeBean过的exposedObject最终A一致,这个我懒得研究了,只能说想想肯定得一致吧?但是实际上可以通过配置关闭这个检查,也即上面代码判断中的allowRawInjectionDespiteWrapping 让它们可以不一致,跳过判断。
还有第二个问题,为什么initializeBean处理后会不一致,这个跟initializeBean中的applyBeanPostProcessorsAfterInitialization方法有关,实际上并不是处理exposedObject类的引用这么简单,有可能返回来的类不再是之前的那个类了,具体需要了解aop相关的源码。
最后总结来说,使用三级缓存有许多目的,三级跟二级最大的区别是一致性问题和性能问题
参考文章:
面试必杀技,讲一讲Spring中的循环依赖
【超级干货】为什么spring一定要弄个三级缓存?
spring 三级缓存的意义是什么,二级缓存也能解决含aop在内的循环依赖问题?
在第三个文章下可以看到有人说,2 3两个文章应该打一架,看懂了会发现这两人说的其实是一码事,第一篇文章的图有助于理解