IOC与BEAN
ioc由来
在 Spring 框架诞生之前,传统 Java 企业级开发中,开发者创建对象、管理组件依赖全靠手动 new 实例化,类与类之间的依赖关系直接硬编码在业务代码里。
这种开发方式带来了严重问题:组件耦合度极高,修改、替换底层类需要改动多处业务代码;对象的创建、销毁、单例管理全由开发者手动控制,缺乏统一规范;同时依赖固化,难以做单元测试、模块复用,项目一旦规模变大,依赖关系错综复杂,维护和扩展成本极高。
控制反转(IoC)并不是 Spring 首创 ,它是一种很早就出现的软件设计思想,核心理念是把对象的创建权限、依赖管理权限,从业务代码中剥离出来,交给第三方容器统一管控。
Spring 框架诞生后,为了彻底解决传统开发高耦合、难维护、难扩展的痛点,将 IoC 思想进行落地实现,自研了IOC 容器 。它把所有业务对象统一交由容器管理,由容器负责 Bean 的实例化、生命周期管理、依赖自动装配,将开发者主动主动依赖对象,转变为容器主动推送依赖,实现了控制权的反转。 自此,IOC 容器成为 Spring 最核心的基础,也奠定了 Spring 解耦、轻量化、易整合的框架特性。 依赖注入:IoC 是容器帮你管对象,依赖自动装配是容器帮你自动给对象配齐它需要的所有依赖。
ioc中bean的生命周期
Spring 启动时 Bean 的完整生命周期
Spring Bean 的生命周期是容器启动 → 实例化 → 属性赋值 → 初始化 → 业务使用 → 容器关闭 → 销毁的完整过程,也是面试和开发的核心知识点。
- Bean 实例化
- 属性填充 / 依赖注入
- Bean 后置处理器 前置处理
- Bean 初始化
- Bean 后置处理器 后置处理
- Bean 就绪,放入单例池对外服务
- 容器关闭,Bean 销毁
1. Bean 实例化
动作 Spring 根据 Bean 定义,通过反射构造器 new 出 Bean 原始对象。
能做什么
- 自定义对象创建方式:工厂 Bean、静态工厂方法、实例工厂方法;
- 此时只是空对象,属性还没赋值、依赖还没注入。
2. 属性填充 & 依赖注入
动作 自动装配 @Autowired、构造器、setter,把依赖 Bean、配置属性赋值到当前对象。
能做什么
- 自动注入其他组件、注入配置文件参数;
- 完成类与类之间依赖绑定,彻底解耦,不用手动 new。
3. BeanPostProcessor 初始化前置
动作 执行 postProcessBeforeInitialization。
能做什么
- 修改原始 Bean、做前置校验、包装对象;
- Spring AOP 动态代理就是在这个阶段生成代理对象。
4. Bean 初始化(三种方式,优先级固定)
优先级:@PostConstruct 注解 > InitializingBean#afterPropertiesSet 接口 > xml init-method
动作依赖注入完成后,执行自定义初始化逻辑。
能做什么
- 初始化资源:创建线程池、初始化缓存、加载字典数据;
- 校验属性参数、建立数据库 / Redis 连接;
- 做项目启动预热、初始化配置。
5. BeanPostProcessor 初始化后置
动作 执行 postProcessAfterInitialization。
能做什么
- 再次包装、替换 Bean 实例;
- 对代理对象做最终加工、缓存代理 Bean。
6. Bean 就绪,存入单例池
动作单例 Bean 放入 Spring 单例池,常驻容器。
能做什么
- 正式对外提供业务服务;
- 接收请求、执行业务逻辑,整个项目运行期间可随时使用。
多例 Bean 不会放入单例池,每次获取都新建对象。
7. Bean 销毁(容器关闭时触发)
优先级:@PreDestroy 注解 > DisposableBean#destroy 接口 > xml destroy-method
动作Spring 容器关闭、销毁上下文时执行。
能做什么
- 释放资源:关闭数据库 / Redis 连接、销毁线程池;
- 清理本地缓存、保存临时数据、释放文件句柄;
注意:多例 Bean Spring 不管理销毁,不会执行销毁方法。
bean单例池
Spring 单例池也叫一级缓存、单例 Bean 缓存池,底层本质是:
java
运行
javascript
体验AI代码助手
代码解读
复制代码
ConcurrentHashMap<String, Object> singletonObjects
- Key:Bean 的名称
- Value :已经完全实例化、属性注入、初始化完成、可直接使用的完整单例 Bean
Spring 默认所有 Bean 都是 singleton 单例作用域 ,容器启动时创建好,放入单例池缓存起来。
单例池 核心五大作用
1. 保证容器级全局单例
整个 Spring IoC 容器中,同一个 Bean 只会存在一个实例 。后续无论多少次 getBean()、@Autowired 依赖注入,都是从单例池拿同一个对象,不会重复 new。
注意:Spring 单例是容器级单例,不是 JVM 全局单例;多个 Spring 容器之间互不影响。
2. 节省资源、提升启动与运行性能
对象创建、反射实例化、依赖注入、初始化方法执行都是耗时操作。
- 只在容器启动时创建一次,放入单例池
- 后续直接复用,避免频繁反射创建对象
- 减少内存占用、减少 GC 压力、大幅提升系统运行效率
3. 统一管控 Bean 完整生命周期
只有放入单例池的单例 Bean,Spring 才会全程托管:
- 实例化 → 属性注入 → Aware 接口 → 初始化前后置处理器
- 执行
@PostConstruct、InitializingBean - AOP 代理生成
- 容器关闭时执行
@PreDestroy、销毁方法
原型 Bean 不进单例池 ,Spring 只负责创建,不管理销毁。
4. 实现依赖注入直接复用
项目中所有 @Autowired、构造器注入、Setter 注入,底层都是直接从单例池取出现成 Bean 进行赋值,不用临时创建。
5. 解决 Spring 循环依赖的核心(重中之重)
Spring 解决单例 Bean 循环依赖 靠三级缓存 ,而单例池就是一级缓存:
- 一级缓存(singletonObjects) :单例池,存放完全就绪、初始化完成的 Bean
- 二级缓存:存放已实例化但未填充属性的半成品 Bean
- 三级缓存:存放 Bean 工厂 ObjectFactory,用于延迟创建、兼顾 AOP 代理
循环依赖最终完好的 Bean 都会存入单例池,供其他 Bean 直接引用。
循环依赖

