前提
在我们的就旧项目中所有的service bean都带上@scope(“prototype”)这样的注解,在开发新的业务时,出现两bean之间的相互依赖,A依赖B、 B依赖A导致项目启动不了。
bean 循环依赖
模拟上面的业务场景,我们创建3个类,这3个类的依赖关系如下:
┌─────┐
| AA
↑ ↓
| BB
↑ ↓
| CC
└─────┘
@Component
@Scope("prototype")
public class AA {
private BB bb;
public AA(BB bb) {
this.bb =bb;
}
}
@Component
@Scope("prototype")
public class BB {
private CC cc;
public BB(CC cc) {
this.cc =cc;
}
}
@Component
@Scope("prototype")
public class CC {
private AA aa;
public CC(AA aa) {
this.aa =aa;
}
}
由于spring bean加载机制是用到才会去创建bean的,所以在入口方法我们注入AA类。
启动时,我们发现启动不了,提示 The dependencies of some of the beans in the application context form a cycle
我们尝试通过@Autowired注入,重新启动下,启动不成功。
我们尝试将@Scope("prototype")注释后,重新启动下,启动成功。
Spring解决循环依赖
基于构造器注入
spring 在创建bean的时候,基于构造器注入对象的bean,但是如果bean之间是循环关系的,A bean 要注入b bean ,b bean 在创建的时候又要注入A bean,两个bean就不能完成注入了,并抛出beanccurrentlyincreationexception,典型的先有鸡还是先有蛋的场景。
基于字段注入 单例模式
首先在解决注入问题先,我们先逆向推理下,spring是怎么实现注入的。
public static Map<String,Object> map = new ConcurrentHashMap();
public static Object getBean(Class classz) throws Exception {
// 将对象本身实例化
Object object = classz.getDeclaredConstructor().newInstance();
map.put(classz.getSimpleName(),object);
// 把所有字段当成需要注入的bean,创建并注入到当前bean中
Field[] fields = object.getClass().getDeclaredFields();
for(Field f:fields){
f.setAccessible(true);
//获取字段类型
Class<?> fieldClass = f.getType();
//从缓存中获取已经实例化的bean
Object waitInjectObject = map.get(fieldClass.getSimpleName());
if(waitInjectObject !=null){
f.set(object,waitInjectObject);
}else {
f.set(object,getBean(fieldClass));
}
}
return object;
}
我的想法是通过一个缓存对象,将已创建好的bean存储起来,当有需要的时候从缓存获取。
接下来我们再看下spring是怎么解决的。
#AbstractAutowireCapableBeanFactory
在AbstractAutowireCapableBeanFactory有这么一句话Eagerly cache singletons to be able to resolve circular references 主动缓存单例,以便能够解析循环引用。
Spring内部维护了三个Map,也就是我们通常说的三级缓存
- singletonObjects 完整的单例bean
- singletonFactories bean工厂,bean的信息
- earlySingletonObjects 早期的bean对象,不完整的bean
singletonFactories 获取到bean工厂,然后创建未解决依赖的bean存在在earlySingletonObjects,最后调用doResolveDependency方法从earlySingletonObjects获取到bean注入到字段中,完成注入,最后将完整的bean存在到singletonObjects中。
多例的情况,由于无法使用三级缓存,所以无法解决循环依赖的问题。