Spring IOC、Bean注入、循环依赖、AOP、动态代理完整原理

约 5181 字

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 完整流程:

  1. 资源加载、扫描配置:加载xml/注解配置、扫描包路径,得到Bean定义信息
  2. 解析Bean定义(BeanDefinition):不创建对象,仅解析类信息、属性、作用域、依赖,存入BeanDefinitionMap
  3. Bean实例化:单例Bean在容器启动阶段统一通过反射创建对象
  4. 依赖注入DI:自动填充属性、注入依赖Bean
  5. 初始化前置后置:Bean后置处理器、InitializingBean、init-method
  6. 完成初始化:放入单例池,对外提供服务
  7. 容器关闭:执行销毁方法

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 互相依赖为例:

  1. A开始实例化,创建空对象,放入三级缓存
  2. A填充属性,发现需要B,去创建B
  3. B实例化完成,填充属性需要A
  4. B先查一级缓存无、二级无、三级拿到A的早期引用
  5. B完成初始化,进入一级缓存
  6. A继续完成属性填充、初始化
  7. 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正常执行与异常执行顺序固定,环绕通知优先级最高,异常后不执行返回通知。