分享
  1. 首页
  2. 主题
  3. 每日一学

Java高手提薪精选–Spring源码解析到手写核心组件|已完结

yuyandemeili · · 217207 次点击 · 开始浏览 置顶
这是一个创建于 的主题,其中的信息可能已经有所发展或是发生改变。

获客:666it.top/14745/ 从 Spring 循环依赖说开去:三级缓存 VS 早期暴露对象的精妙权衡 ** 在 Java 开发领域,Spring 框架无疑是最为耀眼的明星之一,它以强大的依赖注入和面向切面编程能力,极大地提升了企业级应用开发的效率和可维护性。然而,在 Spring 的使用过程中,一个经典的问题时常困扰着开发者 —— 循环依赖。为了解决这个问题,Spring 引入了三级缓存机制与早期暴露对象的概念,二者相互配合,形成了精妙的解决方案。本文将深入剖析这一过程,揭示其中蕴含的设计智慧。 一、循环依赖:Spring Bean 创建的 "死胡同" 循环依赖,顾名思义,是指多个 Bean 之间相互依赖,形成了一个闭环。例如,Bean A 依赖于 Bean B,而 Bean B 又依赖于 Bean A,这种情况在复杂的项目架构中并不罕见。当 Spring 容器在创建这些 Bean 时,就会陷入一个看似无解的困境:创建 A 需要先创建 B,而创建 B 又需要先创建 A,如同鸡生蛋、蛋生鸡的问题,导致 Bean 无法正常初始化。 在 Spring 中,循环依赖主要分为三种类型:构造器循环依赖、setter 方法循环依赖和字段注入循环依赖。其中,构造器循环依赖最为棘手,因为构造器在对象创建时就会被调用,一旦出现循环依赖,Spring 容器将无法完成 Bean 的实例化,直接抛出异常;而 setter 方法循环依赖和字段注入循环依赖,Spring 则可以通过三级缓存机制和早期暴露对象的策略来巧妙化解。 二、早期暴露对象:打破循环依赖的 "钥匙" 早期暴露对象,是 Spring 解决循环依赖的关键一环。其核心思想在于,在 Bean 的生命周期中,将尚未完成初始化(如尚未填充依赖属性、尚未执行初始化方法)的 Bean 实例提前暴露出来,让其他依赖它的 Bean 可以先获取到这个 "半成品",从而打破循环依赖的僵局。 具体来说,在 Spring 创建 Bean 的过程中,当完成 Bean 实例的创建(通过构造函数实例化对象)后,会立即将这个刚创建出来的 Bean 实例添加到二级缓存(earlySingletonObjects)中。此时的 Bean 只是一个原始对象,还未进行属性填充和初始化操作。如果此时有其他 Bean 依赖于这个 Bean,那么就可以从二级缓存中获取到这个早期暴露的对象,从而避免了因等待完整的 Bean 初始化而导致的循环依赖问题。 例如,当 Spring 创建 Bean A 时,首先通过构造函数实例化 Bean A,然后将其放入二级缓存。接着,在为 Bean A 填充依赖的 Bean B 时,发现 Bean B 依赖于 Bean A,此时 Spring 就会从二级缓存中获取早期暴露的 Bean A 实例,注入到 Bean B 中,从而完成 Bean B 的创建,进而顺利完成 Bean A 的创建。 三、三级缓存:保障对象创建的 "安全网" Spring 的三级缓存机制,由一级缓存(singletonObjects)、二级缓存(earlySingletonObjects)和三级缓存(singletonFactories)组成,它们各司其职,共同保障了 Bean 的正确创建和循环依赖的解决。 1. 一级缓存:存储完整的单例 Bean 一级缓存是 Spring 缓存机制的核心,它存储着已经完全初始化的单例 Bean。当 Spring 容器需要获取一个单例 Bean 时,首先会从一级缓存中查找,如果找到了,直接返回该 Bean 实例,无需再进行复杂的创建和初始化操作。这大大提高了 Bean 的获取效率,同时也确保了整个应用中同一单例 Bean 的唯一性。 2. 二级缓存:存储早期暴露的 Bean 如前文所述,二级缓存用于存储早期暴露的 Bean 实例。在 Bean 的创建过程中,将刚实例化的 Bean 放入二级缓存,为解决循环依赖提供了可能。当其他 Bean 依赖于该 Bean 时,可以从二级缓存中获取早期暴露的对象,从而避免循环等待。而当 Bean 完成完整的初始化后,会从二级缓存中移除,并放入一级缓存。 3. 三级缓存:存储 Bean 的创建工厂 三级缓存存储的是ObjectFactory,即 Bean 的创建工厂。它的作用主要体现在处理 AOP 代理对象的创建上。在 Spring 中,如果一个 Bean 配置了 AOP 切面,那么在 Bean 初始化完成后,需要为其创建代理对象。而在解决循环依赖的过程中,为了避免多次创建代理对象,Spring 利用三级缓存来存储一个用于创建代理对象的工厂。 当从二级缓存中获取早期暴露的 Bean 时,如果该 Bean 需要创建代理对象,Spring 会从三级缓存中获取对应的ObjectFactory,通过调用ObjectFactory.getObject()方法来创建代理对象,并将代理对象放入二级缓存(同时移除三级缓存中的ObjectFactory)。这样,无论后续有多少个 Bean 依赖于该 Bean,获取到的都是同一个代理对象,保证了 AOP 代理的一致性和正确性。 四、精妙权衡:三级缓存与早期暴露对象的协同作用 三级缓存和早期暴露对象并非独立存在,而是相互配合,在性能、内存占用和功能完整性之间进行了精妙的权衡。 从性能角度来看,早期暴露对象使得 Spring 能够在 Bean 创建过程中尽早打破循环依赖,避免了因循环等待导致的阻塞,提高了 Bean 的创建效率。而三级缓存的存在,通过缓存 Bean 实例和创建工厂,减少了重复的对象创建和初始化操作,进一步提升了系统的性能。 在内存占用方面,虽然三级缓存会占用一定的内存空间,但通过合理的管理和对象生命周期的控制,避免了大量临时对象的创建和长时间驻留,将内存消耗控制在可接受的范围内。同时,早期暴露对象只是在 Bean 创建的特定阶段存在,随着 Bean 的初始化完成,会被完整的 Bean 实例所替代,不会造成额外的内存负担。 在功能完整性上,三级缓存和早期暴露对象的设计,不仅解决了循环依赖问题,还完美支持了 AOP 代理等 Spring 的核心功能。无论是普通 Bean 还是需要 AOP 增强的 Bean,都能在循环依赖的场景下正确创建和使用,确保了 Spring 框架的强大功能不受影响。 五、总结与启示 Spring 通过三级缓存机制和早期暴露对象的设计,巧妙地解决了循环依赖这一复杂问题,展现了其在设计上的精妙与优雅。这一解决方案不仅为开发者提供了可靠的依赖注入保障,更蕴含着深刻的编程思想和设计原则。 对于开发者来说,理解 Spring 循环依赖的解决方式,有助于我们更好地使用 Spring 框架,优化应用架构,避免因循环依赖导致的问题。同时,Spring 在这一问题上的处理方式也为我们在设计和开发复杂系统时提供了宝贵的借鉴,让我们学会在不同的需求和约束之间进行权衡,找到最优的解决方案。在未来的技术探索中,我们可以从 Spring 的设计理念中汲取灵感,不断提升自己的技术水平和解决问题的能力。 上述内容详细解析了 Spring 循环依赖解决方案。若你觉得某些部分需要补充案例或展开分析,或是有其他修改方向,欢迎随时告知。

有疑问加站长微信联系(非本文作者)

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

关注微信
217207 次点击
添加一条新回复 (您需要 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传

用户登录

没有账号?注册
(追記) (追記ここまで)

今日阅读排行

    加载中
(追記) (追記ここまで)

一周阅读排行

    加载中

关注我

  • 扫码关注领全套学习资料 关注微信公众号
  • 加入 QQ 群:
    • 192706294(已满)
    • 731990104(已满)
    • 798786647(已满)
    • 729884609(已满)
    • 977810755(已满)
    • 815126783(已满)
    • 812540095(已满)
    • 1006366459(已满)
    • 692541889

  • 关注微信公众号
  • 加入微信群:liuxiaoyan-s,备注入群
  • 也欢迎加入知识星球 Go粉丝们(免费)