Spring IoC 核心:循环依赖、注解体系与 Bean 生命周期

spring 📚 learning spring-ioc-core · spring · ioc · circular-dependency · annotation · bean-lifecycle · three-level-cache

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,直接返回 JSONRESTful API
@Repository 的核心差异:
// @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
3 类核心回调:
回调类型调用时机典型用途
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 张卡,点击翻面