Spring 解决循环依赖的核心武器——三级缓存(Three-Level Cache)机制

87 阅读12分钟

核心问题:循环依赖

想象一下场景:

  • ServiceA 需要注入 ServiceB 的实例。
  • ServiceB 需要注入 ServiceA 的实例。

如果 Bean 的创建过程是:

  1. 创建 ServiceA 实例。
  2. 需要注入 ServiceB,于是去创建 ServiceB
  3. 创建 ServiceB 实例。
  4. 需要注入 ServiceA,于是去创建 ServiceA... 陷入死循环!

Spring 的解决方案:三级缓存

Spring IoC 容器使用三级缓存来打破这个循环,同时还能优雅地处理 AOP 代理等场景。这三级缓存分别是:

  1. singletonObjects (一级缓存):

    • 作用:  存储完全初始化好的、可以安全使用的单例 Bean 实例。这是最终的归宿。
    • 类型:  Map<String, Object> (Bean 名称 -> Bean 实例)
  2. earlySingletonObjects (二级缓存):

    • 作用:  存储“早期”暴露出来的 Bean 实例。这个实例是已经创建但尚未完成属性注入的。关键在于,如果需要 AOP 代理,这里存放的可能是代理对象
    • 类型:  Map<String, Object> (Bean 名称 -> 早期 Bean 实例/代理实例)
    • 目的:  解决循环依赖的核心。当 A 创建过程中需要 B,B 创建过程中又需要 A 时,B 可以从这里拿到一个“半成品”的 A(可能是代理),完成自己的创建。
  3. singletonFactories (三级缓存):

    • 作用:  存储能够创建“早期” Bean 实例(并可能进行 AOP 代理包装)的工厂对象。
    • 类型:  Map<String, ObjectFactory<?>> (Bean 名称 -> 能产生早期 Bean 的工厂)
    • 目的:  延迟代理对象的创建。只有当真正发生循环依赖,并且需要提前暴露 Bean 时,才会调用这个 Factory 来获取 Bean(并可能创建代理),然后放入二级缓存。如果 Bean 不需要代理或者没有发生循环依赖,这个 Factory 可能永远不会被调用。这是一种优化。

实现思路

我们将创建一个简单的 IoC 容器类,包含这三级缓存和 Bean 的创建逻辑。

  1. 容器类 (SimpleIocContainer):

    • 管理 Bean 定义(类、依赖)。
    • 包含三个字典模拟三级缓存。
    • 包含一个集合跟踪正在创建的 Bean,用于检测循环依赖。
    • 提供 get_bean 方法获取 Bean 实例。
    • 提供 register_bean 方法注册 Bean 定义。
  2. get_bean 核心逻辑:

    • Step 1: 检查一级缓存 (singletonObjects)  - 如果存在,直接返回(最终成品)。

    • Step 2: 检查是否正在创建 - 如果当前 Bean 正在创建中(表示遇到循环依赖),则进行下一步。如果不是正在创建,则标记为“正在创建”。

    • Step 3: 检查二级缓存 (earlySingletonObjects)  - 如果在二级缓存找到,直接返回(半成品,可能是代理)。这是打破循环的关键点之一(B 找到 A 的半成品)。

    • Step 4: 检查三级缓存 (singletonFactories)  - 如果在三级缓存找到对应的 Factory:

      • 调用 Factory 的 get_object() 方法,得到早期 Bean 实例(这里会模拟可能发生的 AOP 代理)。
      • 将得到的早期 Bean 放入二级缓存 (earlySingletonObjects)。
      • 从三级缓存中移除该 Factory。
      • 返回早期 Bean。这是打破循环的关键点之二(B 通过 Factory 获取 A 的半成品)。
    • Step 5: 创建 Bean 实例 (如果前面都没找到)

      • 实例化 Bean(调用构造函数)。
      • 关键:  创建一个 ObjectFactory (用 lambda 模拟),并放入三级缓存 (singletonFactories)。这个 Factory 知道如何获取当前的“早期” Bean 实例(并在需要时应用代理,我们这里简化,直接返回实例)。
      • 属性注入:  递归调用 get_bean 获取所有依赖项,并设置到当前 Bean 实例上。
      • 获取最终 Bean 引用:  属性注入完成后,需要再次检查二级缓存。因为在注入依赖的过程中,可能因为循环依赖导致当前 Bean 的早期引用(可能是代理)被放入了二级缓存。如果二级缓存中有,则最终 Bean 应该是二级缓存中的那个(保证一致性);否则就是我们刚刚创建和注入完成的实例。
      • 放入一级缓存:  将最终完全初始化好的 Bean 放入一级缓存 (singletonObjects)。
      • 清理:  从二级和三级缓存中移除该 Bean 的相关条目。
      • 标记完成:  从“正在创建”集合中移除。
      • 返回 Bean。

