SpringBoot 核心原理 + Spring 事务 + 缓存

IOC / AOP + Bean 生命周期

IOC控制反转

生活化类比:

  • 传统开发(没有 IOC):你做饭,米、菜、锅全部自己去超市买(自己 new 对象)
  • IOC 思想:有个食堂(Spring 容器),所有食材厨具都由食堂统一管理
  • DI 依赖注入:你要做饭,食堂直接把锅和菜送到你手上(容器注入依赖)

核心考点:解耦。业务代码不需要关心对象怎么创建,只负责业务逻辑,便于单元测试、替换实现类。

IOC 全称控制反转(Inversion of Control)

原本是程序员自己 new 对象、管理对象生命周期;反转后,对象的创建、销毁、依赖管理交给 Spring 容器负责,控制权从业务代码反转到容器。

DI 全称依赖注入(Dependency Injection)

是 IOC 的具体实现手段。容器主动把需要的依赖对象注入到目标 Bean 中,不需要自己手动 new 依赖。

一句话总结关系: IOC 是思想,DI 是实现这个思想的方式。

  • IOC:思想(控制权移交容器)
  • DI:技术手段(注入依赖)
  • Bean 容器:承载所有 Bean,落地 IOC 思想的载体

请口述:IOC 解决了开发中的什么问题?带来了什么好处?

IOC 核心解决了传统开发中,手动 new 对象造成的代码高耦合问题。 好处:

  1. 解耦:业务代码不用自己创建依赖,依赖交给 Spring 容器统一管理;
  2. 便于灵活替换实现类,面向接口编程;
  3. 方便单元测试,可以轻松注入 Mock 对象;
  4. 容器统一管控 Bean 的创建、销毁、生命周期。

AOP(面向切面编程)

大白话类比

正常业务主线:短信下发核心逻辑(主线业务)

切面:日志记录、接口耗时统计、权限校验、事务管理、异常通知

AOP:不改动主线业务代码,横向植入通用公共逻辑

标准定义

AOP 面向切面编程,在不修改原有业务代码的前提下,对方法进行增强,抽取通用横切逻辑。

常见使用场景:

  • 事务管理(@Transactional 底层就是 AOP 动态代理)
  • 接口日志、请求耗时埋点
  • 权限校验、接口限流
  • 全局异常处理

两种动态代理

  1. JDK 动态代理:要求目标类实现接口,代理对象实现和目标相同的接口,只能拦截接口里的方法;
  2. CGLIB 动态代理:通过继承目标类生成子类作为代理,不需要实现接口;无法代理 final 类、final 方法。

SpringBoot2.x 之后,默认使用 CGLIB

@Transactional 事务失效很大一个原因:AOP 动态代理失效(内部方法调用,this 调用,不走代理对象)

Bean 的完整生命周期

大白话类比:

Bean 就像盖房子

定义图纸(BeanDefinition)→ 建房实例化 → 装修填充依赖(DI)→ 各种改造回调(初始化)→ 入住使用 → 到期拆除(销毁)

完整流程

  1. 实例化:调用构造方法,new 出 Bean 原始对象(此时对象存在,属性还没赋值)
  2. 属性填充 DI:完成依赖注入,给对象的成员变量赋值
  3. 初始化
    • BeanPostProcessor 前置处理
    • 执行 InitializingBean 接口 afterPropertiesSet
    • 执行 @PostConstruct 标注方法
    • 指定 init-method 自定义初始化方法
    • BeanPostProcessor 后置处理(AOP 代理就是在这里生成代理对象!重点!)
  4. 就绪阶段:Bean 完成,放入单例池,可供业务使用
  5. 销毁:容器关闭时,执行 @PreDestroy、DisposableBean、destroy-method

通俗大白话对照

  • 实例化 = 毛坯房建好
  • 属性填充 DI = 水电、家具搬进房子
  • 初始化各种回调 = 全屋改造、定制装修
  • BeanPostProcessor 后置 = 加装监控切面(AOP 代理)
  • 就绪阶段 = 可以入住使用
  • 销毁 = 房子拆除

对照表格

