spring 解决循环依赖
1、什么是循环依赖?
循环依赖其实就是循环引用,也就是两个或则两个以上的bean互相持有对方,最终形成闭环。比如A依赖于B,B依赖于C,C又依赖于A。如下图:
spring的单例对象的初始化主要分为三步:
1、实例化:其实也就是调用对象的构造方法实例化对象。
2、注入:填充属性,这一步主要是对bean的依赖属性进行填充。
3、初始化:属性注入后,执行自定义初始化。
2、Spring为了解决单例的循环依赖问题,使用了三级缓存
private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap(16);
这三级缓存分别指:
singletonObjects:单例对象的cache,一级缓存。
earlySingletonObjects :提前暴光的单例对象的Cache,二级缓存。
singletonFactories : 单例对象工厂的cache,三级缓存。
我们在创建bean的时候,首先想到的是从cache中获取这个单例的bean,这个缓存就是singletonObjects。主要调用方法就是:
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
synchronized(this.singletonObjects) {
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null) {
ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
return singletonObject;
}
分析getSingleton()的整个过程,Spring首先从一级缓存singletonObjects中获取。如果获取不到,并且对象正在创建中,就再从二级缓存earlySingletonObjects中获取。如果还是获取不到且允许singletonFactories通过getObject()获取,就从三级缓存singletonFactory.getObject()(三级缓存)获取,如果获取到了则:
从singletonFactories中移除,并放入earlySingletonObjects中。其实也就是从三级缓存移动到了二级缓存。
3.1、单例的setter注入
@Service
public class ServiceA {
@Autowired
private ServiceB serviceB;
}
@Service
public class ServiceB {
@Autowired
private ServiceA serviceA;
}
下面用一张图告诉你,spring是如何解决循环依赖的:
3.2、多例的setter注入
@Service
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class ServiceA {
@Autowired
private ServiceB serviceB;
}
@Service
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class ServiceB {
@Autowired
private ServiceA serviceA;
}
可能你会认为这种情况spring容器启动会报错,其实是不会,为什么呢?其实在AbstractApplicationContext类的refresh方法中告诉了我们答案,它会调用finishBeanFactoryInitialization方法,该方法的作用是为了spring容器启动的时候提前初始化一些bean。该方法的内部又调用了beanFactory.preInstantiateSingletons()方法:能够看出:非抽象、单例 并且非懒加载的类才能被提前初始bean。而多例即SCOPE_PROTOTYPE类型的类,非单例,不会被提前初始化bean,所以程序能够正常启动。如何让它提前初始化bean呢?只需要再定义一个单例的类,在它里面注入serviceA:
@Service
public class ServiceC {
@Autowired
private ServiceA serviceA;
}
重新启动程序,执行结果:果然出现了循环依赖。
注意:这种循环依赖问题是无法解决的,因为它没有用缓存,每次都会生成一个新对象。
3.3、构造器注入
@Service
public class ServiceA {
public ServiceA(ServiceB serviceB) {
}
}
@Service
public class ServiceB {
public ServiceB(ServiceA serviceA) {
}
}
出现了循环依赖,为什么呢?看出构造器注入没能添加到三级缓存,也没有使用缓存,所以也无法解决循环依赖问题。
3.4、单例的代理对象setter注入
这种注入方式其实也比较常用,比如平时使用:@Async注解的场景,会通过AOP自动生成代理对象。
@Service
@EnableAsync
public class ServiceA {
@Autowired
private ServiceB serviceB;
@Async
public void test1() {
System.out.println("async");
}
}
@Service
public class ServiceB {
@Autowired
private ServiceA serviceA;
}
程序启动会报错,出现了循环依赖:
说白了,bean初始化完成之后,后面还有一步去检查:第二级缓存是否存在, 代理对象和原始对象是否相等。到此发现第二级缓存存在并且代理对象和原始对象是不相等,因此抛出上面异常。如果这时候把ServiceA改个名字,改成:ServiceC,其他的都不变。
@Service
public class ServiceB {
@Autowired
private ServiceC serviceC;
}
@Service
@EnableAsync
public class ServiceC {
@Autowired
private ServiceB serviceB;
@Async
public void test1() {
System.out.println("async");
}
}
再重新启动一下程序,成功了,没有报错了!这又是为什么?
这就要从spring的bean加载顺序说起了,默认情况下,spring是按照文件完整路径递归查找的,按路径+文件名排序,排在前面的先加载。所以ServiceA比ServiceB先加载,而改了文件名称之后,ServiceB比ServiceC先加载。为什么ServiceB比ServiceC先加载就没问题呢?答案在下面这张图中:
这种情况ServiceC第二级缓存是空的,不需要跟原始对象判断,所以不会抛出循环依赖。
3.5、DependsOn循环依赖
@Service
@DependsOn("serviceB")
public class ServiceA {
}
@Service
@DependsOn("serviceA")
public class ServiceB {
}
这个例子中本来如果ServiceA和ServiceB都没有加@DependsOn注解是没问题的,反而加了这个注解会出现循环依赖问题。在AbstractBeanFactory类的doGetBean方法的这段代码中:初始化bean前,它会检查dependsOn的实例有没有循环依赖,如果有循环依赖则抛异常。