🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
在Spring框架中,循环依赖的解决是通过“三级缓存”机制来实现的。三级缓存包括: 1. **singletonObjects**:缓存经过了完整生命周期的bean 2. **earlySingletonObjects**:缓存未经过完整生命周期的bean,如果某个bean出现了循环依赖, 就会提前把这个暂时未经过完整生命周期的bean放入earlySingletonObjects中,这个bean如果 要经过AOP,那么就会把代理对象放入earlySingletonObjects中,否则就是把原始对象放入 earlySingletonObjects,但是不管怎么样,就是是代理对象,代理对象所代理的原始对象也是 没有经过完整生命周期的,所以放入earlySingletonObjects我们就可以统一认为是未经过完整 生命周期的bean。 3. **singletonFactories**:缓存的是一个ObjectFactory,也就是一个Lambda表达式。在每个Bean 的生成过程中,经过实例化得到一个原始对象后,都会提前基于原始对象暴露一个Lambda表达 式,并保存到三级缓存中,这个Lambda表达式可能用到,也可能用不到,如果当前Bean没有出 现循环依赖,那么这个Lambda表达式没用,当前bean按照自己的生命周期正常执行,执行完后 直接把当前bean放入singletonObjects中,如果当前bean在依赖注入时发现出现了循环依赖 (当前正在创建的bean被其他bean依赖了),则从三级缓存中拿到Lambda表达式,并执行 Lambda表达式得到一个对象,并把得到的对象放入二级缓存((如果当前Bean需要AOP,那么 执行lambda表达式,得到就是对应的代理对象,如果无需AOP,则直接得到一个原始对象))。 要理解为什么需要三级缓存,而不仅仅是二级缓存,我们需要深入探讨Spring处理循环依赖的过程。 ### 循环依赖的场景 假设存在两个类 `A` 和 `B`,它们相互依赖: * `A` 依赖 `B`,同时 `B` 也依赖 `A`。 当Spring容器初始化 `A` 时,会检测到 `A` 依赖 `B`,因此会先去创建 `B`。但在创建 `B` 的过程中,发现 `B` 依赖于 `A`,于是再次去创建 `A`。此时,如果没有循环依赖的解决机制,会导致循环调用,最终引发栈溢出。 ### 二级缓存的不足 在二级缓存的场景下,`earlySingletonObjects` 存储的是已经实例化但尚未初始化完成的对象。假设我们只有一级和二级缓存,Spring可能在注入依赖时直接使用二级缓存中的未完全初始化的对象。 **问题在于**: * 如果某个Bean `A`在其依赖注入过程中进行了代理(如AOP场景下需要生成一个代理对象),直接将未代理的 `A` 放入二级缓存中,依赖 `A` 的Bean `B`会得到一个未被代理的原始对象。这会导致代理失效的问题,无法实现预期的功能。 ### 三级缓存的作用 三级缓存的引入解决了上述问题。具体步骤如下: 1. **三级缓存(`singletonFactories`)**:存放的是一个`ObjectFactory`,这个工厂可以在需要的时候创建一个“早期对象”,且这个对象已经是代理后的对象(如果需要的话)。在循环依赖中,Spring首先会从三级缓存中获取对象。如果需要代理,那么通过这个工厂获取的对象将是已经代理好的对象。 2. **二级缓存(`earlySingletonObjects`)**:当从三级缓存中获取到对象后,会将其放入二级缓存,以避免反复调用`ObjectFactory`。 3. **一级缓存(`singletonObjects`)**:对象完全初始化完成后(包括依赖注入、代理处理等),它会被放入一级缓存。 ### 总结 三级缓存比二级缓存多了一个`ObjectFactory`,用于在对象尚未完全初始化时提供早期的代理对象。这样可以确保在循环依赖的情况下,Spring能够处理代理对象,并确保各个Bean能获取到正确的依赖对象。 如果只有二级缓存而没有三级缓存,无法确保注入到其他Bean中的对象是代理后的对象,导致AOP等功能无法正常工作。因此,三级缓存是必要的。