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

来源:稀土掘金

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

相关推荐
AKA__Zas1 小时前
初识多线程(2.0)
java·开发语言·学习方法
0xDevNull1 小时前
Java十道高频面试题(二)
java·开发语言
java1234_小锋1 小时前
Spring AI 2.0 开发Java Agent智能体 - 会话记忆(Chat Memory)
java·人工智能·spring
Sylvia33.1 小时前
世界杯数据链路解析:从球场传感器到终端推送的毫秒级架构
java·前端·python·架构
Royzst1 小时前
Lambda 算法基础 集合概述
java·开发语言
Yeh2020581 小时前
Mybatis笔记一
java·笔记·mybatis
likerhood1 小时前
Java 动态代理深度解析:从“为什么“到“底层原理“
java
_阿伟_1 小时前
信息检索简单介绍
java
下次再写1 小时前
深入浅出微服务架构:从理论到Spring Boot实战
java·微服务·springboot·springcloud·架构设计·后端开发·分布式系统