Spring是如何解决循环依赖的?
通过三级缓存提前暴露对象解决的。
三级缓存存放了哪些对象信息?
- 一级缓存存放的是完整对象。
- 二级缓存存放的是那些属性还没赋值的对象。
- 三级缓存存放的是ObjectFactory<?>类型的lambda表达式,就是这用于处理AOP循环依赖的。
没有三级缓存,只有二级或一级也可以解决循环依赖
spring要保证几个事情,只有一级缓存处理流程没法拆分,复杂度也会增加,同时半成品对象可能会有空指针异常。而将完整对象和属性没有赋值的对象分开,处理起来更加优雅、简单、易扩展。另外spring的两大特性中不仅有IOC还有AOP,就是基于字节码增强后的方法,存放在什么地方,而三级缓存最主要,要解决的循环依赖就是对AOP的处理,但如果把AOP代理对象的创建提前,那么二级缓存也一样可以解决,但是违背了String创建对象的原则,Spring更喜欢把所有的普通Bean都初始化完成,在处理代理对象的初始化。
什么是循环依赖?
- 循环依赖分为三种,自身依赖于自身、互相循环依赖、多组循环依赖
- 无论循环依赖的数量有多少,循环依赖的本质是一样的。就是你的完整创建依赖于我,而我的完整创建也依赖于你,但我们互相没法解耦,最终导致依赖创建失败。
- 所以Spring提供了除了构造函数注入和原型注入外的,setter循环依赖注入解决方案。那么我们也可以先来尝试下这样的依赖,如果是我们自己处理的话需要如何解决
问题展现
public class Test{
public static void main(String [] args){
new AClass();
}
}
public AClass{
private BClass b = new BClass();
}
public BClass{
private AClass a = new AClass();
}
- 这段代码就是循环依赖的初级样子,互相依赖,就会形成死循环的模式。运行就会报错java.lang.StackOverflowError
- 这样的循环依赖代码是无法解决的,当你看到Spring中提供了get/set或者注解,这样之所以能解决,首先是进行了一定的解耦。让类的创建和属性的填充分离,先创建出半成品bean,再处理属性的填充,完成成品bean的提供。
问题处理
这部分核心代码就是解决循环依赖,演示如下
public class HandleTest{
private final static Map<String, Object> singletonObject = new ConcurrentHashMap<>(256);
public static void main(String[] args) throws Exception{
System.out.println(getBean(B.class).getA());
System.out.println(getBean(A.class).getB());
}
private static <T> T getBean(Class<T> beanClass) throws Exception{
String beanName = beanClass.getSimpleName().toLowerCase();
if(singletonObject.containsKey(beanName)){
return (T) singletonObject.get(beanName);
}
//实例化对象入缓存
Object obj = beanClass.newInstance();
singletonObject.put(beanName, obj);
//属性填充补全对象
Field[] fields = obj.getClass().getDeclaredFields();
for(Field field : fields){
field.setAccessible(true);
Class<?> fieldClass = fieldClass.getSimpleName().toLowerCase();
field.set(obj, singletonObject.containsKey(fieldBeanName) ? singletonObject.get(fieldBeanName) : getBean(fieldClass));
field.setAccessible(false);
}
return (T) obj;
}
}
class A{
private B b;
//...get/set
}
class B{
private A a;
//...get/set
}
- 这段代码提供了A、B两个类,互相有依赖。但在两个类中的依赖关系使用的是setter的方式进行填充。也就是只有这样才能避免两个类在创建之处不非得强依赖与另外一个对象。
- getBean是整个解决循环依赖的核心内容。A创建后填充属性时依赖B,那么就去创建B,在创建B开始填充时发现依赖于A,但此时A这个半成品对象已经放在缓存到singletonObject中了,所以B可以正常创建,在通过递归把A也创建完成了。
源码分析
整个处理spring循环依赖过程:
Spring循环依赖对象获取过程,有这份执行流程调试起来就方便多了。
总结:
1. 只有一级缓存并不能解决循环依赖。根据Spring中代码处理的流程,我们去分析一级缓存这样存放成品Bean的流程中,是不能解决循环依赖的问题的。因为A的成品创建依赖于B,B的成品创建又依赖于A,当需要补全B的属性时A还没有创建完,所以会出现死循环。
2. 有了二级缓存其实这个事处理起来就会容易,一个缓存用于存放成品对象,另一个缓存用于存放半成品对象。A在创建半成品对象后存放到缓存中,接下来补充A对象中依赖B的属性。B继续创建,创建半成品同样放到缓存中,在补充对象的A属性时,可以从半成品缓存中获取,现在B就是一个完整的对象了,而接下来像是递归操作一样A也是一个完整的对象了。
3. 有了二级缓存就可以解决Spring依赖了,三级缓存存在的意义是什么?三级缓存主要是解决Spring Aop的特性。AOP本身就是对方法的增强,是ObjectFactory<?>类型的almbda表达式,而Spring的原则又不希望将此类类型的Bean前置创建,所以要存放到三级缓存中处理。其实整体处理过程类似,唯独是B在填充属性A时,先查询成品缓存,再查询半成品缓存,最后在看看有没有单例工程类在三级缓存中。最终获取到以后调用getObject方法返回代理引用或者原始引用。至此也就解决了Spring AOP所带来的三级缓存问题。
共同探讨学习技术创建技术氛围Day9884125