烂了大街的 Spring 循环依赖问题,你以为自己就真会了吗
共 14378字,需浏览 29分钟
·
2020-09-21 09:37
new XXX
。new XXX
、序列化、clone()
等等, 只是 Spring 是通过反射 + 工厂的方式创建对象并放在容器的,创建好的对象我们一般还会对对象属性进行赋值,才去使用,可以理解是分了两个步骤。什么是循环依赖
public class BeanB {
private BeanA beanA;
public void setBeanA(BeanA beanA) {
this.beanA = beanA;
}
}
public class BeanA {
private BeanB beanB;
public void setBeanB(BeanB beanB) {
this.beanB = beanB;
}
}
"beanA" class="priv.starfish.BeanA">
"beanB" ref="beanB"/> "beanB" class="priv.starfish.BeanB">
"beanA" ref="beanA"/>
源码解毒
代码版本:5.0.16.RELEASE
获取 Bean 流程
流程从 getBean
方法开始,getBean
是个空壳方法,所有逻辑直接到doGetBean
方法中transformedBeanName
将 name 转换为真正的 beanName(name 可能是 FactoryBean 以 & 字符开头或者有别名的情况,所以需要转化下)然后通过 getSingleton(beanName)
方法尝试从缓存中查找是不是有该实例 sharedInstance(单例在 Spring 的同一容器只会被创建一次,后续再获取 bean,就直接从缓存获取即可)如果有的话,sharedInstance 可能是完全实例化好的 bean,也可能是一个原始的 bean,所以再经 getObjectForBeanInstance
处理即可返回当然 sharedInstance 也可能是 null,这时候就会执行创建 bean 的逻辑,将结果返回
/** Cache of singleton objects: bean name --> bean instance */
private final MapsingletonObjects = new ConcurrentHashMap<>(256);
/** Cache of singleton factories: bean name --> ObjectFactory */
private final Map> singletonFactories = new HashMap<>(16);
/** Cache of early singleton objects: bean name --> bean instance */
private final MapearlySingletonObjects = new HashMap<>(16);
singletonObjects
:完成初始化的单例对象的 cache,这里的 bean 经历过实例化->属性填充->初始化
以及各种后置处理(一级缓存)earlySingletonObjects
:存放原始的 bean 对象(完成实例化但是尚未填充属性和初始化),仅仅能作为指针提前曝光,被其他 bean 所引用,用于解决循环依赖的 (二级缓存)singletonFactories
:在 bean 实例化完之后,属性填充以及初始化之前,如果允许提前曝光,Spring 会将实例化后的 bean 提前曝光,也就是把该 bean 转换成beanFactory
并加入到singletonFactories
(三级缓存)
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 从 singletonObjects 获取实例,singletonObjects 中的实例都是准备好的 bean 实例,可以直接使用
Object singletonObject = this.singletonObjects.get(beanName);
//isSingletonCurrentlyInCreation() 判断当前单例bean是否正在创建中
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) {
// 三级缓存有的话,就把他移动到二级缓存,.getObject() 后续会讲到
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
创建 bean 从以下代码开始,一个匿名内部类方法参数(总觉得 Lambda 的方式可读性不如内部类好理解) if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, () -> {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
destroySingleton(beanName);
throw ex;
}
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}getSingleton()
方法内部主要有两个方法public Object getSingleton(String beanName, ObjectFactory> singletonFactory) {
// 创建 singletonObject
singletonObject = singletonFactory.getObject();
// 将 singletonObject 放入缓存
addSingleton(beanName, singletonObject);
}getObject()
匿名内部类的实现真正调用的又是createBean(beanName, mbd, args)
往里走,主要的实现逻辑在 doCreateBean
方法,先通过createBeanInstance
创建一个原始 bean 对象接着 addSingletonFactory
添加 bean 工厂对象到 singletonFactories 缓存(三级缓存)通过 populateBean
方法向原始 bean 对象中填充属性,并解析依赖,假设这时候创建 A 之后填充属性时发现依赖 B,然后创建依赖对象 B 的时候又发现依赖 A,还是同样的流程,又去getBean(A)
,这个时候三级缓存已经有了 beanA 的“半成品”,这时就可以把 A 对象的原始引用注入 B 对象(并将其移动到二级缓存)来解决循环依赖问题。这时候getObject()
方法就算执行结束了,返回完全实例化的 bean最后调用 addSingleton
把完全实例化好的 bean 对象放入 singletonObjects 缓存(一级缓存)中,打完收工
Spring 解决循环依赖
Spring 创建 bean 主要分为两个步骤,创建原始 bean 对象,接着去填充对象属性和初始化 每次创建 bean 之前,我们都会从缓存中查下有没有该 bean,因为是单例,只能有一个 当我们创建 beanA 的原始对象后,并把它放到三级缓存中,接下来就该填充对象属性了,这时候发现依赖了 beanB,接着就又去创建 beanB,同样的流程,创建完 beanB 填充属性时又发现它依赖了 beanA,又是同样的流程,不同的是,这时候可以在三级缓存中查到刚放进去的原始对象 beanA,所以不需要继续创建,用它注入 beanB,完成 beanB 的创建 既然 beanB 创建好了,所以 beanA 就可以完成填充属性的步骤了,接着执行剩下的逻辑,闭环完成
ObjectFactory> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 三级缓存有的话,就把他移动到二级缓存
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
singletonFactory.getObject()
是一个接口方法,这里具体的实现方法在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;
// 这么一大段就这句话是核心,也就是当bean要进行提前曝光时,
// 给一个机会,通过重写后置处理器的getEarlyBeanReference方法,来自定义操作bean
// 值得注意的是,如果提前曝光了,但是没有被提前引用,则该后置处理器并不生效!!!
// 这也正式三级缓存存在的意义,否则二级缓存就可以解决循环依赖的问题
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
return exposedObject;
}
getEarlyBeanReference
是 SmartInstantiationAwareBeanPostProcessor
接口的默认方法,真正实现这个方法的只有**AbstractAutoProxyCreator
** 这个类,用于提前曝光的 AOP 代理。@Override
public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
this.earlyProxyReferences.put(cacheKey, bean);
// 对bean进行提前Spring AOP代理
return wrapIfNecessary(bean, beanName, cacheKey);
}
@Service
public class HelloServiceImpl implements HelloService {
@Autowired
private HelloService helloService;
@Override
@Transactional
public Object hello() {
return "Hello JavaKeeper";
}
}
Service
类使用到了事务,所以最终会生成一个 JDK 动态代理对象 Proxy
。刚好它又存在自己引用自己的循环依赖,完美符合我们的场景需求。@Component
public class HelloProcessor implements SmartInstantiationAwareBeanPostProcessor {
@Override
public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
System.out.println("提前曝光了:"+beanName);
return bean;
}
}
HelloProcessor
,说明这个 bean 会通过 AOP 代理处理。protected Object doCreateBean( ... ){
...
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName));
// 需要提前暴露(支持循环依赖),就注册一个ObjectFactory到三级缓存
if (earlySingletonExposure) {
// 添加 bean 工厂对象到 singletonFactories 缓存中,并获取原始对象的早期引用
//匿名内部方法 getEarlyBeanReference 就是后置处理器
// SmartInstantiationAwareBeanPostProcessor 的一个方法,
// 它的功效为:保证自己被循环依赖的时候,即使被别的Bean @Autowire进去的也是代理对象
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
// 此处注意:如果此处自己被循环依赖了 那它会走上面的getEarlyBeanReference,从而创建一个代理对象从 三级缓存转移到二级缓存里
// 注意此时候对象还在二级缓存里,并没有在一级缓存。并且此时后续的这两步操作还是用的 exposedObject,它仍旧是原始对象~~~
populateBean(beanName, mbd, instanceWrapper);
exposedObject = initializeBean(beanName, exposedObject, mbd);
// 因为事务的AOP自动代理创建器在getEarlyBeanReference 创建代理后,initializeBean 就不会再重复创建了,二选一的)
// 所以经过这两大步后,exposedObject 还是原始对象,通过 getEarlyBeanReference 创建的代理对象还在三级缓存呢
...
// 循环依赖校验
if (earlySingletonExposure) {
// 注意此处第二个参数传的false,表示不去三级缓存里再去调用一次getObject()方法了~~~,此时代理对象还在二级缓存,所以这里拿出来的就是个 代理对象
// 最后赋值给exposedObject 然后return出去,进而最终被addSingleton()添加进一级缓存里面去
// 这样就保证了我们容器里 最终实际上是代理对象,而非原始对象~~~~~
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
}
...
}
}
自我解惑:
问:还是不太懂,为什么这么设计呢,即使有代理,在二级缓存代理也可以吧 | 为什么要使用三级缓存呢?
AOP
跟 Bean 的生命周期的设计。getObject()
方法并非直接返回实例,而是对实例又使用 SmartInstantiationAwareBeanPostProcessor
的 getEarlyBeanReference
方法对 bean 进行处理,也就是说,当 Spring 中存在该后置处理器,所有的单例 bean 在实例化后都会被进行提前曝光到三级缓存中,但是并不是所有的 bean 都存在循环依赖,也就是三级缓存到二级缓存的步骤不一定都会被执行,有可能曝光后直接创建完成,没被提前引用过,就直接被加入到一级缓存中。因此可以确保只有提前曝光且被引用的 bean 才会进行该后置处理。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) {
// 三级缓存获取,key=beanName value=objectFactory,objectFactory中存储 //getObject()方法用于获取提前曝光的实例
ObjectFactory> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 三级缓存有的话,就把他移动到二级缓存
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
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");
}
// 添加 bean 工厂对象到 singletonFactories 缓存中,并获取原始对象的早期引用
//匿名内部方法 getEarlyBeanReference 就是后置处理器
// SmartInstantiationAwareBeanPostProcessor 的一个方法,
// 它的功效为:保证自己被循环依赖的时候,即使被别的Bean @Autowire进去的也是代理对象~~~~ AOP自动代理创建器此方法里会创建的代理对象~~~
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
再问:AOP 代理对象提前放入了三级缓存,没有经过属性填充和初始化,这个代理又是如何保证依赖属性的注入的呢?
cglib
代理还是jdk
动态代理生成的代理类,代理时,会将目标对象 target 保存在最后生成的代理 $proxy
中,当调用 $proxy
方法时会回调 h.invoke
,而 h.invoke
又会回调目标对象 target 的原始方法。所有,其实在 AOP 动态代理时,原始 bean 已经被保存在 提前曝光代理中了,之后 原始 bean
继续完成属性填充
和初始化
操作。因为 AOP 代理$proxy
中保存着 traget
也就是是 原始bean
的引用,因此后续 原始bean
的完善,也就相当于Spring AOP中的 target
的完善,这样就保证了 AOP 的属性填充
与初始化
了!非单例循环依赖
<bean id="beanA" class="priv.starfish.BeanA" scope="prototype">
<property name="beanB" ref="beanB"/>
bean>
<bean id="beanB" class="priv.starfish.BeanB" scope="prototype">
<property name="beanA" ref="beanA"/>
bean>
Error creating bean with name 'beanA' defined in class path resource [applicationContext.xml]: Cannot resolve reference to bean 'beanB' while setting bean property 'beanB';
Error creating bean with name 'beanB' defined in class path resource [applicationContext.xml]: Cannot resolve reference to bean 'beanA' while setting bean property 'beanA';
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'beanA': Requested bean is currently in creation: Is there an unresolvable circular reference?
prototype
作用域的 bean,Spring 容器无法完成依赖注入,因为 Spring 容器不进行缓存 prototype
作用域的 bean ,因此无法提前暴露一个创建中的bean 。if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
构造器循环依赖
public class BeanA {
private BeanB beanB;
public BeanA(BeanB beanB) {
this.beanB = beanB;
}
}
public class BeanB {
private BeanA beanA;
public BeanB(BeanA beanA) {
this.beanA = beanA;
}
}
<bean id="beanA" class="priv.starfish.BeanA">
<constructor-arg ref="beanB"/>
bean>
<bean id="beanB" class="priv.starfish.BeanB">
<constructor-arg ref="beanA"/>
bean>
Circular dependencies
If you use predominantly constructor injection, it is possible to create an unresolvable circular dependency scenario.
For example: Class A requires an instance of class B through constructor injection, and class B requires an instance of class A through constructor injection. If you configure beans for classes A and B to be injected into each other, the Spring IoC container detects this circular reference at runtime, and throws a
BeanCurrentlyInCreationException
.One possible solution is to edit the source code of some classes to be configured by setters rather than constructors. Alternatively, avoid constructor injection and use setter injection only. In other words, although it is not recommended, you can configure circular dependencies with setter injection.
Unlike the typical case (with no circular dependencies), a circular dependency between bean A and bean B forces one of the beans to be injected into the other prior to being fully initialized itself (a classic chicken-and-egg scenario).
小总结 | 面试这么答
B 中提前注入了一个没有经过初始化的 A 类型对象不会有问题吗?
Spring 是如何解决的循环依赖?
singletonObjects
),二级缓存为提前曝光对象(earlySingletonObjects
),三级缓存为提前曝光对象工厂(singletonFactories
)。为什么要使用三级缓存呢?二级缓存能解决循环依赖吗?
AnnotationAwareAspectJAutoProxyCreator
这个后置处理器来在 Bean 生命周期的最后一步来完成 AOP 代理,而不是在实例化后就立马进行 AOP 代理。参考与感谢:
《Spring 源码深度解析》- 郝佳著
https://developer.aliyun.com/article/766880
http://www.tianxiaobo.com/2018/06/08/Spring-IOC-容器源码分析-循环依赖的解决办法
https://cloud.tencent.com/developer/article/1497692
https://blog.csdn.net/chaitoudaren/article/details/105060882
有道无术,术可成;有术无道,止于术
欢迎大家关注Java之道公众号
好文章,我在看❤️