执行顺序 标准阶段 标准含义 大白话房子比喻
0 BeanDefinition 解析 扫描注解 / XML,加载 Bean 的元数据定义 拿到房子的设计图纸
1 实例化 调用构造方法,new 出原始对象(属性还没赋值) 毛坯房主体建好
2 属性填充 DI 依赖注入,给成员变量赋值 水电、家具搬进房子
3 BeanPostProcessor 前置 初始化前的统一增强处理 装修前的全屋检查
4 @PostConstruct 执行注解标注的初始化方法 业主自己做的首次布置
5 InitializingBean 回调 执行 afterPropertiesSet 接口方法 物业统一验收配置
6 init-method 执行 XML / 注解指定的自定义初始化方法 业主定制的特殊装修
7 BeanPostProcessor 后置 初始化后的增强处理,AOP 代理对象在此生成 加装监控 / 门禁(切面代理)
8 就绪阶段 单例 Bean 放入单例池,对外提供使用 可以正式入住使用
9 @PreDestroy 容器关闭时执行销毁前回调 搬家前清点物品
10 DisposableBean 回调 执行 destroy 接口方法 物业统一断水断电
11 destroy-method 执行自定义销毁方法 业主自行拆除私人物品
  1. AOP 代理生成位置:第 7 步 BeanPostProcessor 后置处理,不是实例化阶段。
  2. 单例 Bean :默认容器启动时就走完 1~8 全部流程;加 @Lazy 才延迟到第一次使用。
  3. 多例 Bean(prototype) :每次 getBean 才实例化,Spring 不管理销毁(9~11 不执行),由 JVM GC 回收。
  4. 执行顺序口诀:实例 → 填属性 → 前置 → 三个初始化 → 后置(生成代理)→ 使用 → 三个销毁。

关键

  1. 单例 Bean:容器启动时就完成实例化、初始化;
  2. 多例 Bean prototype :每次 getBean 才实例化,Spring 容器不管理销毁
  3. AOP 代理生成在初始化后置处理器这一步;
  4. 顺序记忆口诀:实例 → 填属性 → 前置 → 初始化回调 → 后置(生成代理)→ 使用 → 销毁

SpringBoot 自动装配原理

大白话理解

传统 SSM:需要自己大量写 XML、手动配置 Bean(数据源、Mybatis、线程池等),自己 import 各种配置类,配置繁琐、容易冲突。

自动装配:SpringBoot 根据项目引入的依赖、配置文件,自动帮我们导入、创建、装配所需 Bean,不用手写大量配置。

类比: SSM = 组装电脑,主板、显卡、驱动全部自己挑选安装

SpringBoot 自动装配 = 品牌整机,你选好配件(引入 starter 依赖),出厂预装驱动(自动装配 Bean)

核心启动注解:@SpringBootApplication

拆开这个复合注解,由 3 个核心注解组成:

1.@SpringBootConfiguration

本质就是 @Configuration,代表当前类是配置类,可以定义 @Bean

2.@EnableAutoConfiguration(重中之重,自动装配开关)

开启自动装配功能

3.@ComponentScan

默认扫描当前启动类所在包及其子包下所有 @Component、@Service、@Repository、@Controller,注册为 Bean

坑点:如果业务类放在启动类包外层,默认扫描不到,不会注入 Bean!

@EnableAutoConfiguration 底层流程

@EnableAutoConfiguration 里面导入了 AutoConfigurationImportSelector(选择导入哪些自动配置类) 完整流程:

  1. 项目启动,执行 selectImports 方法
  2. 读取 META-INF/spring.factories 文件(springboot 内置在各个 starter 包里面)
  3. 文件里存放大量自动配置类全限定名:MybatisAutoConfiguration、DataSourceAutoConfiguration 等
  4. 拿到所有候选配置类后,进行条件注解过滤 (核心:@ConditionalOnXXX)
    • @ConditionalOnClass:类存在才装配
    • @ConditionalOnMissingBean:容器不存在该 Bean,才自动创建
  5. 满足条件的配置类,导入容器,里面定义好各种 Bean,完成自动装配
  6. 我们 yml 配置文件(spring.datasource 等),用来修改自动装配 Bean 的默认属性

Starter 是什么?

starter = 依赖 jar 包 + 配套自动配置类

例如 mybatis-spring-boot-starter

  • 引入 mybatis、jdbc 相关依赖
  • 内置 MybatisAutoConfiguration 自动配置类,满足条件自动装配 SqlSessionFactory、MapperScannerConfigurer

核心扩展

1.自动装配不是全部加载所有配置类!依靠条件注解按需装配

2.自定义覆盖自动装配 Bean:自己定义同名 Bean,@ConditionalOnMissingBean 会让 SpringBoot 不再自动创建,用户自定义 Bean 优先级更高

举例:SpringBoot 自动装配线程池,我们自己写 @Bean ThreadPoolExecutor,就会覆盖默认线程池(短信项目批量下发场景常用)

3.SpringBoot2.7 之后,不再使用 spring.factories,改用 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

