什么是循环依赖
循环依赖主要是 A服务依赖B服务,B服务又依赖A服务,两者之间的调用关系形成了一个环,也就是环形调用,这种一般是由于不规范的的编码行为导致。
如何解决
在Spring 中,依赖注入有两种方式,以xml配置为例,
-
属性注入
-
构造器注入
官方的几点建议
关于循环依赖,spring官方给了几种建议:
-
对于属性注入,spring在bean实例化的时候使用了三级缓存的方案来解决循环依赖,仅针对单例模式,也就是bean的作用域是配置single的,才能解决,对于原型模式等,会直接报错。
-
构造器注入:
- 使用 @LazyInit 注解,延迟其中一个bean的实例化时间。
- 修改编码方式,没有循环依赖的关系存在。
属性注入如何解决循环依赖
我们这里主要是来分析第一种属性注入方式,spring是如何通过代码来解决循环依赖的问题。
为了突出重点,以下代码块,我们只截出了和循环依赖相关的代码。
public Object getBean(String name) throws BeansException {
return doGetBean(name, null, null, false);
}
protected <T> T doGetBean(
final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)
throws BeansException {
//提取对应的beanName
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 + "'");
}
}
//返回对应的实例,有时候存在诸如BeanFactory的情况并不是直接返回实例本身而是返回指定方法返回的实例
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}
else {
//只有在单例情况才会尝试解决循环依赖
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
BeanFactory parentBeanFactory = getParentBeanFactory();
//如果 beanDefinitionMap 中也就是在所有已经加载的类中不包括 beanName 则尝试从parentBeanFactory中检测
if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
//递归到BeanFactory中寻找
String nameToLookup = originalBeanName(name);
if (args != null) {
// Delegation to parent with explicit args.
return (T) parentBeanFactory.getBean(nameToLookup, args);
}
else {
return parentBeanFactory.getBean(nameToLookup, requiredType);
}
}
}
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject =this.singletonObjects.get(beanName);
if (singletonObject == null) {
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);
}
getSingleton(String beanName, boolean allowEarlyReference),在获取bean的开始首先使用的就是这段方法,为什么会首先使用这段代码呢,这段代码主要就是为了避免循环依赖的问题产生的,在创建单例bean的时候会存在依赖注入的情况,spring创建bean的原则是不等bean创建完成就会将bean的ObjectFactory提前曝光,也就是将ObjectFactory放入到缓存中,如果下个bean创建的时候需要依赖这个bean,则直接使用上。
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
//检查缓存是否存在实例过,因为singleton模式其实就是复用已创建的bean
Object singletonObject = this.singletonObjects.get(beanName);
//如果不存在,则锁定singletonObjects全局变量进行获取
if (singletonObject == null) {
synchronized (this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
ObjectFactory singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
//记录在缓存中,earlySingletonObjects和singletonFactories互斥
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
关键词介绍
-
singletonObjects:用于保存BeanName和创建bean实例之间的关系,俗称的一级缓存。
-
earlySingletonObjects:保存 BeanName 和创建 bean 实例之间的关系,与singletonObjects的不同之处在于,当一个单例bean被放到这里面后,那么当bean还在创建过程中,就可以通过getBean方法获取到了,其目的是用来检测循环引用,二级缓存。
-
singletonFactories:用于保存BeanName和创建 bean的工厂之间的关系,三级缓存。
-
registeredSingletons:用来保存当前所有已注册的bean。
singletonFactories作为最终的缓存,是在什么时候有数据的呢,在doCreateBean中可以看到有存放的动作,我们跟着代码往下走:
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) {
// 判断是否需要提前暴露bean:1.是否单例,2.是否允许循环依赖,3. 当前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");
}
//重点:上面的条件满足后,为避免后期bean循环依赖,在bean初始化完成之前将创建ObjectFactory加入工厂
addSingletonFactory(beanName, new ObjectFactory() {
public Object getObject() throws BeansException {
return getEarlyBeanReference(beanName, mbd, bean);
}
});
}
}
protected void addSingletonFactory(String beanName, ObjectFactory singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
if
(!this.singletonObjects.containsKey(beanName)) {
//singletonFactories和earlySingletonObjects互斥
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}
通过源码的分析,在spring中出现循环依赖的的情况的时候,比如A类包含有属性B,B类又包含属性A,在A和B都是单例的情况下,在创建 B的时候,并不是再去直接创建 A,而是通过放入缓存中地方ObjectFactory来创建实例,也就是singletonFactories,这样就解决了循环依赖的问题。
我们来模拟一个A和B互相包含的创建过程:
- 首先,创建A的实例, A在经历了一系列检查之后,调用addSingletonFactory()方法,这时singletonFactories就有了包含A的ObjectFactory。
- 然后,A开始填充属性的时候,发现B,这时B还没有创建,然后开始了B的创建过程。
- 这时候B在重复了1的条件之后,在填充属性的时候发现A,注意这时候,singletonFactories已经有了包含A的ObjectFactory,所以在getSingleton()的时候能够获取到A的,这时B就已经完成了初始化。
- 在B创建完成之后,A中的属性B也就能够填充,A也完成了之后的初始化。
所以Spring解决循环依赖的思路的本质就是通过一个中间存储,提前暴露对象,来解决该问题的,也就是twosum。那么根据上面的代码分析,只需要二级缓存就可以了,为什么需要三级缓存?
为什么要用三级缓存
推断
只需要二级缓存就可以了,为什么需要三级缓存?
因为有代理的存在,为了避免每次获取都生成新的代理,所以采用了三级。
分析
这个得从addSingletonFactory(String beanName, ObjectFactory singletonFactory)这个方法说起,在上面的doCreateBean()源码中,我们看到,调用addSingletonFactory()的时候,传入了一个匿名函数, ObjectFactory的getObject()实际上调用的是getEarlyBeanReference()
addSingletonFactory(beanName, new ObjectFactory() {
public Object getObject() throws BeansException {
//匿名函数, getObject()实际上调用的是getEarlyBeanReference()
return getEarlyBeanReference(beanName, mbd, bean);
}
});
}
这时回到我们刚刚模拟创建过程的第三步:
实例B在创建属性A的时候,**A已经提前暴露在singletonFactories里了,**所以B是从singletonFactories里获取的A,那么实际上singletonObject = singletonFactory.getObject()执行的就是getEarlyBeanReference()这个获取方法( 匿名函数传进去的).如下图
在getEarlyBeanReference()中,当bean的后置处理器是SmartInstantiationAwareBeanPostProcessor类型的时候,还会在调用实现类的getEarlyBeanReference的方法。
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;
//当bean的后置处理器是SmartInstantiationAwareBeanPostProcessor类型的时候,还会在调用实现类的getEarlyBeanReference的方法
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
if (exposedObject == null) {
return exposedObject;
}
}
}
}
return exposedObject;
}
我们来看下SmartInstantiationAwareBeanPostProcessor有哪些实现类
其中的一个实现类AbstractAutoProxyCreator,很明显,这是一个生成代理类的工具类,那么这时候, 我们只要验证需要被代理的类在调用了这个方法的时候是不是每次都会生成新的代理对象就可以了。
//AbstractAutoProxyCreator实现类中getEarlyBeanReference方法
public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
this.earlyProxyReferences.add(cacheKey);
return wrapIfNecessary(bean, beanName, cacheKey);
}
代码举证
如下面代码所示,我们构建了一个循环依赖的示例代码
@Service
public class TestA {
@Autowired
TestB testB;
}
@Service
public class TestB {
@Autowired
TestA testA;
}
现在启动spring容器,观察刚刚的模拟条件3,B获取A属性的时候,是否会生成新的对象。
这是 已经执行了singletonFactory.getObject()的时候获取的A对象示例
多次执行,发现A对象的地址没有变,所以刚刚的推论不成立。
问题出在哪里呢?
是因为A没有被代理,所以不会生成新的对象,我们下面增加代理类,重新验证。
@Aspect
@Component
public class AopProxy {
@Around("execution(public * com.test.impl.TestA.*(..))")
public void test(){
System.out.println("测试代理对象");
}
}
@Service
public class TestA {
@Autowired
TestB testB;
public void test(){
System.out.println("我是被代理的");
}
}
@Service
public class TestB {
@Autowired
TestA testA;
}
增加了AopProxy代理类来代理TestA,然后继续试验。
上图是第一次获取和debug再次执行singletonFactory.getObject()的时候,很明显对象地址不同,TestA类被代理后,每次执行singletonFactory.getObject()方法,都会生成一个新的代理对象。
原因分析
回到上文,如果使用二级缓存,每次调用都生成一个新的对象,会产生什么后果?
从文章读下来,我们知道只有是单例模式并且是属性注入的循环依赖,spring才能解决,如果只使用二级缓存,注入会出现什么问题呢?伪代码如下:
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
//原来第三级缓存变成二级缓存,这时如果beanName被代理,则每次get都会生成一个新的对象。
singletonObject = singletonFactory.getObject();
this.singletonFactories.remove(beanName);
}
}
}
}
return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
结论
通过上面伪代码分析,如果变成二级缓存,则该对象发生多个属性循环依赖的时候(TestA 包含属性B,C,B和C均依赖A),则每次都会生成一个新的代理对象,这样就破坏了单例模式的语义,并且由于该bean提前暴露的是一个半成品,并没有完全加载成功,那样singletonObject会存在大量的半成品bean,破坏了bean的生命周期。