TL;DR(30 秒扫完)
- 循环依赖:A→B→A 互相引用,Spring 通过三级缓存(L1 成品 / L2 半成品 / L3 ObjectFactory)解决,但构造器注入无法解决
- 注解体系:5 个组件注解(@Component / @Service / @Repository / @Controller / @RestController)元注解都是 @Component,核心差异在语义分层和@Repository 的异常转换
- Bean 生命周期:5 阶段(BeanDefinition 加载 → 实例化 → 属性填充 → 初始化 → 销毁)+ 3 类核心回调(BeanPostProcessor / Aware / InitializingBean)
关键结论
结论 A(循环依赖)Spring 默认通过三级缓存解决单例 Bean 的循环依赖,但构造器注入无法解决(因为构造器执行时 Bean 尚未创建完成)
结论 B(注解体系)@Repository 的核心差异是自动启用异常转换(PersistenceExceptionTranslationPostProcessor,SQLException→DataAccessException),@RestController = @Controller + @ResponseBody
结论 C(Bean 生命周期)核心骨架 = 实例化→放 L3→属性填充→Aware→beforeInit→@PostConstruct→afterPropertiesSet→initMethod→afterInit(AOP 代理)→放 L1→使用→@PreDestroy→destroy→destroyMethod
结论 D(工程实践)循环依赖本质是设计问题,应通过 @Lazy / 拆 Bean / 事件解耦规避,而非依赖三级缓存
完整讲解(费曼四步)
STEP 1 · 概念(一句话定义)
- 循环依赖:两个或多个 Bean 在初始化过程中互相需要对方,导致 Spring 无法按顺序创建它们
- 注解体系:Spring 通过 5 个组件注解标注 Bean 的语义分层,底层元注解都是 @Component
- Bean 生命周期:Spring 容器从 BeanDefinition 加载到 Bean 销毁的完整流程,包含实例化、属性填充、初始化、使用、销毁五个阶段
STEP 2 · 大白话(生活类比)
类比 1(循环依赖):就像你和朋友互相借钱——你要借钱给他,他也想借钱给你,但谁都没有本金。Spring 的三级缓存就像"先给你一张欠条(L3),等对方还钱后再换成现金(L1)"。
类比 2(注解体系):就像公司里的岗位标签——程序员(@Component)、架构师(@Service)、DBA(@Repository)、前台(@Controller),本质都是员工(@Component),但不同标签有不同的职责和权限。
类比 3(Bean 生命周期):就像员工的入职流程——签合同(实例化)→ 分配工位(属性填充)→ 培训(初始化)→ 工作(使用)→ 离职(销毁),每个环节都有 HR(Spring 容器)介入。
STEP 3 · 底层(细节 + 代码/表格)
3.1 循环依赖的三级缓存机制
| 缓存层级 | 存储内容 | 生命周期 |
|---|---|---|
| L1(singletonObjects) | 完全初始化好的成品 Bean | 初始化完成后放入,使用阶段读取 |
| L2(earlySingletonObjects) | 半成品 Bean(已完成实例化,可被代理) | 实例化后放入,初始化完成后移除 |
| L3(singletonFactories) | ObjectFactory(Bean 的工厂对象) | 实例化后放入,初始化完成后移除 |
A 实例化 → 放 L3 → A 属性填充 → 发现需要 B
→ B 实例化 → 放 L3 → B 属性填充 → 发现需要 A
→ 从 L3 拿 A 的 ObjectFactory → getEarlyBeanReference() → 放 L2
→ B 初始化完成 → 放 L1
→ A 从 L2 拿 B → A 初始化完成 → 放 L1
关键限制:- 构造器注入无法解决:构造器执行时 Bean 尚未创建完成,无法放入 L3
- Spring Boot 2.6+ 默认禁止:
spring.main.allow-circular-references=false
@Lazy:延迟初始化,避免启动时触发循环依赖- 拆 Bean:将循环依赖的部分摘出去,放到独立的位置
- 事件解耦:用 Spring Event 替代直接的依赖注入
3.2 五大注解的完整差异
| 注解 | 元注解 | 核心差异 | 典型场景 |
|---|---|---|---|
| @Component | 无 | 通用 Bean 标注 | 通用组件 |
| @Service | @Component | 纯语义标注 | 业务逻辑层 |
| @Repository | @Component | 自动启用异常转换(PersistenceExceptionTranslationPostProcessor) | DAO 层,SQLException→DataAccessException |
| @Controller | @Component | 处理 Web 请求,返回 View 名称 | 传统 Web 应用 |
| @RestController | @Controller | @Controller + @ResponseBody,直接返回 JSON | RESTful API |
// @Repository 自动启用异常转换
@Transactional
@Repository
public class UserDao {
public User findById(Long id) {
// SQLException 会自动转换为 DataAccessException
return jdbcTemplate.queryForObject(...);
}
}
3.3 Bean 生命周期的完整骨架
5 阶段:- BeanDefinition 加载:从 XML / JavaConfig / 注解扫描加载元数据
- 实例化:调用无参构造器创建 Bean 实例,放入 L3
- 属性填充:依赖注入(@Autowired / @Value),可能从 L2/L3 取代理
- 初始化:
- Aware 回调(BeanNameAware / BeanFactoryAware / ApplicationContextAware)
- BeanPostProcessor.postProcessBeforeInitialization()
- 初始化方法(@PostConstruct → afterPropertiesSet → init-method)
- BeanPostProcessor.postProcessAfterInitialization()(AOP 代理生成)
- 放入 L1
- 使用:从 L1 读取成品 Bean
- 销毁:@PreDestroy → destroy → destroy-method
| 回调类型 | 调用时机 | 典型用途 |
|---|---|---|
| BeanPostProcessor | 初始化前后 | AOP 代理、异步支持 |
| Aware | 初始化前 | 获取容器引用(ApplicationContext 等) |
| InitializingBean / @PostConstruct | 初始化阶段 | 自定义初始化逻辑 |
STEP 4 · 简化(比喻/口诀)
循环依赖口诀:L3 放工厂,L2 放代理,L1 放成品;构造器注入解不了,@Lazy 拆 Bean 事件解耦。
注解体系口诀:@Component 是爹,@Service 是业务,@Repository 管异常,@Controller 管 Web,@RestController 出 JSON。
Bean 生命周期口诀:加载→实例化→填属性→初始化→使用→销毁;三兄弟顺序:@PostConstruct → afterPropertiesSet → initMethod。
速查表
循环依赖 · 三级缓存(L1 成品/L2 代理/L3 工厂)· 构造器注入解不了 · @Lazy 拆 Bean 事件解耦
注解体系 · @Component 是爹 · @Service 业务 · @Repository 异常转换 · @Controller Web · @RestController JSON
Bean 生命周期 · 5 阶段(加载→实例化→填属性→初始化→销毁)· 3 类回调(BPP/Aware/InitializingBean)· 三兄弟顺序(@PostConstruct→afterPropertiesSet→initMethod)
常见错点:构造器注入能解循环依赖 ❌ / @Service 和 @Component 没区别 ❌ / 生命周期只有实例化→使用→销毁 ❌
Anki 候选卡片
Q: Spring 三级缓存的三层分别存储什么?
A: L1 成品 Bean / L2 半成品 Bean(可代理)/ L3 ObjectFactory(工厂对象)
Q: 为什么构造器注入无法解决循环依赖?
A: 构造器执行时 Bean 尚未创建完成,无法放入 L3
Q: @Repository 的核心差异是什么?
A: 自动启用异常转换(PersistenceExceptionTranslationPostProcessor,SQLException→DataAccessException)
Q: Spring Bean 生命周期的 5 阶段是什么?
A: BeanDefinition 加载 → 实例化 → 属性填充 → 初始化 → 销毁
Q: Bean 生命周期的 3 类核心回调是什么?
A: BeanPostProcessor / Aware / InitializingBean
Q: 初始化三兄弟的顺序是什么?
A: @PostConstruct → afterPropertiesSet → init-method
Q: Spring Boot 2.6+ 为什么默认禁止循环依赖?
A: 循环依赖本质是设计问题,应通过 @Lazy / 拆 Bean / 事件解耦规避
Q: Spring 解决循环依赖的 3 种规避方案是什么?
A: @Lazy / 拆 Bean / 事件解耦
关联题目
- [ ] 《什么是 Spring 的循环依赖问题?》—— 2026-09-28 Round 1, 评分 ⭐⭐⭐⭐
- [ ] 《Spring 中 @Service、@Component、@Repository 等注解区别是什么?》—— 2026-09-28 Round 1, 评分 ⭐⭐⭐
- [ ] 《Spring Bean 的生命周期是怎么样的?》—— 2026-09-28 Round 1, 评分 ⭐⭐⭐
关联知识
三级缓存解决循环依赖、五大注解语义分层、Bean 生命周期 5 阶段+3 类回调
✦ 记 忆 口 诀 ✦
L3 放工厂 L2 放代理 L1 放成品 / 三兄弟顺序 / 5 阶段 3 类回调
关键可视化
暂无可视化
知识关系
⬆️ 前置(Prerequisite)
暂无🔄 延伸(Extends)
暂无⚡ 对比(Contrast)
暂无
🎯 概念
📏 规则
⚠️ 误区
🔍 追问
✨ 口诀
共 0 张卡,点击翻面