Python 代码实现

import threading
import time
from typing import Dict, Any, Callable, Set, Tuple, Type

# 用于模拟 ObjectFactory<?>
ObjectType = Any
ObjectFactory = Callable[[], ObjectType]

class SimpleIocContainer:
    """
    一个简化的 IoC 容器,模拟 Spring 的三级缓存解决循环依赖。
    """
    def __init__(self):
        # 一级缓存:存储完全初始化好的单例 Bean (bean_name -> instance)
        self._singleton_objects: Dict[str, ObjectType] = {}
        # 二级缓存:存储早期暴露的 Bean(已实例化但未完全初始化,可能为代理) (bean_name -> instance)
        self._early_singleton_objects: Dict[str, ObjectType] = {}
        # 三级缓存:存储 Bean 工厂,用于延迟创建早期 Bean(或代理) (bean_name -> factory)
        self._singleton_factories: Dict[str, ObjectFactory] = {}
        # 存储 Bean 的定义 (bean_name -> (bean_class, [dep_name1, dep_name2,...]))
        self._bean_definitions: Dict[str, Tuple[Type, list[str]]] = {}
        # 跟踪当前正在创建过程中的 Bean 名称,用于检测循环依赖
        self._singletons_currently_in_creation: Set[str] = set()
        # 用于演示的锁,模拟并发获取 Bean 的情况(虽然 Spring 内部更复杂)
        self._lock = threading.RLock() # 可重入锁

    def register_bean(self, bean_name: str, bean_class: Type, *deps: str):
        """注册 Bean 定义"""
        print(f"[Register] 注册 Bean: {bean_name} (Class: {bean_class.__name__}, Deps: {list(deps)})")
        self._bean_definitions[bean_name] = (bean_class, list(deps))

    def get_bean(self, bean_name: str) -> ObjectType:
        """获取 Bean 实例,核心逻辑所在"""
        with self._lock: # 保证 Bean 创建过程的线程安全
            print(f"\n[Get Bean] 尝试获取 Bean: '{bean_name}'")

            # Step 1: 尝试从一级缓存获取 (最终成品)
            singleton = self._singleton_objects.get(bean_name)
            if singleton is not None:
                print(f"  -> Level 1 Cache Hit! ('{bean_name}' 已完全初始化)")
                return singleton

            # Step 2: 检查 Bean 是否正在创建中 (处理循环依赖的关键前置判断)
            if bean_name in self._singletons_currently_in_creation:
                print(f"  !! Detected: '{bean_name}' 正在创建中 (可能存在循环依赖)")
                # 这里不直接返回,继续检查二、三级缓存

            # Step 3: 尝试从二级缓存获取 (早期暴露的半成品)
            early_singleton = self._early_singleton_objects.get(bean_name)
            if early_singleton is not None:
                print(f"  -> Level 2 Cache Hit! ('{bean_name}' 早期实例)")
                # 注意:如果 AOP 代理发生在这里,返回的是代理对象
                return early_singleton

            # Step 4: 尝试从三级缓存获取 Factory,并创建早期 Bean
            factory = self._singleton_factories.get(bean_name)
            if factory is not None:
                print(f"  -> Level 3 Cache Hit! ('{bean_name}' 找到 Factory)")
                # 调用 Factory 创建早期 Bean (这里可能进行 AOP 代理)
                early_singleton = factory()
                print(f"  [Factory] '{bean_name}' 的 Factory 被调用,产生早期实例: {early_singleton}")
                # 放入二级缓存
                self._early_singleton_objects[bean_name] = early_singleton
                # 从三级缓存移除 (Factory 的使命完成了)
                del self._singleton_factories[bean_name]
                print(f"  [Cache Update] '{bean_name}' 移入 Level 2 Cache,从 Level 3 Cache 移除")
                return early_singleton # 返回早期实例

            # Step 5: 如果缓存都没有,开始创建 Bean
            if bean_name not in self._bean_definitions:
                raise ValueError(f"Bean '{bean_name}' 未定义")

            print(f"  -- No Cache Hit for '{bean_name}'. Starting creation...")

            # 标记 Bean 开始创建
            print(f"  [Creation] 标记 '{bean_name}' 为正在创建")
            self._singletons_currently_in_creation.add(bean_name)

            try:
                # 5.1 实例化 Bean (调用构造器)
                bean_class, deps = self._bean_definitions[bean_name]
                instance = bean_class()
                print(f"  [Creation] '{bean_name}' 实例已创建: {instance}")

                # 5.2 关键:创建并放入三级缓存 (允许其他 Bean 提前获取)
                # 这个 Factory 封装了获取早期 Bean 的逻辑(包括可能的代理)
                # 为了简化,这里的 Factory 直接返回刚创建的实例
                # 在真实的 Spring 中,这里会调用 getEarlyBeanReference,可能返回代理
                factory: ObjectFactory = lambda: self._get_early_bean_reference(bean_name, instance)
                self._singleton_factories[bean_name] = factory
                print(f"  [Cache Update] 为 '{bean_name}' 创建 Factory 并放入 Level 3 Cache")

                # 5.3 属性注入 (填充依赖)
                print(f"  [Injection] 开始为 '{bean_name}' 注入依赖: {deps}")
                for dep_name in deps:
                    # 递归调用 get_bean 获取依赖项
                    # 如果这里发生循环依赖,对方会通过上面的 Step 3 或 Step 4 获取到当前 Bean 的早期引用
                    print(f"    - '{bean_name}' 需要依赖 '{dep_name}', 递归调用 get_bean('{dep_name}')...")
                    dep_instance = self.get_bean(dep_name)
                    # 模拟注入 (设置属性)
                    setattr(instance, dep_name, dep_instance)
                    print(f"    - 依赖 '{dep_name}' ({dep_instance}) 注入到 '{bean_name}' 成功")

                # 5.4 依赖注入完成,准备放入一级缓存
                # 关键:检查当前 Bean 是否在注入期间被提前暴露并放入了二级缓存
                # 如果是,说明发生了循环依赖,并且可能已经创建了代理,最终实例应该是二级缓存中的那个
                final_instance = self._early_singleton_objects.get(bean_name, instance)
                if final_instance is not instance:
                     print(f"  [Consistency Check] '{bean_name}' 在注入期间被提前暴露 (Level 2),使用早期实例 {final_instance} 作为最终实例")
                else:
                    print(f"  [Consistency Check] '{bean_name}' 未在注入期间被提前暴露,使用当前实例 {instance} 作为最终实例")
                    final_instance = instance # 保持引用名称一致性

                # 5.5 放入一级缓存 (Bean 完全准备好了)
                self._singleton_objects[bean_name] = final_instance
                print(f"  [Cache Update] '{bean_name}' 完全初始化,放入 Level 1 Cache: {final_instance}")

                # 5.6 清理工作
                self._early_singleton_objects.pop(bean_name, None)
                self._singleton_factories.pop(bean_name, None)
                print(f"  [Cache Update] 从 Level 2 和 Level 3 Cache 清理 '{bean_name}'")

                return final_instance

            finally:
                # 标记 Bean 创建完成
                self._singletons_currently_in_creation.remove(bean_name)
                print(f"  [Creation] 标记 '{bean_name}' 创建完成")


    def _get_early_bean_reference(self, bean_name: str, bean_instance: ObjectType) -> ObjectType:
        """
        模拟 Spring 的 getEarlyBeanReference 方法。
        这个方法是 AOP 等后处理器介入,对早期 Bean 进行包装(如创建代理)的地方。
        为了简化,我们这里直接返回原始实例,但打印一条信息表示可以进行包装。
        """
        print(f"    * (Simulating AOP) '{bean_name}' Factory 调用: 获取早期引用 {bean_instance}. 可在此处进行 AOP 代理包装.")
        # 在真实 Spring 中,这里会检查是否有 InstantiationAwareBeanPostProcessor
        # 并调用其 getEarlyBeanReference 方法,可能返回原始 bean,也可能返回代理 proxy
        return bean_instance