精简版

SpringBoot 自动装配由 @EnableAutoConfiguration 开启,底层通过 AutoConfigurationImportSelector 读取配置文件获取候选自动配置类,配合 @ConditionalOnXXX 条件注解,根据引入的 starter 依赖按需自动创建 Bean;开发者可自定义 Bean 覆盖默认自动装配,减少手写配置。

组件 作用
@SpringBootApplication 启动复合注解,整合三大功能
@EnableAutoConfiguration 开启自动装配核心开关
AutoConfigurationImportSelector 读取配置文件,获取候选自动配置类
spring.factories / AutoConfiguration.imports 存放所有自动配置类全类名
@ConditionalOnXXX 条件注解 按需过滤,满足条件才装配 Bean

Spring 事务

核心目标:掌握 7 种事务传播机制 + 事务隔离级别 + 事务失效高频场景

基础铺垫:@Transactional 底层原理

大白话: @Transactional 本质依靠 AOP 动态代理

  1. 调用目标方法时,先走代理对象
  2. 方法执行前:开启事务
  3. 正常执行完毕:提交事务
  4. 抛出指定异常:事务回滚

⚠️ 关键点:必须通过代理对象调用方法,事务才会生效,这是大部分事务失效的根源。

两大基础概念

事务隔离级别(和 MySQL InnoDB 隔离级别一一对应)

@Transactional(isolation = Isolation.XXX)

  1. DEFAULT:默认,使用数据库本身隔离级别(InnoDB 默认 RR 可重复读)
  2. READ_UNCOMMITTED:读未提交
  3. READ_COMMITTED:读已提交
  4. REPEATABLE_READ:可重复读
  5. SERIALIZABLE:串行化

7 种事务传播行为

传播行为解决:一个带事务的方法,调用另一个带事务的方法,事务怎么合并 / 创建 @Transactional(propagation = Propagation.XXX)

传播属性 含义(大白话) 常用场景
REQUIRED(默认) 有事务就加入当前事务;没有就新建事务 绝大多数业务,短信创建、保存日志
REQUIRES_NEW 不管有没有事务,新建独立事务,各自互不影响 记录操作日志,主事务回滚,日志依然可以提交保存
SUPPORTS 有事务就加入,没有就非事务运行 查询类方法,可有可无事务
NOT_SUPPORTED 永远非事务执行,挂起已有事务 异步通知、第三方调用,不参与事务
MANDATORY 必须在已有事务中运行,没有直接抛异常 内部核心业务,禁止无事务调用
NEVER 禁止存在事务,如果已有事务直接抛异常 纯查询,严禁事务包裹
NESTED 嵌套事务,依赖外层事务;外层回滚,内层全部回滚;内层回滚不影响外层 批量操作,支持局部回滚(保存点)

高频区分:REQUIRES_NEW vs NESTED

REQUIRES_NEW:两个完全独立事务,互不影响提交回滚

NESTED:内层是外层事务的保存点,外层回滚内层一起回滚

Spring 事务失效五大类场景

1.方法不是 public 修饰

@Transactional 只作用于 public 方法,private、protected 不会生成代理,事务失效。

2.同类中方法内部调用(this 调用)

例如 A 方法(无注解)this 调用本类带 @Transactional 的 B 方法,不走代理对象,事务失效。

✅ 解决方案:注入自身代理对象,或者拆分到不同类

3.异常类型不对,不会触发回滚

默认只捕获 RuntimeException / Error;如果抛出受检 Exception,不回滚。

解决:@Transactional (rollbackFor = Exception.class)

4.异常被 try-catch 手动捕获吃掉

方法内部 try catch 捕获异常,没有对外抛出,代理感知不到异常,不会回滚。

5.多线程场景

事务是绑定当前线程,新开启子线程执行数据库操作,无法共用主线程事务,事务失效。

补充额外坑:数据库引擎不支持事务(MyISAM),不管怎么加注解都无效。

业务举例(短信批量创建业务)

批量创建多条短信记录,希望整体要么全部入库,要么全部回滚。 方法上加 @Transactional (rollbackFor = Exception.class),默认 REQUIRED。

如果中间某条短信参数校验失败抛异常,整体事务回滚,不会出现一半数据入库脏数据。

缓存专项全套(三大缓存问题:穿透、击穿、雪崩)

重点:每个问题都记住 现象 → 产生原因 → 工业级解决方案,结合短信业务理解

前置基础:缓存使用流程

先查 Redis 缓存 ✅ 命中直接返回;缓存没命中,查询 MySQL,查到数据写入 Redis,再返回结果

