虽然spring已经设计了三级缓存机制尽可能地避免循环依赖(Circular dependencies),但在某些情况下,该问题仍然存在。
循环依赖,一般的解释是指两个或多个模块、组件或实体之间存在相互依赖的场景,但在本文,循环依赖指的是一种会导致应用无法正常启动的系统性问题。
首先需要明确的是,“三级缓存解决循环依赖“的结果,是在”已经创建了对象“的条件下完成的。
先得把对象创建出来,再谈解决问题
下面,引用spring获取bean的源码进行说明。
org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// Quick check for existing instance without full singleton lock.
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
if (!this.singletonLock.tryLock()) {
// Avoid early singleton inference outside of original creation thread.
return null;
}
try {
// Consistent creation of early reference within full singleton lock.
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
// Singleton could have been added or removed in the meantime.
if (this.singletonFactories.remove(beanName) != null) {
this.earlySingletonObjects.put(beanName, singletonObject);
}
else {
singletonObject = this.singletonObjects.get(beanName);
}
}
}
}
}
finally {
this.singletonLock.unlock();
}
}
}
return singletonObject;
}
其中,singletonObjects为一级缓存,该集合保存了已经初始化完毕,可直接使用的bean;
/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
earlySingletonObjects为二级缓存,保存了未初始化完成,但已经实例化的bean;
/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
而所谓的三级缓存,其实就是用于创建bean的工厂方法或构造器;
/** Creation-time registry of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new ConcurrentHashMap<>(16);
在向ioc容器请求bean时,首先会在一级缓存中查找,如果存在就直接返回;
Object singletonObject = this.singletonObjects.get(beanName);
然后再到二级缓存中查找,存在则返回,即便还未初始化完成;
singletonObject = this.singletonObjects.get(beanName);
还找不到时,最后从三级缓存中取出工厂方法或构造器(如果存在的话),创建对象并将其加入二级缓存;
// 获取工厂方法或构造器
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
// 创建bean
singletonObject = singletonFactory.getObject();
// 加入缓存
this.earlySingletonObjects.put(beanName, singletonObject);
以上便是经过简化的spring用于处理循环依赖的三级缓存机制。
然而,正是这种机制,会存在着这样的问题:如果现有A、B两个类,且它们是通过构造器互相注入参数的话,将会导致循环依赖,以下面的代码为例。
@Component
public class A {
final private B b;
A(B b) {
this.b = b;
}
}
@Component
public class B {
final private A a;
B(A a) {
this.a = a;
}
}
在创建a时,spring发现其构造函数中依赖到了B,于是开始创建b(spring的依赖注入机制【Dependency Injection】)。
注意,此时a还未实例化完成,因此,二级缓存中不存在对应的bean
而创建b时,spring又发现依赖到了A,接着转过头去创建a...
此时,spring就会检测到,无论是a还是b,都在处于创建状态中,没法完成依赖注入(DI)。
最终,抛出了循环依赖的异常。
图1 循环依赖异常
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference or an asynchronous initialization dependency? at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.beforeSingletonCreation(DefaultSingletonBeanRegistry.java:544) at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:312) ...
不过,要解决这个问题,其实也简单。
正如前面所说,三级缓存机制只是不能处理构造注入(Constructor-based Dependency Injection)方式互相引用的场景。
换个说法,只要避免互相引用的双方使用构造注入,让工厂方法或构造器完成bean的实例化即可。
事实上,除了构造注入外,spring还提供了其他的注入方式,如方法注入(Setter-based Dependency Injection)、字段注入(直接在字段上使用@Autowired、@Inject等注解标注)。
比方说,使用方法注入的方式,可以很好地解决这个问题:
只需要改动其中的一个
@Component
public class A {
private B b;
@Autowired
public void setB(B b) {
this.b = b;
}
}
这样一来,a便可不依赖b完成实例化,同时加入二级缓存,此后,b也可以从缓存中取出a并注入(初始化稍后执行),最终程序得以正常运行。
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
final String[] definitionNames = context.getBeanDefinitionNames();
for (String name : definitionNames) {
System.out.println(name);
}
}
图2 正常执行main方法的结果
总的来说,文中所举的例子只是一种极端的情况,在项目中并不常见,设计程序时稍作留意即可;而由于spring的三级缓存已经处理了大部分循环依赖的场景,一般情况下甚至都不会遇到循环依赖的问题。
以上。