-解决循环依赖问题)
解决循环依赖问题思路是使用二级缓存创建了实例后先放入 earlySingletonObjects 中加载所有属性成为成功实例后再放入 singletonObjects 中并从 earlySingletonObjects 中删除下图展示了二级缓存解决循环依赖的完整流程flowchart TD A[线程 1 调用 getBean A] -- B{singletonObjects 中是否有 A?} B -- 否 -- C[实例化 A 半成品] C -- D[将 A 放入 earlySingletonObjects] D -- E[遍历 A 的字段发现依赖 B] E -- F[递归调用 getBean B] F -- G{singletonObjects 中是否有 B?} G -- 否 -- H[实例化 B 半成品] H -- I[将 B 放入 earlySingletonObjects] I -- J[遍历 B 的字段发现依赖 A] J -- K{singletonObjects 中是否有 A?} K -- 否 -- L{earlySingletonObjects 中是否有 A?} L -- 是 -- M[从 earlySingletonObjects 获取 A 半成品] M -- N[将 A 注入 B] N -- O[移除 B 半成品将 B 成品放入 singletonObjects] O -- P[返回 B 成品给 A] P -- Q[将 B 注入 A] Q -- R[移除 A 半成品将 A 成品放入 singletonObjects] R -- S[返回 A 成品] T[线程 2 调用 getBean A] -- U{singletonObjects 中是否有 A?} U -- 否 -- V{earlySingletonObjects 中是否有 A?} V -- 是 -- W[直接获取 A 半成品] W -- X[尝试调用 A 依赖的 B 的方法] X -- Y{此时 B 是否已注入 A?} Y -- 否 -- Z[出现空指针异常] Y -- 是 -- AA[正常使用 A] B -- 是 -- AB[直接返回 A 成品] G -- 是 -- AC[直接返回 B 成品] K -- 是 -- AD[直接返回 A 成品] U -- 是 -- AE[直接返回 A 成品] V -- 否 -- AF[等待线程 1 完成或重新创建]public static Map earlySingletonObjects new HashMap(); //单例对象就存放半成品类型 public static Map singletonObjects new HashMap(); // 存放成品类型其实使用一级缓存也可以搞定但是放到多线程的环境中可能就会出现空指针问题。线程 1 刚把半成品 a 放入到缓存中还未来得及将 b 注入进去。此时线程 2 直接在缓存中获取到了 a在尝试调用其所依赖的 b 的任何方法时就会出现空指针异常。代码如下public class Main3 { public static Map earlySingletonObjects new HashMap(); // 存放成品类型 public static Map singletonObjects new HashMap(); // 单例对象就存放半成品类型 private static Object getSingleton(String className) { Object singletonObject singletonObjects.get(className); if (singletonObject null) { singletonObject earlySingletonObjects.get(className); } return singletonObject; } private static T T getBean(ClassT clazz) throws IllegalAccessException, InstantiationException { // 优先从缓存中获取 Object object getSingleton(clazz.getName()); if (object ! null) { return (T) object; } synchronized (singletonObjects) { object singletonObjects.get(clazz.getName()); if (object ! null) { return (T) object; } // 实例化对象 T instance clazz.newInstance(); earlySingletonObjects.put(clazz.getName(), instance); // 实例化后就放入缓存 // 获取当前类中的所有字段 Field[] fields clazz.getDeclaredFields(); for (Field field : fields) { // 允许访问私有变量 field.setAccessible(true); // 判断字段是否被 Load 注解修饰 boolean isUseLoad field.isAnnotationPresent(Load.class); if (!isUseLoad) { continue; } // 获取需要被注入的字段的 class Class? fieldType field.getType(); // 递归获取字段的实例对象 Object fieldBean getBean(fieldType); // 将实例对象注入到该字段中 field.set(instance, fieldBean); } earlySingletonObjects.remove(clazz.getName()); singletonObjects.put(clazz.getName(), instance); return instance; } } public static void main(String[] args) throws InstantiationException, IllegalAccessException { /* A a getBean(A.class); System.out.println(a.getB().getClass()); */ new Thread(() - { try { A a1 getBean(A.class); System.out.println(t1.a: a1.hashCode()); System.out.println(t1.b: a1.getB().hashCode()); } catch (IllegalAccessException | InstantiationException e) { e.printStackTrace(); } }).start(); new Thread(() - { try { A a1 getBean(A.class); System.out.println(t2.a: a1.hashCode()); System.out.println(t2.b: a1.getB().hashCode()); } catch (IllegalAccessException | InstantiationException e) { e.printStackTrace(); } }).start(); new Thread(() - { try { B b getBean(B.class); System.out.println(t3.b: b.hashCode()); System.out.println(t3.a: b.getA().hashCode()); } catch (IllegalAccessException | InstantiationException e) { e.printStackTrace(); } }).start(); } }public class A { Load private B b; public B getB() { return b; } } public class B { Load private A a; public A getA() { return a; } }三级缓存与二级缓存的对比二级缓存虽然能解决普通的循环依赖问题但在涉及 AOP 代理时存在明显不足。下面通过表格对比二级缓存与三级缓存在解决循环依赖、AOP 代理和并发安全方面的差异对比维度二级缓存三级缓存解决循环依赖可以解决普通单例 Bean 的循环依赖通过 earlySingletonObjects 暴露半成品供依赖方提前获取。同样可以解决循环依赖且处理方式更灵活通过 ObjectFactory 延迟获取目标对象。AOP 代理无法解决 AOP 代理问题。半成品直接放入缓存无法在注入前生成代理对象导致代理失效或注入原始对象。可以解决 AOP 代理问题。通过三级缓存的 ObjectFactory 在需要时才创建代理确保注入的是代理对象。并发安全earlySingletonObjects 未加锁多线程并发读取半成品时可能出现空指针异常。通过 synchronized 和 ObjectFactory 的延迟创建机制在并发场景下能更好地保证线程安全。Spring 之所以需要三级缓存核心原因在于AOP 代理对象的创建时机。当 Bean 需要被代理时代理对象必须在属性注入之前生成否则注入到依赖方中的将是原始对象而非代理对象。三级缓存通过 ObjectFactory 将代理的创建延迟到真正需要注入的时刻既保证了循环依赖的解决又确保了 AOP 代理的正确性。而二级缓存由于缺少这一层延迟机制无法在注入前动态生成代理因此 Spring 必须引入三级缓存来兼顾循环依赖与 AOP 代理的双重需求。总结二级缓存解决循环依赖的核心思路可以概括为以下几点实例化后立即放入 earlySingletonObjects 暴露半成品对象创建后先以半成品形式放入缓存供依赖方提前获取引用。属性注入时从缓存获取依赖对象递归创建依赖对象时优先从缓存中查找避免重复实例化。成品完成后移入 singletonObjects所有属性注入完成后将成品放入正式缓存并移除半成品。二级缓存无法解决 AOP 代理问题涉及代理对象时需要引入三级缓存来延迟创建代理。多线程下需注意 earlySingletonObjects 的并发访问半成品缓存未加锁并发读取时可能出现空指针异常。