1. 问题描述
了解问题的本质再分析问题,往往更利于对问题有更深入的了解和研究。所以我们在分析 Spring 关于循环依赖的源码之前,先要了解下什么是循环依赖。
循环依赖分为三种,自身依赖于自身、互相循环依赖、多组循环依赖。
但无论循环依赖的数量有多少,循环依赖的本质是一样的。就是你的完整创建依赖于我,而我的完整创建也依赖于你,但我们互相没法解耦,最终导致依赖创建失败。
所以 Spring 提供了除了构造函数注入和原型注入外的,setter循环依赖注入解决方案。那么我们也可以先来尝试下这样的依赖,如果是我们自己处理的话该怎么解决。
2. 问题体现
- public class ABTest {
- public static void main(String[] args) {
- new ClazzA();
- }
- }
- class ClazzA {
- private ClazzB b = new ClazzB();
- }
- class ClazzB {
- private ClazzA a = new ClazzA();
- }
- 这段代码就是循环依赖最初的模样,你中有我,我中有你,运行就报错 java.lang.StackOverflowError
- 这样的循环依赖代码是没法解决的,当你看到 Spring 中提供了 get/set 或者注解,这样之所以能解决,首先是进行了一定的解耦。让类的创建和属性的填充分离,先创建出半成品Bean,再处理属性的填充,完成成品Bean的提供。
3. 问题处理
在这部分的代码中就一个核心目的,我们来自己解决一下循环依赖,方案如下:
- public class CircleTest {
- private final static Map<String, Object> singletonObjects = 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 (singletonObjects.containsKey(beanName)) {
- return (T) singletonObjects.get(beanName);
- }
- // 实例化对象入缓存
- Object obj = beanClass.newInstance();
- singletonObjects.put(beanName, obj);
- // 属性填充补全对象
- Field[] fields = obj.getClass().getDeclaredFields();
- for (Field field : fields) {
- field.setAccessible(true);
- Class<?> fieldClass = field.getType();
- String fieldBeanName = fieldClass.getSimpleName().toLowerCase();
- field.set(obj, singletonObjects.containsKey(fieldBeanName) ? singletonObjects.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 这个半成品对象已经存放在缓存到singletonObjects 中了,所以 B 可以正常创建,在通过递归把 A 也创建完整了。
四、源码分析
1. 说说细节
通过上面的例子我们大概了解到,A和B互相依赖时,A创建完后填充属性B,继续创建B,再填充属性A时就可以从缓存中获取了,如下:
那这个解决循环依赖的事放到 Spring 中是什么样呢?展开细节!
虽然,解决循环依赖的核心原理一样,但要放到支撑起整个 Spring 中 IOC、AOP 特性时,就会变得复杂一些,整个处理 Spring 循环依赖的过程如下;
- 以上就是关于 Spring 中对于一个有循环依赖的对象获取过程,也就是你想要的说说细节
- 乍一看是挺多流程,但是这些也基本是你在调试代码时候必须经过的代码片段,拿到这份执行流程,再调试就非常方便了。