☰
Spring循环依赖与三级缓存:源码级剖析Bean创建过程
2026/10/28 22:47:59 网站建设 项目流程

1. 循环依赖是什么,Spring 为什么一开始也拿它没办法

1.1 一个最小可复现的循环依赖示例

很多人第一次接触循环依赖是在网上看到别人的报错截图,然后自己也写了一个 Demo,复制出一个诡异的问题。最简单的场景就是这样两个类:

@Service public class UserService { @Autowired private OrderService orderService; }
@Service public class OrderService { @Autowired private UserService userService; }

启动 Spring 容器时,控制台会出现类似的错误:

BeanCurrentlyInCreationException: Error creating bean with name 'userService': Requested bean is currently in creation: Is there an unresolvable circular reference?

这句话翻译成人话就是:Spring 正在创建 UserService 这个 Bean,结果在创建过程中发现它要依赖 OrderService;于是转头去创建 OrderService,结果 OrderService 也依赖 UserService;而 UserService 目前还没创建完,两边都在等对方完成,形成了一个死等。

注意,这里必须区分两件事:代码逻辑层面的互相调用,和对象构建阶段的依赖环,是两码子事。UserService 的方法里调用 OrderService 的方法,OrderService 的方法里又调用 UserService 的方法,这在业务上完全可以没问题,因为调用栈本身是分层的,只要一个线程按顺序执行就不会死锁。真正会让 Spring 崩掉的是“对象实例化阶段”的互相依赖:A 要被创建出来,就必须先拿到 B;B 要被创建出来,又必须先拿到 A。这时候两个对象都还没法完成构造,整个循环就锁死了。

如果用一个生活化的例子来类比,就像两个人合作种菜:A 说“我要先等 B 把土翻完才能播种”,B 说“我要先等 A 把种子拿来才能翻土”,两个人站在地头互相等待,谁也不肯先动手。Spring 要解决的就是这个互相等待的问题。

1.2 Spring Bean 生命周期:循环依赖到底卡在哪一步

要理解 Spring 为什么能解决一部分循环依赖,又为什么解决不了另一部分,必须先搞清楚一个 Bean 从无到有会经历哪些阶段。我把标准流程简化成几个核心环节:

  1. 通过扫描或配置注册 BeanDefinition。
  2. getBean 入口被触发,单例 Bean 先查缓存。
  3. 实例化(createBeanInstance):通过构造器或工厂方法,在堆上 new 出原始对象。
  4. 属性填充(populateBean):把 @Autowired、@Resource、XML property 等依赖注入进去。
  5. 初始化(initializeBean):执行 Aware 回调、BeanPostProcessor 的前置和后置处理、afterPropertiesSet、init-method 等。
  6. 注册为完整单例,放入单例池。

循环依赖会卡在第四步的原因在于:UserService 在属性填充阶段需要 OrderService,所以 Spring 必须先把 OrderService 创建完成;而 OrderService 创建到属性填充阶段时,又反过来需要 UserService。此时 UserService 的实例化其实已经完成了——对象已经躺在堆内存里了,只是它的属性还没赋值、初始化方法还没调。从内存角度看,这个半成品对象是真实存在的,只是还没有被放进一个“可供其他人查找”的地方。

这一步很关键。正因为半成品对象已经存在,才让“提前暴露”有了施展空间。如果连对象都还没创建出来,比如构造器参数里就需要对方,那无论怎么缓存都救不了,因为根本没有一个可以被提前拿走的引用。这也是后文会提到“构造器循环依赖无法解决”的根本原因。

1.3 真实项目里循环依赖是怎么蔓延出来的

说实话,大部分循环依赖不是开发者刻意写出来的,而是改代码改出来的。我见过几种典型情况。

第一种是多模块依赖没有理清。比如用户模块和订单模块互相调用,产品一开始只有一个 Service,后来拆模块时为了图省事,UserService 里直接注入了 OrderService,OrderService 里为了查询用户信息又反向注入了 UserService。编译期完全没问题,等到启动容器,错误才突然炸出来。

第二种是团队成员各自加需求。本来 A 依赖 B,A 和 C 没有关系;后来同事在 C 里加了 A 的依赖,在 A 里又加了 C 的依赖,两个需求并行开发,代码合并后循环依赖凭空产生。

