撕开 Spring 的底裤:解析 Bean 生命周期与三级缓存的“破局”之术
2026/6/6 20:45:58 网站建设 项目流程

在 Java 后端开发中,Spring 的 IoC 容器就像是一个庞大且精密的全自动化黑灯工厂。我们只需要在图纸上标明@Component@Service,剩下的全交给 Spring。

但作为一名合格的工程师,绝不能仅仅满足于当一个“调包侠”。今天,我们就深入这座工厂的内部,顺着流水线,彻底扒开Spring Bean 的生命周期,并直击那个让无数初学者头疼的终极难题:循环依赖与三级缓存

一、 流水线纪实:Bean 的四大核心生命周期

如果把创建一个完整的 Bean 比作制造一辆高配跑车,那么 Spring 容器的流水线严格遵循以下四个宏观步骤:

1. 实例化 (Instantiation) —— 打造车架骨骼

这是流水线的第一步。Spring 扫描到你的类后,会利用 Java 的反射机制调用类的构造方法,在堆内存中开辟一块空间,生生“捏”出一个原始对象。

  • 状态:此时的对象是一个极度纯粹的“半成品”。它虽然存在于内存中,但里面的属性(比如@Autowired标注的其他 Service)全都是null

2. 属性注入 (Populate Properties) —— 安装发动机与零件

车架子搭好了,接下来就是组装。

Spring 会检查这个半成品对象内部有哪些依赖项,然后去 IoC 容器里把其他对应的 Bean 找出来,通过 Setter 方法或直接反射字段,强行将它们“塞”进当前对象的属性中。

3. 初始化 (Initialization) —— 质检与涡轮增压 (AOP)

零件装完,车子基本成型,但还不算完工。这一步是 Spring 留给开发者的扩展接口:

  • 各种回调执行:比如带有@PostConstruct注解的初始化方法会在这里被触发,执行一些数据预热逻辑。

  • 核心魔法 (AOP):这是极其关键的一环!如果这个 Bean 配置了切面(比如@Transactional事务),Spring 会在这一步通过BeanPostProcessor后置处理器,利用 JDK 动态代理或 CGLIB,为其生成一个代理对象(Proxy)。最终交到我们手里的,其实是这个被强化过的代理车。

4. 销毁 (Destruction) —— 报废回收

当 Spring 容器关闭时,流水线停工。Spring 会调用带有@PreDestroy注解的方法,或者实现了DisposableBean接口的方法,优雅地释放数据库连接、关闭线程池等资源。

二、 架构师的梦魇:循环依赖的“死锁”悖论

按照上述流水线,正常的 Bean 创建如丝般顺滑。但现实业务中,我们经常会遇到一种极其尴尬的场景:循环依赖

假设我们有ServiceAServiceB

  1. ServiceA内部@AutowiredServiceB

  2. ServiceB内部又@AutowiredServiceA

灾难发生了:

  • 流水线开始制造 A:实例化 A(半成品) -> 准备给 A 注入属性 B。

  • 发现 B 还没造出来,停下 A 的流水线,去造 B。

  • 流水线开始制造 B:实例化 B(半成品) -> 准备给 B 注入属性 A。

  • 发现 A 也没造完(还在等 B),停下 B 的流水线,去等 A。

这就像是“先有鸡还是先有蛋”的死循环,如果没有任何干预,程序将直接抛出BeanCurrentlyInCreationException异常。

三、 破局之术:三级缓存与半成品暴露

为了打破这种“死锁”,Spring 引入了神级设计——三级缓存(Three-Level Cache)。本质上,这就是三个大 Map。

  • 一级缓存 (singletonObjects):存放完全初始化好的成品 Bean。也就是我们平时getBean()拿到的最终对象。

  • 二级缓存 (earlySingletonObjects):存放提早暴露的半成品 Bean(已经实例化,但还没注入属性)。

  • 三级缓存 (singletonFactories):存放Bean 的工厂对象(ObjectFactory),这是一个生成引用的 Lambda 表达式。

见证奇迹的时刻:缓存是如何解开循环依赖的?

让我们重新推演 A 和 B 的制造过程,看看 Spring 是如何利用缓存巧妙破局的:

  1. A 的实例化:Springnew出了 A 的半成品。【核心转折】Spring 没有急着去注入属性,而是先心急地把 A 包装成一个ObjectFactory(工厂函数),塞进三级缓存。这相当于提前向世界宣布:“A 已经有个胚子了,大家谁急用可以先拿去凑合!”

  2. A 注入 B:A 开始属性注入,发现需要 B,去一级缓存找,没有。触发 B 的创建。

  3. B 的创建:同样,B 实例化成半成品,把自己的工厂函数塞进三级缓存。然后 B 准备注入属性 A。

  4. B 寻找 A(破局点):

    • B 去一级缓存找 A(没找到,A 是半成品)。

    • B 去二级缓存找 A(没找到)。

    • B 跑到三级缓存找到了 A 留下的ObjectFactory

    • B 调用这个工厂方法,拿到了 A 的早期引用,并顺手把 A 升级放入二级缓存(同时清空三级缓存中的 A)。

  5. B 成功出厂:B 顺利把“半成品 A”注入到了自己的属性中。B 继续执行初始化,彻底完工,被放入一级缓存

  6. A 恢复生产:B 造好了,卡在流水线上的 A 终于拿到了完整的 B,将其注入自己的属性。A 也继续执行初始化,彻底完工,从二级缓存升级进入一级缓存

至此,死锁解开!

灵魂拷问:为什么要用“三级”缓存?二级不够吗?

如果你仔细思考,会发现只用两级缓存也能存放“半成品”,为什么 Spring 非要搞一个存ObjectFactory的三级缓存呢?

答案是为了兼容我们在第一部分提到的AOP 动态代理

按照正常的生命周期,AOP 代理对象是在第三步初始化时才生成的。但循环依赖发生时,B 需要提前拿到 A 的引用。如果 A 配置了事务处理(需要被代理),B 拿到的绝对不能是 A 的原始对象,而必须是 A 的代理对象

三级缓存里存的那个ObjectFactory,就是用来处理这个特殊情况的。它内部包含了一个逻辑:如果 A 需要被 AOP 增强,这个工厂方法就会被触发,强行提前(在属性注入之前)生成 A 的代理对象并返回给 B;如果不需要增强,就直接返回原始对象。

结语

从一行简单的@Autowired,到背后精密的四段式生命周期,再到解决循环依赖的三级缓存。Spring 用极其优雅的代码结构,向我们展示了什么是顶级的系统解耦与状态机管理。

记住,缓存的本质是空间换时间,而三级缓存的本质,是利用“提前暴露”打破因果死锁,并巧妙兼顾了 AOP 的侵入逻辑。搞懂了这套机制,Spring 的底层源码对你而言,将不再有秘密。

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

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

立即咨询