--- title: "04-Bean生命周期与三级缓存" created: 2026-09-03 tags: - Java --- # Bean生命周期与三级缓存 > [!note] 本篇定位 > "Bean 从生到死经历了什么"是 Spring 面试分水岭——只答"构造→注入→初始化→销毁"的人远多于能讲清**三级缓存与循环依赖**的人。本篇前置:[[01-Spring IOC(控制反转)|IOC]] 与 [[02-依赖注入(DI)|DI]]。 ## 一、Bean 生命周期全景 ```text 1. 实例化(Instantiation) └─ 构造器创建对象(反射 newInstance),属性都还是默认值 2. 属性填充(Populate) └─ 依赖注入:@Autowired / @Value / setter 注入,把依赖 Bean 塞进去 3. 初始化前(BeanPostProcessor.postProcessBeforeInitialization) └─ @PostConstruct 注解的方法在此执行 4. 初始化(InitializingBean.afterPropertiesSet / init-method) └─ 自定义初始化逻辑(连接池启动、资源预加载) 5. 初始化后(BeanPostProcessor.postProcessAfterInitialization) └─ **AOP 代理对象在这里生成**——这就是为什么从容器拿到的 Bean 可能是代理 6. 就绪(Ready) └─ Bean 可用,存在单例池(singletonObjects)中 7. 销毁(DisposableBean.destroy / @PreDestroy) └─ 容器关闭时调用,释放资源(连接池关闭、线程池 shutdown) ``` **BeanPostProcessor** 是 Spring 最关键的扩展点——它包裹了"初始化前后"两个钩子,所有框架级功能(`@Autowired`、`@Transactional`、`@Async`)都是通过自定义 BeanPostProcessor 实现的。 ## 二、三级缓存与循环依赖 **问题**:A 依赖 B,B 依赖 A——两个 Bean 都等着对方先创建好,传统思路会死锁。 **Spring 的答案——三级缓存**: ```text singletonObjects 一级:完全就绪的 Bean(经过实例化→填充→初始化→代理) earlySingletonObjects 二级:早期引用(已经实例化,但还没填充属性) singletonFactories 三级:ObjectFactory,能提前暴露"半成品"的引用 ``` **A 与 B 互相依赖的解决过程**: 1. 创建 A → 实例化 A(反射调构造器)→ 把 A 的 ObjectFactory 放进三级缓存("谁要我给谁半成品") 2. 填充 A 的属性 → 发现需要 B → 去创建 B 3. 创建 B → 实例化 B → 把 B 的 ObjectFactory 放进三级缓存 4. 填充 B 的属性 → 发现需要 A → **从三级缓存拿到 A 的半成品引用**(此时 A 还没填充属性,但引用已经存在了) 5. B 完成初始化 → 放进一级缓存 6. 回到 A → 把 B 注入 → A 完成初始化 → 放进一级缓存 **三个关键事实**: 1. **三级缓存只解决单例 setter 注入的循环依赖**——构造器注入的循环依赖无法解决(因为实例化这一步就卡住了,根本没机会暴露半成品); 2. **AOP 代理**:三级缓存里 ObjectFactory 调用 `getObject()` 时,如果该 Bean 需要 AOP 代理,会返回代理对象而非原始对象——所以注入的依赖始终是"最终形态"; 3. 二级缓存的作用:防止每次获取都重新调用 ObjectFactory 生成代理(缓存最终的早期引用)。 ## 三、Bean 作用域 | 作用域 | 含义 | 常见场景 | | --- | --- | --- | | singleton | 整个容器一个实例(默认) | 无状态 Bean(Service/DAO) | | prototype | 每次获取新实例 | 有状态 Bean(每次请求不同数据) | | request | 每次 HTTP 请求一个实例 | Web 环境下请求级数据 | | session | 每个 HTTP Session 一个实例 | 用户会话数据 | **prototype 的坑**:Spring 不管理 prototype Bean 的完整生命周期——只负责创建,销毁由调用方自己管。因此 singleton Bean 里注入 prototype Bean 时,**singleton 只会被注入一次**(创建 singleton 时的那次),之后每次调用拿到的都是同一个——这不是"每次新实例",除非用 `@Lookup` 或 `ObjectProvider`。 ## 四、高频面试题速答 1. **Bean 生命周期有哪些阶段?** 见第一节全景图,关键点:BeanPostProcessor 的两个钩子、AOP 代理在初始化后生成。 2. **Spring 如何解决循环依赖?** 三级缓存:实例化后暴露半成品引用(ObjectFactory),让依赖方先拿到引用再等填充完成。只支持单例 setter 注入的循环依赖。 3. **构造器注入的循环依赖能解决吗?** 不能——实例化这一步就卡住了,Bean 还没出生,三级缓存里没有它的半成品。 4. **prototype 作用域的循环依赖?** 同样不能——prototype 不进入三级缓存,Spring 会直接抛 BeanCurrentlyInCreationException。 5. **二级缓存为什么存在?** 缓存 ObjectFactory 生成的早期引用(包括 AOP 代理),避免重复调用 getObject()。 ## 勾连 - IoC 容器与 BeanFactory 的关系:[[01-Spring IOC(控制反转)|Spring IOC]] - 依赖注入的三种方式与循环依赖的限制:[[02-依赖注入(DI)|依赖注入 DI]] - AOP 代理的生成时机:[[03-Spring AOP(面向切面编程)|Spring AOP]] --- ⬅️ [[03-Spring AOP(面向切面编程)|Spring AOP]] 🏠 [[00-Java|00-Java]] ➡️ [[05-Spring事务管理|05-Spring事务管理]]