第三种是历史遗留代码。老项目里大量使用字段注入,Service 之间互相引用的关系像蜘蛛网一样,平时不起眼,等整个系统启动时一次性爆炸。

排查这种问题最痛苦的地方在于:报错信息只告诉你“某 Bean 正在创建中”,你还需要自己去梳理调用链,搞清楚谁先创建、谁引发了谁。所以只有理解了 Spring 内部的三级缓存机制,才能在看到报错时快速定位。

2. 核心机制拆解:为什么 Spring 选择用三个 Map 来兜底

2.1 三个缓存 Map 各自承担什么角色

三级缓存是业内习惯叫法,在 Spring 源码里其实就是 DefaultSingletonBeanRegistry 类中的三个成员变量。我先把它们画成一个对照说明。

缓存层级变量名类型存放内容生命周期阶段
一级缓存singletonObjectsMap<String, Object>完整的、已经初始化完成的 Bean创建完成之后
二级缓存earlySingletonObjectsMap<String, Object>提前暴露的早期引用,可能是原始对象,也可能是代理对象半成品阶段
三级缓存singletonFactoriesMap<String, ObjectFactory<?>>单例工厂,用它来生成早期引用实例化完成之后、属性填充之前

这里有个容易混淆的点:三级缓存放的不是“半个 Bean”,而是一个 ObjectFactory。它在这个时刻并没有真正生成返回给下游的对象,而是在关键时刻才调用工厂的 getObject 方法去生成。

另外还有一个很重要的辅助集合:singletonsCurrentlyInCreation,它记录当前正在创建中的 Bean 名字。每当 Spring 开始创建一个单例 Bean,就会把名字加入这个集合;创建完成后再移除。如果没有这个集合,Spring 根本不会知道“当前这个 Bean 正处于创建过程中”,也就无法启动后续的早期引用搜索逻辑。

整个数据流向是这样的:

  1. Bean 实例化完成后,先把自己封装成一个 ObjectFactory,放进第三级缓存。
  2. 当容器发现有循环依赖,需要提前拿到这个 Bean 的半成品时,会通过第三级缓存的 ObjectFactory 生成一个早期引用,同时放入二级缓存,并移除三级中的工厂。
  3. 所有属性填充和初始化都完成后,Spring 把完整对象放入一级缓存,同时移除二级缓存中的早期引用。

所以本质上,一级缓存是“最终成绩单”,二级缓存是“临时借给别人的中间产物”,三级缓存是“可生成临时产物的工厂”。

2.2 getSingleton 的查询逻辑:一级、二级、三级是如何串起来的

了解数据结构只是第一步,真正关键的是 DefaultSingletonBeanRegistry 的 getSingleton 方法。这里有一个带 allowEarlyReference 参数的版本,它就是解决循环依赖的核心入口。我摘录一段我最常用来解释的简化源码:

