前言
问题
- 什么是循环依赖?
- 如何检测是否存在循环依赖?
- 如何解决循环依赖?
- 多例能否解决循环依赖?
- 单例的情况下,虽然可以解决循环依赖,是否存在其他问题?
- 为什么采用三级缓存解决循环依赖?如果直接将早期bean丢到二级缓存可以么?
1.什么是循环依赖?
A依赖B,B依赖C,C依赖A,形成一个环形。
2.如何检测是否存在循环依赖?
单例bean
检测原理:使用一个列表来记录正在创建中的bean,bean创建之前,先去记录中看一下自己是否已经在列表中了,如果在,说明存在循环依赖,如果不在,则将其加入到这个列表,bean创建完毕之后,将其再从这个列表中移除。
源码:DefaultSingletonBeanRegistry类中有一个beforeSingletonCreation方法
protected void beforeSingletonCreation(String beanName) {
if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
}
public BeanCurrentlyInCreationException(String beanName) {
super(beanName,
"Requested bean is currently in creation: Is there an unresolvable circular reference?");
}
private final Set<String> singletonsCurrentlyInCreation =
Collections.newSetFromMap(new ConcurrentHashMap<>(16));
private final Set<String> inCreationCheckExclusions =
Collections.newSetFromMap(new ConcurrentHashMap<>(16));
我们来看这个表达式,第一个条件是inCreationCheckExclusions不包含这个bean,第二个条件是singletonsCurrentlyInCreation不能添加这个bean,这个时候会抛出一个异常。我们在异常这里打一个断点观察
当前正在创建的set中无法添加这个bean,满足条件二。排除set里面没有值,不包含这个bean,满足第一个条件。
非单例bean
AbstractBeanFactory类中的 doGetBean 方法
protected <T> T doGetBean(String name, @Nullable Class<T> requiredType,
@Nullable Object[] args, boolean typeCheckOnly) throws BeansException {}
273 - 275 行
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
348 - 359 行
else if (mbd.isPrototype()) {
// It's a prototype -> create a new instance.
Object prototypeInstance = null;
try {
beforePrototypeCreation(beanName);
prototypeInstance = createBean(beanName, mbd, args);
}
finally {
afterPrototypeCreation(beanName);
}
beanInstance = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}
3.Spring如何解决循环依赖的问题
spring创建bean主要的几个步骤:
-
步骤1:实例化bean,即调用构造器创建bean实例
-
步骤2:填充属性,注入依赖的bean,比如通过set方式、@Autowired注解的方式注入依赖的bean
-
步骤3:bean的初始化,比如调用init方法等。
从上面3个步骤中可以看出,注入依赖的对象,有2种情况:
-
通过步骤1中构造器的方式注入依赖
-
通过步骤2注入依赖
构造器方式
创建三个Service类,让他们循环依赖,a->b->c。下面给出A的示例,其他两个类似。
@Service
public class AaService {
BbService bbService;
@Autowired
public AaService(BbService bbService) {
this.bbService = bbService;
}
}
启动后会看到下面这样的输出,创建失败。
set方式
创建三个Service类,让他们循环依赖,d->e->f。下面给出D的示例,其他两个类似。
@Service
public class DdService {
EeService eeService;
@Autowired
public void setEeService(EeService eeService) {
this.eeService = eeService;
}
}
启动后会看到下面这样的输出,创建失败。
如果将方法上的注解移动到字段上时是可以启动成功的。
强制忽略循环依赖
spring:
main:
allow-circular-references: true
循环依赖无法解决的情况
只有单例的bean会通过三级缓存提前暴露来解决循环依赖的问题,而非单例的bean,每次从容器中获取都是一个新的对象,都会重新创建,所以非单例的bean是没有缓存的,不会将其放到三级缓存中。
探讨:为什么需要用3级缓存
如果只使用2级缓存,直接将刚实例化好的bean暴露给二级缓存出是否可以否?
先下个结论吧:不行。
这样做是可以解决:早期暴露给其他依赖者的bean和最终暴露的bean不一致的问题。
若将刚刚实例化好的bean直接丢到二级缓存中暴露出去,如果后期这个bean对象被更改了,比如可能在上面加了一些拦截器,将其包装为一个代理了,那么暴露出去的bean和最终的这个bean就不一样的,将自己暴露出去的时候是一个原始对象,而自己最终却是一个代理对象,最终会导致被暴露出去的和最终的bean不是同一个bean的,将产生意向不到的效果,而三级缓存就可以发现这个问题,会报错。
单例bean解决了循环依赖,还存在什么问题?
循环依赖的情况下,由于注入的是早期的bean,此时早期的bean中还未被填充属性,初始化等各种操作,也就是说此时bean并没有被完全初始化完毕,此时若直接拿去使用,可能存在有问题的风险。