Spring IOC、Bean注入、循环依赖、AOP、动态代理完整原理
本文全覆盖 Spring 底层五大核心考点:IOC容器原理、Bean生命周期、三种注入方式、三级缓存解决循环依赖、AOP底层原理、JDK/CGLIB动态代理区别,无废话、全面试高频、逻辑闭环、可直接背诵手撕面试。
一、Spring IOC 核心原理
1. IOC 概念(控制反转)
传统开发:开发者主动 new 对象、管理依赖、控制生命周期(程序员主动)
IOC控制反转:将对象创建、依赖管理、生命周期控制权反转交给Spring容器(容器主动)
DI依赖注入:IOC的实现手段,容器自动给Bean赋值、注入依赖
2. IOC 核心作用
- 解耦:彻底消除硬编码 new 对象
- 统一管理Bean生命周期
- 自动依赖注入、自动解决依赖关系
- 为AOP、事务、Spring全家桶提供底层支撑
3. IOC 容器核心执行流程(必背)
Spring 启动加载 Bean 完整流程:
- 资源加载、扫描配置:加载xml/注解配置、扫描包路径,得到Bean定义信息
- 解析Bean定义(BeanDefinition):不创建对象,仅解析类信息、属性、作用域、依赖,存入BeanDefinitionMap
- Bean实例化:单例Bean在容器启动阶段统一通过反射创建对象
- 依赖注入DI:自动填充属性、注入依赖Bean
- 初始化前置后置:Bean后置处理器、InitializingBean、init-method
- 完成初始化:放入单例池,对外提供服务
- 容器关闭:执行销毁方法
4. Bean 完整生命周期(满分背诵)
扫描解析 → 实例化 → 依赖注入 → 初始化 → 就绪使用 → 销毁
Bean生命周期完整链路
1. 解析配置生成 BeanDefinition
2. 反射实例化对象(空对象)
3. 依赖注入填充属性
4. 执行BeanPostProcessor前置处理
5. 执行InitializingBean / init-method
6. 执行BeanPostProcessor后置处理(AOP代理在此步生成)
7. Bean就绪对外提供服务
8. 容器关闭执行销毁方法
二、Spring 三种依赖注入方式 & 区别
1. 字段注入(@Autowired)
- 直接注解在字段上,简洁方便
- 底层原理:反射暴力赋值
弊端:
- 违反单一职责
- 容易循环依赖
- 不利于单元测试
2. Setter 方法注入
- 通过set方法注入
- 适合可变依赖、非必须依赖
- Spring早期主流方式,现在极少用
3. 构造器注入(Spring官方推荐)
- 通过构造方法注入所有依赖
优点:
- 保证Bean初始化完全、线程安全
- 避免循环依赖(提前暴露依赖)
- 适合必须依赖、不可缺依赖
- 方便单元测试
4. 三种注入终极对比
- 必须依赖、核心业务 → 构造器注入(首选)
- 可选依赖、动态修改 → Setter注入
- 简单业务、快速开发 → 字段注入(不规范但常用)
5、三种注入底层坑点补充
- 字段注入 @Autowired:Spring底层通过 ReflectionUtils 暴力反射赋值,跳过构造器,极易产生循环依赖、隐藏依赖、单元测试无法new独立对象
- Setter注入:适合热更新配置、动态修改依赖,不会触发循环依赖卡死,但依赖不保证非空
- 构造器注入:Spring4.3+ 单Bean可省略@Autowired,自动构造注入,强制依赖非空、线程安全、杜绝循环依赖、利于单元测试,阿里/Spring官方双推荐
三、Spring 循环依赖 完整原理 + 三级缓存解决方案
1. 什么是循环依赖
A依赖B,B依赖A,互相嵌套依赖,导致创建死锁
2. 哪些场景能解决、哪些不能解决(必考)
- 可以解决:单例Bean、字段/setter注入
无法解决:
- 多例Bean(不缓存)
- 构造器注入循环依赖(创建即依赖,无解)
- 原型作用域Bean
3. 三级缓存核心结构(核心)
Spring三级缓存结构
一级缓存 singletonObjects:完整初始化完成的单例Bean
二级缓存 earlySingletonObjects:已实例化、未填充属性的早期Bean
三级缓存 singletonFactories:Bean工厂对象(提前暴露引用/代理入口)
4. 三级缓存完整执行流程(满分背诵)
以 A、B 互相依赖为例:
- A开始实例化,创建空对象,放入三级缓存
- A填充属性,发现需要B,去创建B
- B实例化完成,填充属性需要A
- B先查一级缓存无、二级无、三级拿到A的早期引用
- B完成初始化,进入一级缓存
- A继续完成属性填充、初始化
- A从三级移除 → 二级过渡 → 进入一级缓存
5. 三级缓存各自作用(面试追问)
- 一级缓存:存放最终成熟Bean,对外提供服务
- 二级缓存:解决普通循环依赖,存储半成品Bean,避免重复创建
- 三级缓存:核心!解决AOP代理循环依赖,提前暴露Bean引用与代理工厂
6. 为什么需要三级缓存,二级不行?
如果没有三级缓存:普通依赖可以解决,AOP代理循环依赖必炸
三级缓存存放的是 ObjectFactory,可在早期直接返回代理对象,保证依赖双方拿到的是同一个代理Bean,杜绝循环依赖冲突。
7、循环依赖高频终极答疑(面试绝杀)
- 为什么构造器注入无法解决循环依赖?
构造器执行在实例化阶段,对象还没创建完成,根本没有机会放入三级缓存,依赖互相卡死,无解。 - 为什么多例Bean无法解决循环依赖?
多例Bean不进入三级缓存、不做任何缓存,每次都是新建对象,无法提前暴露引用,循环依赖直接报错。 - 二级缓存能干什么?
二级缓存可以解决普通无代理的循环依赖,但是解决不了AOP代理Bean的循环依赖。 - 三级缓存核心价值:延迟生成代理,保证早期暴露的引用和最终容器Bean是同一个对象/同一个代理。
四、Spring AOP 完整原理
1. AOP 概念
面向切面编程,在不修改原有业务代码的前提下,对方法进行增强、拦截、扩展。
核心思想:横向扩展、解耦通用日志、事务、权限、监控
2. AOP 核心术语
- 切点 Pointcut:拦截哪些方法
- 通知 Advice:拦截后做什么(前置、后置、异常、环绕)
- 切面 Aspect:切点+通知结合体
- 连接点 JoinPoint:可被拦截的所有方法
- 织入 Weaving:将切面逻辑植入目标方法的过程
3. AOP 执行时机(Bean生命周期)
Bean初始化后置处理阶段生成代理对象
目标Bean创建完成后,Spring通过后置处理器AbstractAutoProxyCreator 判断是否需要代理,需要则生成代理Bean放入容器,容器中最终存在的是代理对象而非原对象。
4. AOP 完整执行链路
AOP调用流程
1. 容器获取代理Bean
2. 进入代理拦截器
3. 执行前置通知 @Before
4. 执行目标方法
5. 执行后置通知 @After
6. 执行返回通知 @AfterReturning
7. 出现异常执行 @AfterThrowing
8. 环绕通知包裹全程5、AOP 五大通知执行顺序(必考)
正常执行顺序:环绕前置 → @Before → 目标方法 → @After → @AfterReturning → 环绕后置
异常执行顺序:环绕前置 → @Before → 目标方法报错 → @After → @AfterThrowing(无AfterReturning)
6、AOP 代理失效经典场景(高频坑点)
- 同类内部调用:A类a方法调用本类b切面方法,AOP失效(绕过代理对象,直接this调用原对象)
- final/static/private方法:无法被CGLIB/JDK代理,切面失效
- 构造方法切面:Bean还未创建完成,无法织入切面
解决方案:手动从容器获取自身代理对象、使用AopContext.currentProxy()
五、JDK动态代理 vs CGLIB 动态代理(面试必考区别)
1. 两种代理底层原理
JDK 动态代理
- 基于接口代理
- 底层通过反射生成接口实现类
- 必须实现接口,无接口无法代理
- 只能拦截接口方法
CGLIB 动态代理
- 基于子类继承代理
- ASM字节码技术,动态生成目标类子类
- 无需接口,普通类也能代理
- 无法代理final类、final方法、private方法
2. Spring AOP 代理选择规则(必背)
- 目标有接口 → 默认JDK动态代理
- 目标无接口 → 自动CGLIB代理
- SpringBoot2.0+ 默认强制CGLIB
4、动态代理性能 & 实战细节补充
- JDK动态代理:启动创建代理类慢,运行调用快;接口方法有限,更稳定
- CGLIB动态代理:基于ASM字节码生成,创建速度快;支持普通类;因为是继承代理,final类/方法完全无法拦截
- Spring选择策略本质:优先兼容无接口类,所以Boot默认统一CGLIB,减少切换成本
3. 终极区别总结
| 对比维度 | JDK动态代理 | CGLIB动态代理 |
|---|---|---|
| 依赖条件 | 必须有接口 | 无需接口,继承父类 |
| 底层技术 | 反射机制 | ASM字节码生成 |
| 代理范围 | 仅接口方法 | 所有非final、非private方法 |
| 性能 | 调用快、创建慢 | 创建快、调用略快 |
六、Spring核心终极面试总结(直接背诵)
- IOC原理:控制反转将对象创建与依赖管理交给容器,DI注入实现解耦,Bean经历解析、实例化、注入、初始化、服务、销毁完整生命周期。
- 注入方式:构造器注入官方推荐保证安全,setter适配可选依赖,字段注入简洁但易循环依赖。
- 循环依赖:单例字段/setter依赖可通过三级缓存解决,三级缓存专门解决AOP代理循环依赖,构造器循环依赖无解。
- AOP原理:基于动态代理,在Bean初始化后置阶段生成代理对象,实现方法无侵入增强,用于事务、日志、权限。
- 代理区别:JDK代理基于接口、反射实现;CGLIB基于子类继承、ASM字节码实现,SpringBoot统一默认CGLIB,适配所有普通类与接口类。
- 高频坑点:同类内部调用AOP失效、构造器注入无解循环依赖、三级缓存专为解决代理循环依赖、final方法无法被代理。
- 执行顺序:AOP正常执行与异常执行顺序固定,环绕通知优先级最高,异常后不执行返回通知。