protected Object getSingleton(String beanName, boolean allowEarlyReference) { // 优先查一级缓存:是否已经有完整对象 Object singletonObject = this.singletonObjects.get(beanName); // 一级没有,并且当前 Bean 正在创建中 if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { // 查二级缓存:看看是否有人已经提前拿过它 singletonObject = this.earlySingletonObjects.get(beanName); // 二级也没有,并且允许提前引用 if (singletonObject == null && allowEarlyReference) { synchronized (this.singletonObjects) { // 加锁后重新检查,防止并发重复创建 singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null) { // 从三级缓存中取出 ObjectFactory 并执行 getObject() ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { singletonObject = singletonFactory.getObject(); // 生成早期引用后存入二级缓存,同时移除三级缓存 this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } } } return singletonObject; }

读这段源码时有几个点要格外注意。

第一,查除一级缓存前,必须先判断 isSingletonCurrentlyInCreation(beanName)。这个判断非常重要,如果不满足就说明当前 Bean 压根不是“因为创建到一半才找不到”,而是“本身就不存在或已经创建完成”,这时候应该走正常创建流程,而不是提前引用逻辑。

第二,从三级缓存取出 ObjectFactory 并执行 getObject 之后,会把生成的对象放进二级缓存、从三级缓存移除。为什么要这样设计?因为 ObjectFactory 的执行是有成本的,如果没有二级缓存,每次都去调 getObject,就可能导致一个半成品对象被反复创建,甚至产生多个代理对象不一致的问题。所以一旦生成过早期引用,就把它固定在二级缓存里,后续直接复用。

第三,getSingleton(beanName, false) 这种调用方式下,allowEarlyReference 是 false,那就不会走第三级缓存,即使三级里有工厂也不会用。这样做的目的是在某些不希望提前暴露的场景下阻断取用。

2.3 为什么必须用三级缓存,二级到底差在哪里

这是面试中最容易被追问的问题:既然二级缓存已经能放一个半成品对象,为什么还需要第三级?

我的理解是这样的:如果不考虑 AOP 代理,二级缓存其实够用。Spring 在 Bean 实例化完成后直接把对象放进二级缓存,B 填充属性时从二级缓存里把 A 的半成品拿出来用,完全可行。

但现实的 Spring 世界里,大量 Bean 在初始化阶段会被 BeanPostProcessor 处理,最常见的就是 AbstractAutoProxyCreator 这类后置处理器。Spring 的 AOP 代理默认发生在 Bean 初始化完成后的 postProcessAfterInitialization 阶段。举个具体例子:如果一个 Bean 上有 @Transactional 或者 @Aspect 切面,那么它最终放进一级缓存的对象通常不是原生对象,而是一个 JDK 动态代理或 CGLIB 代理对象。

现在假设只保留二级缓存,会有问题吗?我们来推演一下:

实例化 A 之后,容器把原生 A 放进二级缓存。属性填充阶段,B 依赖 A,于是从二级缓存拿到了原生 A。后来 A 全部初始化完成,Spring 在 A 上执行切面逻辑,生成代理 A,最后把代理 A 放进一级缓存。这时问题就出现了:B 里持有的还是原生 A,而容器最终的成品是代理 A。如果切面逻辑靠代理拦截才能生效,B 调用 A 的方法时会绕过所有切面,这是严重 bug。

那如果换一种方案:实例化 A 之后,不等有没有人需要,就立刻对 A 做 AOP 代理,把代理 A 放进二级缓存,行吗?这样又会让所有 Bean 在实例化后都提前生成代理,哪怕根本没有循环依赖、也没有被任何对象提前引用,白白增加开销。而且有些 Bean 是否应该被代理,跟它是否完成初始化状态有关,早期强制代理可能产生副作用。

三级缓存解决得就很优雅:第三级缓存存的是一个 ObjectFactory,这个工厂内部会调用 getEarlyBeanReference 方法。当且仅当真的有另一个 Bean 需要提前拿 A 的时候,工厂才会执行,此时 Spring 才判断“A 是否需要提前 AOP 代理”。如果需要就返回代理对象,如果不需要就返回原生对象。这样既避免了所有 Bean 无条件提前代理,也保证了 B 拿到的对象和容器最终成品是同一个引用。

用一句话概括:第三级缓存的核心作用是“延迟 AOP 代理的决策时机”。Spring 不是在一开始就决定某个 Bean 要不要生成代理,而是等确认它确实要被循环依赖引用时再决定。这正是第三级缓存存在的最大价值。

2.4 三级缓存生效的三个前提条件

三级缓存并不是万能钥匙,它要生效,有几个硬性前提。

第一,Bean 必须是单例。Spring 的单例池机制只对单例 Bean 生效,Prototype 作用域的 Bean 每次都创建新实例,不存在一个“当前正在创建中的对象”可以被反复引用的概念,所以循环依赖一旦涉及 prototype,会直接报错。

第二,循环依赖必须发生在属性填充阶段。也就是实例化确实已经完成,Bean 已经被 addSingletonFactory 放入三级缓存之后。如果依赖发生在构造器阶段,spring 还没有实例化完成,三级缓存里根本没有这个 Bean 的工厂,自然救不了。

第三,不能存在无法被“破坏”的外部条件。比如循环依赖链条里有 @Async 的 Bean,或者这个 Bean 需要依赖某种特殊代理,处理起来都可能出现奇怪的问题,这部分我在后面第 4 节详细讲。

3. 源码级追踪:一个半成品 Bean 是怎么被“借”出去的

3.1 doCreateBean 中三级缓存的注册时机

当容器进入 doCreateBean 方法后,会执行一个非常关键的逻辑。下面是 AbstractAutowireCapableBeanFactory.doCreateBean 中的一段节选:

boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) { addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); }