# --- 演示 ---

# 定义有循环依赖的 Service 类
class ServiceA:
    def __init__(self):
        self.service_b: ServiceB = None
        print(">>> ServiceA instance created")

    def do_something_a(self):
        print(f"ServiceA doing something, needs ServiceB -> {self.service_b.__class__.__name__}")
        self.service_b.do_something_b_for_a()

    def do_something_a_for_b(self):
        print("ServiceA helping ServiceB")

class ServiceB:
    def __init__(self):
        self.service_a: ServiceA = None
        print(">>> ServiceB instance created")

    def do_something_b(self):
        print(f"ServiceB doing something, needs ServiceA -> {self.service_a.__class__.__name__}")
        self.service_a.do_something_a_for_b()

    def do_something_b_for_a(self):
        print("ServiceB helping ServiceA")


# 创建容器
container = SimpleIocContainer()

# 注册 Bean 定义,显式声明依赖关系
container.register_bean("serviceA", ServiceA, "serviceB")
container.register_bean("serviceB", ServiceB, "serviceA")

# --- 开始获取 Bean ---
print("\n=====================================")
print("开始获取 Service A...")
print("=====================================")
service_a_instance = container.get_bean("serviceA")

print("\n=====================================")
print("获取 Service A 完成.")
print("=====================================")