Spring 通过 三级缓存提前暴露实例化后但未初始化的对象引用 ,打破了循环创建的僵局,但前提是 不能通过构造器循环依赖。
AOP详解&&JVM与AOP关联
为什么引入AOP
假设你每个业务方法都要做这些通用操作:
- 接口日志入参 / 出参打印
- 方法耗时统计
- 事务开启 / 提交 / 回滚
- 权限校验
- 全局异常捕获
- 接口限流、埋点统计
传统写法:把这些通用代码硬写在每一个业务方法里
- 大量重复代码,到处复制粘贴
- 业务代码和非业务代码强耦合,业务逻辑被杂七杂八的代码淹没
- 要改日志格式、改事务规则、加新校验,所有业务方法都要改,维护爆炸
- 违反单一职责、开闭原则
OOP 面向对象只能纵向通过继承、复用代码 ,但跨多个无关类、无关方法的通用逻辑,OOP 根本搞不定。
实现原理
Spring AOP 提供了两种动态代理实现,根据目标类的特性自动选择,这是 AOP 的底层基石:
1. JDK 动态代理(Spring 默认)
- 适用场景 :目标类实现了业务接口
- 实现原理 :代理类和目标类实现同一个接口,重写接口方法,在方法中插入切面逻辑,再调用目标对象的原方法。
- 底层依赖 :JDK 原生的
java.lang.reflect.Proxy+InvocationHandler - 优点:JDK 原生,无需额外依赖
- 缺点 :必须基于接口,无法代理没有实现接口的类
2. CGLIB 动态代理(Code Generation Library)
- 适用场景 :目标类没有实现任何接口
- 实现原理 :代理类继承目标类,重写目标方法,在方法中插入切面逻辑。
- 底层依赖:ASM 字节码操作框架(直接生成子类字节码)
- 优点:无需接口,直接代理普通类
- 缺点 :无法代理
final类 /final方法(无法继承重写)
Spring 代理选择规则
- 目标类实现了接口 → 自动用 JDK 动态代理
- 目标类未实现接口 → 自动用 CGLIB 动态代理
- 可强制全局使用 CGLIB:配置
proxy-target-class=true
反射与jdk代理
表格
| 对比维度 | 反射 Reflection | JDK 动态代理 |
|---|---|---|
| 本质 | Java 基础元数据操作工具 | 基于反射封装的代理增强机制 |
| 是否生成新类 | ❌ 不生成,只操作已有类 | ✅ 运行时动态生成全新代理类 |
| 核心能力 | 读类信息、动态创建对象、主动调用方法 | 拦截方法调用、前后增强逻辑 |
| 方法拦截 | ❌ 无拦截能力,只能主动调用 | ✅ 天生就是为拦截而生 |
| 依赖接口 | 不需要接口,任意类都能反射 | 必须基于接口才能代理 |
| 独立性 | 可单独使用,和代理无关 | 底层强依赖反射,不能脱离反射 |
| 典型用途 | ORM 框架、注解解析、AOP 切点解析 | Spring AOP 接口类代理、事务拦截 |
| 调用控制权 | 代码主动发起调用 | 外部调用先走代理,再转发原方法 |
代理运行步骤
- 你
@Autowired注入拿到的是 代理对象,不是原生目标对象 - 你调用:
userService.save() - 实际执行的是:代理类里的 save () 方法
- 先跑代理类里面的 AOP 通用逻辑(前置、日志、权限、事务)
- 代理再主动去调用 目标类原生的 save () 业务逻辑
1. JDK 代理
代理类 和 目标类 实现同一个接口 → 代理类里有完全一模一样的方法→ 一调用就进代理的方法,先执行切面逻辑,再反射调目标类方法
2. CGLIB 代理
代理类 是 目标类的子类 → 代理类重写了父类(目标类)一模一样的方法 → 一调用就进重写后的代理方法,先走切面逻辑,再 super 调用父类原方法
作者:zue
链接:https://juejin.cn/post/7637734396590047238
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。