这里有几个信息很关键。

第一,earlySingletonExposure 本质上是三个条件的与关系:Bean 是单例;容器允许循环依赖;这个 Bean 确实还在创建过程中。Spring 存在一个 allowCircularReferences 参数,默认是 true,但如果被设置为 false,就相当于从源头关闭了循环依赖支持。

第二,addSingletonFactory 会把一个 lambda 表达式放入第三级缓存。这个 lambda 包装了 getEarlyBeanReference(beanName, mbd, bean) 方法。所以放进三级缓存的并不是最终要暴露的那个对象,而是一个延迟生成的逻辑。

第三,这段代码执行的位置,是 Bean 实例化完成之后、属性填充之前。也就是说,只要 Bean 是通过无参构造器或普通构造方式 new 出来的,Spring 能保证在 populateBean 之前把这个工厂注册好。但如果是构造器参数里需要别的 Bean,那 Spring 在 createBeanInstance 阶段就会陷入死循环,根本走不到 addSingletonFactory 这一步。

3.2 完整时间线:UserService 和 OrderService 互相依赖时发生了什么

下面我以一个虚构但典型的过程,把 A 和 B 互相依赖的完整创建过程列出来。假设容器先创建 UserService,我们叫它 A;A 依赖 OrderService,我们叫它 B。

步骤容器动作二级缓存状态三级缓存状态
1开始创建 A,A 实例化完成空存放 A 的 ObjectFactory
2A 进入属性填充,发现需要 B空存放 A 的 ObjectFactory
3容器开始创建 B,B 实例化完成空A、B 的 ObjectFactory
4B 进入属性填充,发现需要 A空A、B 的 ObjectFactory
5B 填充 A 时 getBean(A),发现 A 正在创建中从三级取出 A 的早期引用删除 A 的工厂
6B 拿到 A 的早期引用,继续完成自身属性填充和初始化存 A 的早期引用存 B 的 ObjectFactory
7B 初始化完成,移入一级缓存存 A 的早期引用无 B
8A 继续属性填充,从容器获取完整的 B存 A 的早期引用无 B
9A 属性填充完成,执行初始化,移入一级缓存删除 A 的早期引用无

第 5 步是整个流程最关键的一步。B 在属性填充阶段需要 A,于是调用依赖解析,最终走到了 getBean(A)。此时容器发现 A 虽然还没有初始化完成,但它已经注册了一个 ObjectFactory 在三级缓存里,于是调用 getObject 生成 A 的早期引用,再放入二级缓存,同时移除三级缓存。

B 拿到 A 的早期引用后,它自己的创建过程就能继续往下推进。等 B 成为完整单例,A 再拿走 B 的完整对象,最后完成 A 自己的生命周期。整个过程就像两个人合伙干活,A 先给了 B 一个“半成品订单”,B 拿着这个订单完成自己的任务,然后又给 A 提供 B 的完整成果,A 最后补完自己的剩余工作。

3.3 getEarlyBeanReference 是如何和 AOP 代理联动的

三级缓存里的 ObjectFactory 最终会调用 getEarlyBeanReference 方法。这个方法在 AbstractAutowireCapableBeanFactory 中的逻辑是遍历所有 SmartInstantiationAwareBeanPostProcessor,让它们有机会对早期引用进行处理。

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) { Object exposedObject = bean; if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) { for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) { exposedObject = bp.getEarlyBeanReference(exposedObject, beanName); } } return exposedObject; }

Spring AOP 中的 AbstractAutoProxyCreator 就实现了 SmartInstantiationAwareBeanPostProcessor。它的 getEarlyBeanReference 长这样:

@Override public Object getEarlyBeanReference(Object bean, String beanName) { Object cacheKey = getCacheKey(bean.getClass(), beanName); this.earlyProxyReferences.put(cacheKey, bean); return wrapIfNecessary(bean, beanName, cacheKey); }

这段代码有两个点值得注意。

earlyProxyReferences 这个 Map 记录了“哪些 Bean 已经在早期被代理过了”。wrapIfNecessary 是真正创建 AOP 代理的方法。如果某个 Bean 需要生成代理,这里就会返回一个代理对象;如果不需要,会原样返回原生对象。