缓存穿透

现象

大量请求查询数据库不存在的数据,缓存永远不会写入数据,请求全部绕过缓存,直接打到数据库,压垮 DB。

短信业务例子:前端传入不存在的手机号,查询短信下发记录,Redis 查不到,每次都查 MySQL

原因

查询的 key 对应数据在数据库本身就不存在,缓存没有兜底数据,每次都穿透到数据库

解决方案(工业常用)

  1. 缓存空值 / 空对象:数据库查不到,向 Redis 存入空值,设置较短过期时间(5 分钟),拦截后续相同请求
  2. 布隆过滤器(高并发推荐):前置存放所有合法 key,非法 key 直接拦截,不访问缓存和数据库
  3. 接口参数校验:手机号格式校验,非法参数直接拒绝

缓存击穿

现象

热点 key 过期瞬间,大量并发请求同时进来,缓存失效,所有请求瞬间打到数据库。

短信业务例子:营销活动热门手机号的下发统计缓存,刚好过期,瞬时大量请求访问 DB

原因

某个访问极高的热点 key 到期删除,并发请求同时到来,缓存缺失

解决方案

  1. 互斥锁(分布式锁,Redisson):缓存失效时,只放行一个线程去查 DB 并回写缓存,其他线程等待重试
  2. 热点 key 永不过期(逻辑过期):不设置物理 expire,后台异步线程定时更新缓存数据,避免集中过期

注意:互斥锁会有等待,存在性能损耗;逻辑过期适合高并发热点数据

缓存雪崩

现象

大量 key 在同一时间段集体过期,大量请求同时绕过缓存,数据库压力瞬间飙升,引发服务雪崩。

区分:击穿是单个热点 key 过期;雪崩是大批量 key 集中过期

原因

  1. 大量缓存 key 设置相同过期时间,同一时刻全部失效
  2. Redis 集群宕机,整体缓存服务不可用

解决方案

  1. 过期时间加随机值:基础过期时间 + 随机偏移量,打散过期时间,避免集体失效
  2. Redis 高可用部署:主从、哨兵、Redis Cluster 集群,防止单点宕机
  3. 服务层限流熔断(Sentinel):缓存失效后,限制访问数据库的流量,保护 DB
  4. 多级缓存:本地 Caffeine 缓存 + Redis 远程缓存,双层兜底
问题 核心特征 根本原因 主流解决方案
缓存穿透 查询不存在的数据,永远无缓存 DB 无对应数据 缓存空值、布隆过滤器、参数校验
缓存击穿 单个热点 key 过期,并发打 DB 高访问热点 key 到期 分布式互斥锁、逻辑永不过期
缓存雪崩 大批量 key 同时失效,整体压力突增 大量 key 同一时间过期 / Redis 宕机 过期加随机值、Redis 集群、限流熔断、多级缓存

缓存更新策略

三种常用策略

  1. 先更新数据库,再删除缓存(工业主流方案,短信项目推荐)
  2. 先删缓存,再更新数据库(存在短期脏数据风险)
  3. 更新数据库同时更新缓存(不推荐,并发下数据不一致)

配套问题:数据一致性如何保障?

最终方案:定时任务校对缓存与数据库数据;监听 binlog(Canal)异步更新缓存,保证最终一致性。

相关推荐
2601_963870202 小时前
【计算机毕业设计】基于Spring Boot的专科医院医疗管理系统
java·spring boot·课程设计
Bingo_BIG2 小时前
Java Spring 批量修改,实体、接口、方法的定义
java·spring
jufeng13075 小时前
【系列:TDengine 工业物联网实战:从零搭起可运行系统 · 第 8 篇】
java·spring boot·时序数据库·tdengine
敲个大西瓜7 小时前
Spring Could Alibaba 核心面试题
java·后端·spring
添砖java‘’8 小时前
Redis中常用数据结构
数据库·redis·缓存
caimouse12 小时前
ReactOS 图形系统分析(53):系统库存对象 — stockobj.c
c语言·开发语言·spring
SQL-First布道者12 小时前
持久层框架的评价标准:只有一个
java·spring boot·spring·tomcat·mybatis·spring jdbc
m0_5873830012 小时前
智慧场馆解决方案实战指南:从系统架构到落地部署全解析
java·spring boot·架构·系统架构
智码看视界13 小时前
Day53-Docker:Dockerfile最佳实践 + 多阶段构建 + docker-compose编排
java·spring boot·docker·docker-compose·dockerfile·容器化部署·多阶段构建