print("\n=====================================")
print("尝试获取 Service B (应该直接从缓存获取)...")
print("=====================================")
service_b_instance = container.get_bean("serviceB")
print("=====================================")
print("获取 Service B 完成.")
print("=====================================")


# --- 验证 ---
print("\n--- Verification ---")
print(f"Got serviceA instance: {service_a_instance}")
print(f"Got serviceB instance: {service_b_instance}")
print(f"Is serviceA's dependency serviceB correct? {service_a_instance.service_b == service_b_instance}")
print(f"Is serviceB's dependency serviceA correct? {service_b_instance.service_a == service_a_instance}")

# 调用方法,检查依赖是否正常工作
print("\n--- Invoking Methods ---")
service_a_instance.do_something_a()
print("---")
service_b_instance.do_something_b()

# 打印最终缓存状态
print("\n--- Final Cache States ---")
print("Level 1 (singletonObjects):", container._singleton_objects)
print("Level 2 (earlySingletonObjects):", container._early_singleton_objects) # 应该是空的
print("Level 3 (singletonFactories):", container._singleton_factories) # 应该是空的


运行输出分析 (关键步骤)

当你运行这段代码,你会看到类似以下的日志输出,清晰地展示了三级缓存如何工作的:

  1. get_bean('serviceA') 启动:

    • 缓存未命中。
    • 标记 serviceA 为正在创建。
    • 创建 ServiceA 实例 (>>> ServiceA instance created)。
    • 为 serviceA 创建 Factory 并放入三级缓存 (Level 3 Cache)。
    • 开始为 serviceA 注入依赖 serviceB
    • 递归调用 get_bean('serviceB')
  2. get_bean('serviceB') (递归中):

    • 缓存未命中。
    • 标记 serviceB 为正在创建。
    • 创建 ServiceB 实例 (>>> ServiceB instance created)。
    • 为 serviceB 创建 Factory 并放入三级缓存 (Level 3 Cache)。
    • 开始为 serviceB 注入依赖 serviceA
    • 递归调用 get_bean('serviceA')
  3. get_bean('serviceA') (再次递归):

    • 一级缓存未命中。
    • 检测到 serviceA 正在创建中 (循环依赖信号!)。
    • 二级缓存未命中。
    • 三级缓存命中!  找到 serviceA 的 Factory。
    • 调用 serviceA 的 Factory (_get_early_bean_reference 被调用,模拟 AOP)。
    • 返回早期 serviceA 实例。
    • 将早期 serviceA 实例放入二级缓存 (Level 2 Cache)。
    • 从三级缓存移除 serviceA 的 Factory。
    • 返回早期 serviceA 实例给 get_bean('serviceB') 调用。
  4. 回到 get_bean('serviceB'):

    • 成功获取到依赖 serviceA (早期实例)。
    • 将早期 serviceA 注入到 serviceB 实例的 service_a 属性。
    • serviceB 属性注入完成。
    • 检查二级缓存,发现 serviceB 自己不在里面。
    • 将完全初始化的 serviceB 实例放入一级缓存 (Level 1 Cache)。
    • 清理二、三级缓存中 serviceB 的条目。
    • 标记 serviceB 创建完成。
    • 返回完全初始化的 serviceB 实例给最初的 get_bean('serviceA') 调用。
  5. 回到最初的 get_bean('serviceA'):

    • 成功获取到依赖 serviceB (完全初始化的实例)。
    • 将 serviceB 注入到 serviceA 实例的 service_b 属性。
    • serviceA 属性注入完成。
    • 关键:  检查二级缓存,发现 serviceA 在里面 (是第 3 步放进去的那个早期实例)。
    • 使用二级缓存中的 serviceA 实例作为最终实例(保证一致性)。
    • 将这个最终的 serviceA 实例放入一级缓存 (Level 1 Cache)。
    • 清理二、三级缓存中 serviceA 的条目(移除第 3 步放入 Level 2 的早期实例)。
    • 标记 serviceA 创建完成。
    • 返回最终的 serviceA 实例。
  6. 后续 get_bean('serviceB') 调用: 直接从一级缓存获取,非常快。

总结

这个 Python 模拟清晰地展示了:

  • 三级缓存的分工:  Level 1 存成品,Level 2 存半成品(可能是代理),Level 3 存半成品的工厂(延迟创建和代理)。
  • 如何打破循环:  当 A -> B -> A 发生时,B 请求 A 时,不会重新创建 A,而是通过 Level 3 的 Factory 得到一个 A 的早期引用(可能被代理),并放入 Level 2。B 完成创建后,A 继续创建,最终完成后,确保使用的 A 实例与 B 中注入的 A 实例是同一个(通过 Level 2 的传递)。
  • 为什么需要三级(而不是两级):  主要是为了延迟代理对象的创建。如果只有 Level 1 和 Level 2(直接存早期对象),那么 A 一创建就可能需要判断是否代理并放入 Level 2。如果 A 不需要代理,或者最终没有发生循环依赖,这个代理判断和创建(如果需要)就可能是浪费的。Level 3 的 Factory 把这个决策和动作推迟到真正发生循环依赖且需要提前暴露的时候才执行,更加高效。

希望这个从零实现的模拟和讲解,能让你对 Spring 的三级缓存机制有一个更深入、清晰的理解!