那么问题来了:如果一个 Bean 在早期被代理了,它初始化完成之后,会不会再被代理一次?答案是,不会。原因在 AbstractAutoProxyCreator.postProcessAfterInitialization 里有判断:

@Override public Object postProcessAfterInitialization(Object bean, String beanName) { if (bean != null) { Object cacheKey = getCacheKey(bean.getClass(), beanName); if (this.earlyProxyReferences.remove(cacheKey) != bean) { return wrapIfNecessary(bean, beanName, cacheKey); } } return bean; }

如果这个 Bean 的原始对象之前被记录在 earlyProxyReferences 中,说明它已经在 getEarlyBeanReference 阶段被代理过了,那么 remove(cacheKey) 会返回之前记录的原始对象,和当前 bean 是同一个对象,这时候条件不成立,就不会再走一次 wrapIfNecessary。这样能保证一个 Bean 不会因为循环依赖而被代理两次。

假如没有循环依赖,这个 Bean 从一开始就不会被提前代理,postProcessAfterInitialization 中的 earlyProxyReferences 里没有记录,那么在初始化完成后会正常执行代理逻辑。换句话说:同一个 Bean,只有在被循环依赖提前引用时,才会走提前代理路径;否则走的是常规后置处理路径。两者最终只会有一个生效。

3.4 一个容易踩坑的细节:被提前暴露的 Bean 如果不需要代理会怎样

可能有人会问:如果 A 不需要 AOP 代理,那三级缓存返回的是原生对象;B 拿到 A 的原生对象;A 初始化完成后放进一级缓存的也是同一个原生对象,这没问题。

如果 A 需要 AOP 代理,情况稍微复杂:第三级工厂执行 getEarlyBeanReference 时,A 已经被代理了一次,返回给 B 的是代理对象;后续 A 初始化完成后,由于 earlyProxyReferences 已经记录了同样一个对象,postProcessAfterInitialization 不会再生成新代理,最终一级缓存里放进来的还是同一个代理对象。所以 B 拿到的代理对象和容器最终保存的对象是同一个引用,里面的方法增强能正常生效。

这种“同一个引用”的保证是 Spring 设计上的关键约束。只要有一处不一致,就会造成 B 拿到的对象和容器的对象不是同一个,轻则缓存数据错乱,重则 AOP 切面失效、事务不生效,排查起来极其隐蔽。

4. 常见问题排查:什么情况下循环依赖仍然救不了

4.1 快速判断当前报错属于哪一类

循环依赖相关的报错看多了,基本可以按下面这个表格去定位。

现象出现阶段本质原因推荐解法
构造器注入的循环依赖启动即报错对象尚未实例化,无法提前暴露改 setter/字段注入,更推荐 @Lazy 或重构
prototype 作用域循环依赖启动即报错prototype 不走单例池调整作用域,避免 prototype 互相注入
@Async 方法导致的循环依赖启动或运行期报代理错误早期代理和 @Async 代理时机不一致把 @Async 抽到独立 Bean
Spring Boot 2.6+ 默认报循环依赖启动即报错官方默认禁止 allow-circular-references设置 allow-circular-references 为 true,但建议先优化结构

构造器循环依赖是最好认的,因为它很“绝情”:报错信息里会直接显示两个 Bean 名,并且是在实例化阶段就抛出来。我之前遇到过这样一个场景:

@Service public class AService { private final BService bService; public AService(BService bService) { this.bService = bService; } }

B 的构造器里又需要 AService。这种写法在启动时一定会失败。唯一不会启动失败的方法是给其中一个构造器参数加 @Lazy 注解,让 Spring 先注入一个延迟解析代理,真正调方法时才把目标 Bean 创建出来。例如:

public AService(@Lazy BService bService) { this.bService = bService; }

技术上是能解,但业务上要慎重。因为 @Lazy 的本质是“掩盖问题”,如果一个对象在构造阶段就需要调用另一个对象的方法,你用代理拖延到运行期,可能把启动期的问题延迟到请求中才暴露,定位成本会变高。

4.2 最隐蔽的 @Async 循环依赖陷阱

很多人知道 setter 循环依赖默认能跑,于是认为只要不是构造器循环就没问题。但在实际项目中,如果循环依赖里有 @Async 这样的“方法级代理增强”,就可能出现启动不报错、运行期才出问题的现象。

