spring重点详解

IOC与BEAN

ioc由来

在 Spring 框架诞生之前,传统 Java 企业级开发中,开发者创建对象、管理组件依赖全靠手动 new 实例化,类与类之间的依赖关系直接硬编码在业务代码里。

这种开发方式带来了严重问题:组件耦合度极高,修改、替换底层类需要改动多处业务代码;对象的创建、销毁、单例管理全由开发者手动控制,缺乏统一规范;同时依赖固化,难以做单元测试、模块复用,项目一旦规模变大,依赖关系错综复杂,维护和扩展成本极高。

控制反转(IoC)并不是 Spring 首创 ,它是一种很早就出现的软件设计思想,核心理念是把对象的创建权限、依赖管理权限,从业务代码中剥离出来,交给第三方容器统一管控

Spring 框架诞生后,为了彻底解决传统开发高耦合、难维护、难扩展的痛点,将 IoC 思想进行落地实现,自研了IOC 容器 。它把所有业务对象统一交由容器管理,由容器负责 Bean 的实例化、生命周期管理、依赖自动装配,将开发者主动主动依赖对象,转变为容器主动推送依赖,实现了控制权的反转。 自此,IOC 容器成为 Spring 最核心的基础,也奠定了 Spring 解耦、轻量化、易整合的框架特性。 依赖注入:IoC 是容器帮你管对象,依赖自动装配是容器帮你自动给对象配齐它需要的所有依赖。

ioc中bean的生命周期

Spring 启动时 Bean 的完整生命周期

Spring Bean 的生命周期是容器启动 → 实例化 → 属性赋值 → 初始化 → 业务使用 → 容器关闭 → 销毁的完整过程,也是面试和开发的核心知识点。

  1. Bean 实例化
  2. 属性填充 / 依赖注入
  3. Bean 后置处理器 前置处理
  4. Bean 初始化
  5. Bean 后置处理器 后置处理
  6. Bean 就绪,放入单例池对外服务
  7. 容器关闭,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

假设你每个业务方法都要做这些通用操作:

  • 接口日志入参 / 出参打印
  • 方法耗时统计
  • 事务开启 / 提交 / 回滚
  • 权限校验
  • 全局异常捕获
  • 接口限流、埋点统计

传统写法:把这些通用代码硬写在每一个业务方法里

  1. 大量重复代码,到处复制粘贴
  2. 业务代码和非业务代码强耦合,业务逻辑被杂七杂八的代码淹没
  3. 要改日志格式、改事务规则、加新校验,所有业务方法都要改,维护爆炸
  4. 违反单一职责、开闭原则

OOP 面向对象只能纵向通过继承、复用代码 ,但跨多个无关类、无关方法的通用逻辑,OOP 根本搞不定。

实现原理

Spring AOP 提供了两种动态代理实现,根据目标类的特性自动选择,这是 AOP 的底层基石:

1. JDK 动态代理(Spring 默认)
  • 适用场景 :目标类实现了业务接口
  • 实现原理 :代理类和目标类实现同一个接口,重写接口方法,在方法中插入切面逻辑,再调用目标对象的原方法。
  • 底层依赖 :JDK 原生的 java.lang.reflect.Proxy + InvocationHandler
  • 优点:JDK 原生,无需额外依赖
  • 缺点必须基于接口,无法代理没有实现接口的类
2. CGLIB 动态代理(Code Generation Library)
  • 适用场景 :目标类没有实现任何接口
  • 实现原理 :代理类继承目标类,重写目标方法,在方法中插入切面逻辑。
  • 底层依赖:ASM 字节码操作框架(直接生成子类字节码)
  • 优点:无需接口,直接代理普通类
  • 缺点 :无法代理 final 类 / final 方法(无法继承重写)

Spring 代理选择规则
  1. 目标类实现了接口 → 自动用 JDK 动态代理
  2. 目标类未实现接口 → 自动用 CGLIB 动态代理
  3. 可强制全局使用 CGLIB:配置 proxy-target-class=true

反射与jdk代理

表格

对比维度 反射 Reflection JDK 动态代理
本质 Java 基础元数据操作工具 基于反射封装的代理增强机制
是否生成新类 ❌ 不生成,只操作已有类 ✅ 运行时动态生成全新代理类
核心能力 读类信息、动态创建对象、主动调用方法 拦截方法调用、前后增强逻辑
方法拦截 ❌ 无拦截能力,只能主动调用 ✅ 天生就是为拦截而生
依赖接口 不需要接口,任意类都能反射 必须基于接口才能代理
独立性 可单独使用,和代理无关 底层强依赖反射,不能脱离反射
典型用途 ORM 框架、注解解析、AOP 切点解析 Spring AOP 接口类代理、事务拦截
调用控制权 代码主动发起调用 外部调用先走代理,再转发原方法

代理运行步骤

  1. @Autowired 注入拿到的是 代理对象,不是原生目标对象
  2. 你调用:userService.save()
  3. 实际执行的是:代理类里的 save () 方法
  4. 先跑代理类里面的 AOP 通用逻辑(前置、日志、权限、事务)
  5. 代理再主动去调用 目标类原生的 save () 业务逻辑

1. JDK 代理

代理类 和 目标类 实现同一个接口 → 代理类里有完全一模一样的方法→ 一调用就进代理的方法,先执行切面逻辑,再反射调目标类方法

2. CGLIB 代理

代理类 是 目标类的子类 → 代理类重写了父类(目标类)一模一样的方法 → 一调用就进重写后的代理方法,先走切面逻辑,再 super 调用父类原方法

作者:zue

链接:https://juejin.cn/post/7637734396590047238

来源:稀土掘金

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

相关推荐
Penge6661 天前
Go 接口编译期断言
后端
我是一颗柠檬1 天前
【MySQL全面教学】MySQL面试高频考点汇总Day15(2026年)
数据库·后端·mysql·面试
橙淮1 天前
并发编程(六)
java·jvm
拽着尾巴的鱼儿1 天前
springboot openfeign 自定义feign 接口重试机制
java·spring boot·后端
白露与泡影1 天前
2026大厂Java面试题大全!牛客网最新版
java·开发语言
Ceelog1 天前
久坐党自救指南:屏幕前 8 小时,身体到底在经历什么
前端·后端
EntyIU1 天前
JVM内存与GC笔记
java·jvm·笔记
XS0301061 天前
并发编程 六
java·后端
yaoxin5211231 天前
419. 现代 Java IO 最佳实践 - 写入文本文件
java·windows·python
雪宫街道1 天前
synchronized 锁的范围:对象锁、类锁与代码块锁
java·jvm·后端·面试