返回

揭秘 Spring 循环依赖:三级缓存还是二级缓存?

后端

前言

在 Spring 框架的日常开发中,bean 之间的循环依赖问题十分常见。为了简化开发者的工作,Spring 巧妙地解决了此类问题,让开发者无感知。本文将深入分析 Spring 如何利用三级缓存,而非二级缓存,来化解循环依赖难题。

Spring 的 IoC 容器

Spring 采用基于依赖注入(IoC)的容器,负责管理和实例化应用程序中的对象。IoC 容器维护着一个 BeanFactory,其中存储着所有已创建的 bean。当应用程序需要一个 bean 时,IoC 容器会先检查 BeanFactory 中是否已存在该 bean 的实例。如果存在,则直接返回实例;如果不存在,则会创建一个新的实例并将其存储在 BeanFactory 中。

循环依赖问题

循环依赖是指两个或多个 bean 相互依赖的情况。例如,bean A 依赖于 bean B,而 bean B 又依赖于 bean A。当 IoC 容器尝试实例化 bean A 时,它需要首先实例化 bean B;而当它尝试实例化 bean B 时,它又需要首先实例化 bean A。这种互相依赖会导致死锁,无法实例化任何 bean。

二级缓存的局限性

二级缓存是一种缓存机制,用于存储已实例化的 bean。当 IoC 容器需要一个 bean 时,它会首先检查二级缓存中是否已存在该 bean 的实例。如果存在,则直接返回实例;如果不存在,则会创建一个新的实例并将其存储在二级缓存中。

然而,二级缓存无法解决循环依赖问题。这是因为,当 IoC 容器尝试从二级缓存中获取 bean 实例时,它需要先实例化该 bean。而对于循环依赖的 bean,在实例化过程中会再次遇到循环依赖,导致死锁。

三级缓存的引入

为了解决循环依赖问题,Spring 引入了三级缓存。三级缓存将二级缓存拆分成两个独立的缓存:早期单例对象缓存和完全单例对象缓存。

早期单例对象缓存

早期单例对象缓存用于存储正在被实例化的 bean 的早期实例。当 IoC 容器需要实例化一个 bean 时,它会首先检查早期单例对象缓存中是否已存在该 bean 的早期实例。如果存在,则直接返回该早期实例;如果不存在,则会创建一个新的早期实例并将其存储在早期单例对象缓存中。

完全单例对象缓存

完全单例对象缓存用于存储已完全实例化的 bean。当一个 bean 被完全实例化后,它会从早期单例对象缓存中移除,并存储在完全单例对象缓存中。当 IoC 容器需要一个 bean 时,它会首先检查完全单例对象缓存中是否已存在该 bean 的实例。如果存在,则直接返回实例;如果不存在,则会从早期单例对象缓存中获取早期实例并完成实例化,然后将其存储在完全单例对象缓存中。

解决循环依赖

通过三级缓存,Spring 可以解决循环依赖问题。当遇到循环依赖时,IoC 容器会将相互依赖的 bean 的早期实例存储在早期单例对象缓存中。这样,当 IoC 容器尝试实例化某个 bean 时,它可以从早期单例对象缓存中获取其早期实例,而不会再次遇到循环依赖。当所有循环依赖的 bean 都被完全实例化后,它们会从早期单例对象缓存中移除并存储在完全单例对象缓存中。

总结

Spring 利用三级缓存(而不是二级缓存)巧妙地解决了循环依赖问题。早期单例对象缓存和完全单例对象缓存的协作,允许 IoC 容器在不陷入死锁的情况下实例化相互依赖的 bean。这大大简化了开发者的工作,让他们可以专注于应用程序逻辑,而无需担心循环依赖带来的复杂性。