原因得从代理时机说起。@Async 的代理是由 AsyncAnnotationBeanPostProcessor 在 bean 初始化完成后通过 BeanPostProcessor 机制生成的,它不像 AbstractAutoProxyCreator 那样会参与 SmartInstantiationAwareBeanPostProcessor 的 getEarlyBeanReference。如果 A 被 B 提前引用,早期引用返回的时候,A 根本还没机会生成 @Async 代理;等 A 完整初始化后才生成异步代理,放进一级缓存。这时候 B 持有的早期引用和容器最终对象不一致,B 调 A 的 @Async 方法时其实走的是非代理的原始对象,异步逻辑不会生效。

这类问题非常难排查,因为它往往表现为“方法没有在异步线程执行”,而不是报异常。我在处理线上问题时,通常第一步就去看类上是否有 @Async、@Transactional 这类注解,以及它有没有被别的 Bean 循环依赖。如果存在这种交叉,最稳妥的办法是调整结构,把 @Async 抽到一个独立的 Service 中,让它不参与循环链条。

4.3 Spring Boot 2.6 以后为什么默认禁止循环依赖

从 Spring Boot 2.6 开始,官方默认把 allow-circular-references 设成了 false。这意味着,以前可以正常启动的 setter 循环依赖,在升级后可能直接启动失败,并提示:

The dependencies of some of the beans in the application context form a cycle

官方这么做的本意,其实是告诉大家:循环依赖本身是设计上的坏味道,不应该成为正常操作的一部分。三级缓存只是历史兼容机制,给那些历史包袱比较重的遗留系统找一条活路,而不是鼓励大家写循环依赖。

如果项目里确实有非常庞大的历史代码,短时间内没法一次性改完,可以显式开启配置来兼容:

spring: main: allow-circular-references: true

但我一般建议,这种开关只能在升级过渡期临时用,同时应该把“修复循环依赖”列入技术债,逐步清掉。否则每次升级 Spring Boot 大版本都可能暴雷。

4.4 我在实际项目中的排查步骤与经验

如果代码里已经出现循环依赖,我的排查套路通常是三步。

第一步,先把报错信息里的 Bean 名列出来。多看几次你会发现,Spring 报错时通常会指出当前正在创建的 Bean 是谁,比如 “Bean named 'aService' is expected to be created but is already in creation”。把这个 Bean 名记下来。

第二步,利用 IDE 的调用层级搜索,反查谁注入了这个 Bean,这个 Bean 又注入了谁。手动画一张简单的依赖图。循环链条一般不会太长,很快就能找出来。

第三步,判断这个循环依赖是不是可以安全存在的。如果两个类之间确实没有必要出现双向依赖,就抽取第三方 Service、或者用应用事件解耦。如果只是构造器造成的“硬环”,优先用 @Lazy 降低修改成本。

还有一种比较实用的排查技巧:如果项目里实现了 ApplicationContextAware 或 ApplicationRunner,可以临时在启动后打印 DefaultSingletonBeanRegistry 里的 singletonsCurrentlyInCreation 集合,看看哪些 Bean 创建过程互相等待。这个集合是受保护的成员变量,可以通过反射拿到。Debug 时观察它,能直观看到循环依赖发生时 Spring 到底把哪些 Bean 卡在创建中。

5. 深入思考:三级缓存设计带来的启发和我的个人体会

源码看得越细,越会觉得三级缓存这个设计其实挺有味道的。它最核心的思想不是“多一个缓存”,而是“延迟决策”。Spring 把一个对象的创建过程拆成实例化、属性填充、初始化三个阶段,在有循环依赖的情况下,允许下游对象提前拿到一个“可能还不是最终形态”的引用,但不急于把这个引用完全固化,而是通过 ObjectFactory 延迟决定要暴露原生对象还是代理对象。

这种模式在实际工作中也有很大参考价值。我在做一个对接第三方 SDK 的功能时,遇到过类似问题:多个外部系统之间互相依赖对方的客户端对象,但初始化第三方客户端非常昂贵,不能每次都重新创建,更不能在服务启动阶段就直接把所有客户端全部建好。后来我用了一个类似 ObjectFactory 的思路:先注册一个工厂,只在某个模块真正需要访问对端时才实例化客户端,并且用二级缓存保存已经完成的实例,避免重复创建。这个思路本质上跟 Spring 的三级缓存如出一

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询