【个人学习||spring】
1. 学科一句话概括
Spring 是一套用来“管理对象、解耦模块、统一横切能力、承接 Web 请求、组织企业应用”的 Java 框架体系。
如果把一个 Java 后端项目比作一家公司:
- Java 类 是员工。
- 对象创建与依赖关系 是招聘和组织架构。
- 日志、事务、权限 是所有部门都要遵守的共性制度。
- HTTP 请求 是外部客户的需求单。
- Spring 做的事,就是把这家公司从“各自为战”变成“有组织、有流程、可扩展”的系统。
一句话再压缩就是:
Spring 的本质,是把原本散落在代码各处的“对象管理、流程控制、公共能力”集中收拢,交给框架统一组织。
2. 先明确 Spring 在学什么
2.1 这门学科研究什么
Spring 研究的不是“某一个 API 怎么调”,而是:
- Java 企业应用应该如何组织
- 对象之间怎样解耦
- 公共逻辑怎样复用
- 请求怎样流转
- 项目怎样快速启动、配置、扩展和维护
所以,Spring 的学习重点不是背注解,而是理解:
- 为什么要把对象交给容器管理。
- 为什么日志、事务、权限这些能力不应该写死在业务代码里。
- 为什么 Web 请求需要统一入口和统一调度。
- 为什么 Spring Boot 能大幅减少样板配置。
2.2 它解决什么核心问题
如果没有 Spring,一个 Java Web 项目通常会遇到四类痛点:
| 痛点 | 本质问题 | 结果 |
|---|---|---|
到处 new 对象 |
创建权和使用权耦合 | 模块关系混乱,不好替换和测试 |
| 公共逻辑到处复制 | 横切逻辑分散 | 日志、事务、权限代码污染业务 |
| 请求处理流程杂乱 | 缺少统一入口 | 参数绑定、异常处理、返回值处理重复 |
| 配置繁琐、启动复杂 | 工程装配成本高 | 新项目起步慢,维护成本高 |
Spring 的核心解法分别是:
- IoC / DI:把对象创建和依赖注入交给容器。
- AOP:把日志、事务、权限等横切逻辑从业务代码中剥离。
- Spring MVC:把 Web 请求处理收敛到统一调度流程。
- Spring Boot:用自动配置和约定优于配置,大幅降低工程搭建成本。
2.3 为什么它在课程体系、考试、面试、项目里都重要
Spring 重要,不是因为“它火”,而是因为它正好卡在 Java 后端学习链条的中间位置:
- 往前,它承接 Java 基础、面向对象、反射、代理、注解、Servlet、JDBC。
- 往后,它连接 MyBatis/JPA、Redis、MQ、Spring Security、Spring Cloud、微服务。
所以它是一个典型的“承上启下”学科:
- 课程里:它是 Java Web / 企业开发的中轴。
- 考试里:IoC、AOP、事务、MVC、Boot 是高频考点。
- 面试里:Bean 生命周期、循环依赖、事务失效、自动装配原理、MVC 流程都是高频题。
- 项目里:几乎所有主流 Java 后端项目都离不开它。
2.4 它和相近学科的因果关联
| 相近学科 | 与 Spring 的关系 | 因果关系 |
|---|---|---|
| Java 面向对象 | Spring 的基础 | 没有类、接口、继承、多态,就理解不了依赖注入与解耦 |
| 反射与注解 | Spring 的底层工具 | Spring 能扫描类、创建对象、装配依赖,靠的就是反射与注解元数据 |
| 动态代理 | AOP 的基础 | Spring AOP 本质上依赖代理去“包一层”增强逻辑 |
| Servlet / HTTP | Spring MVC 的前身和基础 | MVC 是在 Servlet 模式上做的高级封装 |
| JDBC / MyBatis / JPA | 数据访问层能力 | Spring 不替代数据库操作本身,但负责整合、事务管理与工程组织 |
| Spring Boot | Spring 的工程化增强 | Boot 不是另一个框架,而是让 Spring 更容易启动和配置 |
| Spring Cloud | 分布式扩展 | 先懂 Spring/Boot,才有资格学 Cloud |
把关系说得更白一点:
Java 提供语言能力,Servlet/JDBC 提供底层接口,Spring 负责把这些基础能力组织成一套可维护的工程体系。
3. 学科知识地图
3.1 整体知识框架
| 模块 | 核心问题 | 重要程度 | 为什么先学它 | 它为后面提供什么 |
|---|---|---|---|---|
| 模块 0:Spring 整体世界观 | 为什么会有 Spring | ★★★★★ | 先理解痛点,后学解法 | 为后面所有模块建立主线 |
| 模块 1:IoC 与 DI | 对象到底该谁创建、怎么装配 | ★★★★★ | Spring 的根基 | AOP、事务、MVC、Boot 都建立在容器之上 |
| 模块 2:Bean 生命周期与配置 | Bean 何时创建、作用域如何控制 | ★★★★★ | IoC 的深化 | 理解自动装配、启动流程、面试高频问题 |
| 模块 3:AOP | 公共逻辑怎样不污染业务代码 | ★★★★★ | 事务、日志、权限都依赖它 | 为事务、权限、监控打基础 |
| 模块 4:数据访问整合与事务 | 多步数据库操作怎样保证一致性 | ★★★★★ | 项目和考试高频 | 理解 @Transactional 原理与失效场景 |
| 模块 5:Spring MVC | HTTP 请求怎样进入业务代码 | ★★★★★ | Web 开发主线 | 为接口开发、前后端分离打基础 |
| 模块 6:Spring Boot | 项目为什么能“一把启动” | ★★★★★ | 现代项目主流入口 | 理解自动配置、Starter、外部化配置 |
| 模块 7:测试、调试与工程实践 | 学会把知识落在项目里 | ★★★★ | 连接理论和实战 | 能真正写项目、答面试、查问题 |
| 模块 8:扩展边界 | 哪些内容属于后续学习 | ★★ | 防止知识边界混乱 | 为 Security、Cloud、消息队列做铺垫 |
3.2 模块之间的前后依赖关系
可以把 Spring 的学习链看成下面这条因果链:
Java/Servlet/JDBC 基础
↓
Spring 为什么出现(解决对象管理和解耦问题)
↓
IoC / DI(把对象交给容器)
↓
Bean 生命周期 / 配置 / 自动装配(容器怎么真正工作)
↓
AOP(把公共逻辑从业务代码剥离)
↓
事务管理(AOP 的典型应用)
↓
Spring MVC(把 HTTP 请求接进来)
↓
Spring Boot(把前面所有能力快速装配成项目)
↓
测试、调试、项目实践
还有两个非常容易考的“横向依赖”:
- 事务依赖 AOP:没有代理增强,就没有声明式事务。
- Boot 依赖 IoC 容器:自动配置最终还是往容器里放 Bean。
3.3 你应该如何理解这张知识地图
不要把 Spring 看成“很多注解的堆积”,而要看成一套层层递进的工程答案:
- 先解决对象管理,于是有 IoC。
- 再解决公共逻辑复用,于是有 AOP。
- 再解决数据一致性,于是有事务。
- 再解决 Web 请求处理,于是有 MVC。
- 最后解决工程搭建效率,于是有 Boot。
这就是 Spring 最重要的因果顺序。
4. 推荐学习顺序
4.1 最适合小白的顺序
- 先理解 Spring 在解决什么工程问题
- 学习 IoC / DI
- 学习 Bean 生命周期、作用域、配置方式
- 学习 AOP
- 学习声明式事务
- 学习 Spring MVC
- 学习 Spring Boot 自动配置与项目结构
- 做一个完整的小项目并调试常见错误
4.2 为什么不能一上来就学 Spring Boot
很多人一开始就写:
@SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
然后觉得“Spring 很简单,不就是几个注解吗”。
这是最危险的错觉。
因为你一旦不理解底层主线,就会在这些问题上卡死:
- 为什么有些 Bean 注入失败?
- 为什么同类内部方法调用时事务失效?
- 为什么
@RestController能直接返回 JSON? - 为什么加了一个 Starter,系统里突然多了一堆 Bean?
- 为什么同样是单例 Bean,有时线程不安全?
所以正确顺序不是“先学怎么跑”,而是“先学为什么这样跑”。
5. 核心模块详解
5.1 模块 0:Spring 的整体世界观
5.1.1 本模块解决的问题
本模块解决的不是代码问题,而是认知问题:
- Spring 究竟是什么?
- 它到底是框架、容器、还是全家桶?
- 为什么 Java 后端项目离不开它?
如果这个问题不先搞懂,后面每个知识点都容易变成死记硬背。
5.1.2 核心概念
| 概念 | 专业解释 | 通俗理解 |
|---|---|---|
| Framework(框架) | 提供一套通用结构和运行规则的基础设施 | 像房子的钢筋骨架,很多东西不是你现搭,而是按它的结构来建 |
| Container(容器) | 负责创建、保存、管理对象的运行环境 | 像公司的人事系统,谁入职、谁归谁管、谁依赖谁,都由系统登记 |
| Inversion of Control(IoC) | 控制权从程序员手里转交给框架 | 原来你自己招人,现在是公司统一招聘分配 |
| Dependency Injection(DI) | 容器把一个对象需要的依赖注入进去 | 员工上岗时,电脑、账号、工位都提前配好 |
| Aspect(切面) | 横切多个模块的公共逻辑 | 像打卡、权限、日志,不属于某个部门,却影响所有部门 |
5.1.3 用因果链理解 Spring
问:为什么单纯用 Java 类不够?
答:因为项目一大,类和类之间会大量依赖,到处
new,一改就牵一片。
问:那第一步该解决什么?
答:先解决“对象由谁创建”的问题,所以有 IoC。
问:对象都交给容器后,为什么还不够?
答:因为日志、事务、权限这些逻辑还会污染业务代码,所以需要 AOP。
问:后端项目除了写业务,还要接 HTTP 请求,怎么办?
答:于是有 Spring MVC,把请求调度流程标准化。
问:可这些东西配起来仍然很麻烦,怎么办?
答:于是有 Spring Boot,用约定和自动配置把项目快速装起来。
5.1.4 常见误区
-
误区 1:Spring 就是 Spring Boot。
错。Boot 是 Spring 的工程化增强,不是替代品。
-
误区 2:Spring 主要是注解集合。
错。注解只是入口,背后是容器、反射、代理、自动装配机制。
-
误区 3:学 Spring 就是学会 CRUD。
错。CRUD 只是表面,真正高频考的是容器、AOP、事务、请求流程。
5.1.5 简短总结
Spring 不是单个功能,而是一套把企业应用“组织起来”的方法论和框架实现。
5.2 模块 1:IoC 与 DI
5.2.1 本模块解决的问题
本模块解决的问题只有一个,但它是 Spring 的根:
对象到底该谁创建?对象之间的依赖到底怎么连接?
如果每个类都自己 new 依赖,会出现三个问题:
- 强耦合:换实现类要改源码。
- 难测试:很难注入假对象或 Mock。
- 难管理:对象创建时机、数量、配置散落各处。
Spring 的答案是:
对象由容器创建,依赖由容器注入,业务类只负责“使用”,不负责“组装”。
5.2.2 先讲直觉:为什么叫“控制反转”
以前写代码时,你会这样做:
public class OrderService {
private OrderDao orderDao = new OrderDaoImpl();
}
这表示:
OrderService自己决定依赖谁OrderService自己创建依赖- 控制权在业务类手里
用了 IoC 之后:
@Service
public class OrderService {
private final OrderDao orderDao;
public OrderService(OrderDao orderDao) {
this.orderDao = orderDao;
}
}
此时:
OrderService不再自己创建OrderDao- 容器负责找一个合适的
OrderDao放进来 - 控制权从业务类“反转”给容器
所以“控制反转”不是一个神秘术语,它的本质就是:
以前你自己管对象,现在框架替你管对象。
5.2.3 核心概念
| 概念 | 专业术语/写法 | 通俗理解 |
|---|---|---|
| IoC | Inversion of Control | 本来自己做的“招人组队”,现在交给容器统一做 |
| DI | Dependency Injection | 容器按需要把依赖塞给你 |
| Bean | Spring 容器管理的对象 | 被公司正式登记在册的员工 |
| 容器 | BeanFactory、ApplicationContext |
管理所有 Bean 的“总调度室” |
| 装配 | Autowire / Inject | 把依赖关系连起来 |
| 配置元数据 | XML、注解、Java Config | 告诉容器“该创建谁、怎么创建、依赖谁”的说明书 |
5.2.4 Bean、容器、依赖注入到底是什么关系
可以这样理解:
- Bean:一个具体对象。
- 容器:保存和管理这些对象的地方。
- 依赖注入:容器把 A 对象需要的 B 对象放进去。
关系式可以写成:
应用运行 = 容器启动 + 创建 Bean + 建立依赖关系 + 对外提供服务
5.2.5 常见配置方式
方式一:XML 配置
早期教材和考试很喜欢考 XML,因为它能清楚展示 IoC 的本质。
<bean id="orderDao" class="com.demo.dao.impl.OrderDaoImpl"/>
<bean id="orderService" class="com.demo.service.OrderService">
<constructor-arg ref="orderDao"/>
</bean>
这段配置表达的意思是:
- 创建一个
orderDao - 创建一个
orderService - 给
orderService注入orderDao
方式二:注解配置
现代项目更常用。
@Repository
public class OrderDaoImpl implements OrderDao {
}
@Service
public class OrderService {
private final OrderDao orderDao;
public OrderService(OrderDao orderDao) {
this.orderDao = orderDao;
}
}
方式三:Java Config
@Configuration
public class AppConfig {
@Bean
public OrderDao orderDao() {
return new OrderDaoImpl();
}
@Bean
public OrderService orderService(OrderDao orderDao) {
return new OrderService(orderDao);
}
}
5.2.6 依赖注入的三种常见方式
| 方式 | 写法 | 优点 | 缺点 | 推荐度 |
|---|---|---|---|---|
| 构造器注入 | 通过构造方法注入 | 依赖完整、便于测试、适合必需依赖 | 代码略长 | ★★★★★ |
| Setter 注入 | 通过 setXxx() 注入 |
适合可选依赖 | 对象可能在未完整初始化时暴露 | ★★★ |
| 字段注入 | @Autowired private Xxx xxx; |
写起来最短 | 不利于测试,依赖不显式 | ★★ |
最推荐的是构造器注入。
原因很简单:
- 依赖一眼就能看见
- 不容易漏
- 单元测试更方便
5.2.7 BeanFactory 和 ApplicationContext
| 概念 | 说明 | 通俗理解 |
|---|---|---|
BeanFactory |
最基础的 IoC 容器 | 能“存对象”的基础仓库 |
ApplicationContext |
更常用、更高级的容器 | 不只是仓库,还是具备事件、国际化、AOP 整合等能力的完整运营中心 |
考试和实际开发里,你几乎都把 ApplicationContext 看成主流容器。
5.2.8 苏格拉底式问答:为什么 IoC 会让系统更好
问:如果
OrderService自己new OrderDaoImpl(),有什么问题?答:
OrderService被绑死在OrderDaoImpl上,想换成OrderDaoMock、OrderDaoJdbc、OrderDaoMyBatis都要改源码。
问:如果容器注入接口实现,会发生什么变化?
答:
OrderService只依赖抽象接口,不关心具体实现,耦合度就降下来了。
问:耦合度降下来,有什么实际价值?
答:更好测试、更容易扩展、更容易替换实现。
5.2.9 常见误区
-
误区 1:用了
@Autowired就等于理解了 IoC。不对。
@Autowired只是注入动作的入口,不是 IoC 全部。 -
误区 2:IoC 只是为了少写
new。不对。少写
new只是表面,真正价值是 解耦、可维护、可测试。 -
误区 3:接口多是形式主义。
不对。接口让容器可以在多个实现之间切换,这是解耦的重要前提。
5.2.10 简短总结
IoC/DI 是 Spring 最核心的根。后面你看到的 AOP、事务、MVC、Boot,最终都离不开“容器里有 Bean,Bean 之间能被正确管理和装配”。
5.3 模块 2:Bean 生命周期、作用域与配置深化
5.3.1 本模块解决的问题
IoC 解决了“谁创建对象”,但还没解决:
- 什么时候创建?
- 创建后会经历哪些阶段?
- 一个 Bean 是全局一个,还是每次都新建?
- 为什么有的 Bean 在启动时报错?
本模块就是在回答:
容器到底怎样把一个 Bean 从“类定义”变成“可用对象”。
5.3.2 核心概念
| 概念 | 专业术语/写法 | 通俗理解 |
|---|---|---|
| Bean Definition | Bean 定义信息 | 告诉容器“要创建谁、怎么创建”的档案 |
| Scope | singleton、prototype 等 |
这个对象是一份共用,还是按需多份 |
| Initialization | 初始化 | 对象创建后做收尾准备 |
| Destruction | 销毁 | 容器关闭前做资源释放 |
| BeanPostProcessor | Bean 后处理器 | 在 Bean 初始化前后插一手的扩展点 |
| Lazy | 延迟加载 | 不急着创建,真用到时再建 |
| Circular Dependency | 循环依赖 | A 依赖 B,B 又依赖 A |
5.3.3 Bean 生命周期主线
面试和考试最爱问的一条线就是 Bean 生命周期。
先记主干顺序:
读取 Bean 定义
↓
实例化 Bean
↓
属性注入
↓
Aware 回调
↓
BeanPostProcessor 前置处理
↓
初始化方法
↓
BeanPostProcessor 后置处理
↓
Bean 可用
↓
容器关闭时销毁
你不需要死背每个接口名字,但一定要抓住三个阶段:
- 创建出来
- 注入依赖并初始化好
- 在需要时被增强,在结束时被销毁
5.3.4 用生活类比理解 Bean 生命周期
把 Bean 想成一个新员工入职:
- 实例化:人先招进来。
- 依赖注入:给他电脑、工位、账号。
- 初始化:培训、开权限、加载资源。
- 后处理器增强:可能给他套上额外制度,比如代理、日志、事务。
- 正式上岗:开始处理工作。
- 销毁:离职时清理资源和连接。
5.3.5 作用域 Scope
| Scope | 含义 | 常见场景 | 易错点 |
|---|---|---|---|
singleton |
容器中默认单例 | Service、DAO、配置类 | 单例不等于线程安全 |
prototype |
每次获取都创建新对象 | 临时状态对象 | Spring 通常不负责其完整销毁 |
request |
每次 HTTP 请求一个 Bean | Web 请求级数据 | 仅 Web 环境可用 |
session |
每个 Session 一个 Bean | 会话级状态 | 不适合乱存大对象 |
application |
整个 ServletContext 共享 | Web 全局对象 | 现代业务代码中不算高频 |
最容易考的点:
Spring 默认是单例 Bean,但单例 Bean 并不自动线程安全。
原因是:
- “单例”只表示只有一个对象实例。
- “线程安全”表示多个线程并发访问不会出错。
- 如果单例 Bean 里保存了可变成员变量,它照样可能有并发问题。
5.3.6 常用注解配置
| 注解 | 作用 | 常见位置 |
|---|---|---|
@Component |
通用组件 | 任意组件类 |
@Service |
业务层组件 | Service 类 |
@Repository |
数据访问层组件 | DAO/Mapper 封装类 |
@Controller |
MVC 控制器 | 返回页面的控制器 |
@RestController |
REST 控制器 | 返回 JSON 的控制器 |
@Configuration |
配置类 | Java Config 类 |
@Bean |
把方法返回对象注册为 Bean | 配置类方法 |
@ComponentScan |
扫描组件 | 启动类或配置类 |
@Scope |
指定作用域 | Bean 定义处 |
@Lazy |
延迟初始化 | Bean 定义处 |
5.3.7 @Component 和 @Bean 的区别
这是高频混淆点。
| 对比项 | @Component |
@Bean |
|---|---|---|
| 放在哪里 | 类上 | 方法上 |
| 适合谁 | 你自己写的类 | 第三方类或需要手工构造的对象 |
| 注册方式 | 扫描发现 | 明确声明 |
| 控制度 | 相对自动 | 更细粒度 |
一句话:
@Component适合“这个类本来就是组件”;@Bean适合“这个对象需要我自己明确生产”。
5.3.8 自动装配规则
@Autowired 默认按 类型 注入。
如果同类型有多个 Bean,就可能报错,此时常用解决方案有:
@Qualifier("beanName")@Primary- 按名称注入
例如:
@Service
public class PayService {
private final PayStrategy payStrategy;
public PayService(@Qualifier("aliPayStrategy") PayStrategy payStrategy) {
this.payStrategy = payStrategy;
}
}
5.3.9 循环依赖怎么理解
循环依赖最常见的形式是:
A 依赖 B
B 依赖 A
它为什么危险?
- 因为 A 没创建完就需要 B
- B 也没创建完却又需要 A
- 容器会陷入“你等我、我等你”的困境
对初学者,记住这几个结论就够了:
- 循环依赖通常说明设计本身值得反思。
- 构造器注入下的循环依赖更容易直接暴露问题。
- 不要把“Spring 能不能兜底处理”当成设计合理。
5.3.10 常见误区
-
误区 1:单例 Bean 一定线程安全。
错。只要有共享可变状态,就可能不安全。
-
误区 2:
prototype一定更高级。错。大多数业务组件应该仍然是无状态单例。
-
误区 3:扫描不到 Bean 只是 IDE 问题。
错。常见原因是包路径不在扫描范围内,或者条件不满足。
5.3.11 简短总结
IoC 让容器“管对象”,Bean 生命周期则让你知道容器“到底怎么管”。
5.4 模块 3:AOP
5.4.1 本模块解决的问题
业务代码里经常有一些“所有方法都想加,但又不属于业务本身”的逻辑,比如:
- 日志记录
- 权限校验
- 性能统计
- 事务控制
- 审计追踪
如果把这些逻辑写进每个方法,代码会出现两个问题:
- 重复:很多地方都写类似代码。
- 污染:业务方法看起来不像业务,而像“业务 + 各种手续”。
于是 AOP 的问题意识是:
怎样在不改业务核心代码的前提下,把公共逻辑统一织进去?
5.4.2 先讲直觉:AOP 像“代理人”
把目标对象想成一个专家,真正擅长的是解决业务问题。
但专家每次出场前,可能都要:
- 登记
- 安检
- 录像
- 计时
- 记录结果
这些手续不应该由专家自己完成,而应该交给一个“代理人”包在外面处理。
这个“代理人”思想,就是 AOP 的直觉。
5.4.3 核心概念
| 概念 | 专业术语 | 通俗理解 |
|---|---|---|
| Target | 目标对象 | 真正做业务的人 |
| Proxy | 代理对象 | 替目标对象加手续的人 |
| Aspect | 切面 | 一组横切逻辑的打包方案 |
| Join Point | 连接点 | 可以被增强的位置,比如方法执行点 |
| Pointcut | 切点 | 从很多连接点里挑出哪些要增强 |
| Advice | 通知 | 具体增强动作,如前置、后置、环绕 |
| Weaving | 织入 | 把增强逻辑套到目标对象上的过程 |
5.4.4 Spring AOP 最常见的几种通知
| 通知类型 | 注解 | 含义 | 适合场景 |
|---|---|---|---|
| 前置通知 | @Before |
方法执行前做什么 | 参数校验、日志打点 |
| 后置通知 | @After |
方法结束后做什么 | 收尾记录 |
| 返回通知 | @AfterReturning |
正常返回后做什么 | 记录返回值 |
| 异常通知 | @AfterThrowing |
抛异常后做什么 | 异常日志、报警 |
| 环绕通知 | @Around |
自己包住整个方法 | 统计耗时、统一包装、最强大也最常用 |
5.4.5 一个最小例子
@Aspect
@Component
public class LogAspect {
@Around("execution(* com.demo.service..*(..))")
public Object log(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis();
try {
return pjp.proceed();
} finally {
long cost = System.currentTimeMillis() - start;
System.out.println(pjp.getSignature() + " cost = " + cost + "ms");
}
}
}
这段代码表达的意思是:
- 拦截
service包下的方法 - 调用前后做增强
- 业务代码本身不用改
5.4.6 AOP 为什么依赖代理
问:Spring 又没有改你源码,它怎么做到“调用前先执行增强逻辑”的?
答:因为真正暴露给外界的,往往不是目标对象本体,而是它的代理对象。
当你调用方法时,实际流程是:
调用方
↓
代理对象
↓ 先做日志/事务/权限等增强
目标对象
↓ 执行业务
返回结果
所以 AOP 不是“魔法插入”,而是“代理包裹”。
5.4.7 JDK 动态代理和 CGLIB
| 方式 | 适用情况 | 核心特点 |
|---|---|---|
| JDK 动态代理 | 目标类实现了接口 | 基于接口生成代理 |
| CGLIB | 目标类没有接口时常用 | 通过继承目标类生成代理 |
你不必一开始深究字节码细节,但一定要知道:
Spring AOP 的增强,本质是代理对象在起作用。
5.4.8 AOP 的典型价值
| 场景 | 如果不用 AOP | 用了 AOP 后 |
|---|---|---|
| 日志 | 每个方法手写日志 | 统一切面记录 |
| 事务 | 每个方法手动开启/提交/回滚 | @Transactional 自动处理 |
| 权限 | 每个方法手动判断 | 切面统一校验 |
| 性能统计 | 每个方法手动计算耗时 | 环绕通知统一统计 |
5.4.9 最容易出错的地方
问题一:同类内部调用,AOP 可能失效
例如:
public class UserService {
public void a() {
b();
}
@Transactional
public void b() {
}
}
为什么 b() 上的事务可能不生效?
因为 a() 里是 对象内部直接调用,没有经过代理对象。
要记住:
Spring AOP 的增强通常发生在“通过代理对象从外部进入”时。
问题二:不是所有方法都适合 AOP
private方法final方法- 容器没托管的对象方法
这些场景下,增强往往不能按你预期工作。
5.4.10 常见误区
-
误区 1:AOP 是面试概念,项目里没用。
错。事务、日志、权限、监控,项目里都是真实在用。
-
误区 2:AOP 会把业务逻辑变复杂,所以最好别用。
不对。AOP 的目标恰恰是把横切逻辑从业务中剥出去。
-
误区 3:有注解就一定生效。
不对。还得看对象是否由 Spring 管理、调用是否经过代理、方法是否满足增强条件。
5.4.11 简短总结
AOP 的本质是:让公共逻辑不进入业务方法内部,而是由代理统一包裹。事务就是它最经典的应用。
5.5 模块 4:数据访问整合与事务
5.5.1 本模块解决的问题
数据库操作真正难的,不只是“查得到”,而是:
- 多步操作如何保持一致?
- 中途报错怎么办?
- 读脏数据、不可重复读、幻读怎么理解?
- 为什么事务有时会失效?
所以本模块的核心问题是:
当业务跨越多个数据库操作时,如何保证数据正确、一致、可回滚。
5.5.2 Spring 在数据访问层到底干什么
要先分清一件事:
- Spring 不是 SQL 本身
- Spring 也不是 ORM 本身
- Spring 的强项是整合和事务管理
也就是说:
- 你可以用
JdbcTemplate - 你可以整合 MyBatis
- 你可以整合 JPA
但无论你选哪种数据库访问技术,Spring 都能帮你:
- 管理数据源
- 管理 DAO / Service Bean
- 管理事务边界
5.5.3 事务为什么存在
经典转账例子:
- A 账户扣 100
- B 账户加 100
如果第一步成功、第二步失败,就会出大问题。
所以事务要保证:
要么两步都成功,要么两步都失败。
这就是事务最核心的意义。
5.5.4 ACID 四大特性
| 特性 | 含义 | 通俗理解 |
|---|---|---|
| Atomicity 原子性 | 要么全做,要么全不做 | 一整套动作不能只做一半 |
| Consistency 一致性 | 事务前后数据满足约束 | 做完后账还是对的 |
| Isolation 隔离性 | 多事务之间互不干扰到规定程度 | 多个人同时办业务,不该互相看乱了 |
| Durability 持久性 | 提交后结果应持久保存 | 一旦确认,断电也不能丢 |
5.5.5 @Transactional 的本质
@Transactional 不是给方法“贴个标签就自动神奇成功”,它背后做的是:
调用 Service 方法
↓
经过代理对象
↓
事务拦截器判断是否开启事务
↓
调用目标方法
↓
成功则提交,异常则按规则回滚
所以你要把它理解成:
声明式事务 = AOP 代理 + 事务管理器 + 回滚规则
5.5.6 为什么事务一般加在 Service 层
因为事务通常对应的是 一个完整业务动作,而不是某一条 SQL。
例如“下订单”这个业务,可能包含:
- 扣库存
- 写订单
- 写订单明细
- 记录操作日志
这几个动作合在一起,才构成一个完整业务单元,所以事务边界通常放在 Service 层最合理。
5.5.7 事务传播行为
传播行为解决的问题是:
一个带事务的方法调用另一个也带事务的方法时,事务关系怎么算?
最常考的是这些:
| 传播行为 | 含义 | 常见理解 |
|---|---|---|
REQUIRED |
有事务就加入,没有就新建 | 默认、最常用 |
REQUIRES_NEW |
不管外面有没有,都新开一个 | 内外事务相互独立 |
SUPPORTS |
有事务就加入,没有就非事务运行 | 可有可无地跟随 |
MANDATORY |
必须在事务中运行 | 没有事务就报错 |
NOT_SUPPORTED |
不在事务中运行 | 有事务也先挂起 |
NEVER |
绝不能在事务中运行 | 有事务就报错 |
NESTED |
嵌套事务 | 有保存点概念,理解层面知道即可 |
记忆技巧:
- REQUIRED:能蹭就蹭,不能蹭就自己开。
- REQUIRES_NEW:我不管你外面有没有,我自己单开。
5.5.8 隔离级别
隔离级别回答的问题是:
多个事务并发读写时,允许彼此“看到”多少。
| 隔离级别 | 能解决的问题 | 可能仍存在的问题 |
|---|---|---|
READ_UNCOMMITTED |
几乎不保证 | 脏读、不可重复读、幻读都可能有 |
READ_COMMITTED |
防脏读 | 仍可能不可重复读、幻读 |
REPEATABLE_READ |
防脏读、防不可重复读 | 幻读问题依数据库实现而异 |
SERIALIZABLE |
最严格 | 性能最差,并发最低 |
通俗理解:
- 隔离越高,数据越稳。
- 但隔离越高,并发性能通常越差。
5.5.9 回滚规则
Spring 默认规则里,最常见的考试点是:
默认对运行时异常
RuntimeException和Error回滚;对受检异常不一定默认回滚。
所以很多人会写:
@Transactional(rollbackFor = Exception.class)
public void createOrder() throws Exception {
}
它表达的是:
- 只要出现
Exception及其子类,都回滚
5.5.10 一个典型例子
@Service
public class TransferService {
@Transactional(rollbackFor = Exception.class)
public void transfer(Long fromId, Long toId, BigDecimal money) {
accountMapper.decrease(fromId, money);
accountMapper.increase(toId, money);
}
}
这段代码的业务意义是:
- 扣款和加款属于一个事务单元
- 中途任何一步抛异常,就整体回滚
5.5.11 事务失效的高频场景
这是面试最爱问的地方之一。
| 场景 | 为什么失效 |
|---|---|
| 同类内部方法直接调用 | 没经过代理对象 |
方法不是 public(常见代理场景下) |
代理增强受限 |
| Bean 不是 Spring 容器管理的 | 容器都没接管,自然无事务 |
| 捕获异常后不再抛出 | 代理看不到异常,就可能不回滚 |
| 数据库引擎不支持事务 | 框架想回滚也无能为力 |
| 配置了错误的事务管理器 | 事务边界没真正接上资源 |
5.5.12 readOnly、timeout、rollbackFor 怎么看
| 属性 | 作用 | 正确理解 |
|---|---|---|
readOnly = true |
表示以读为主 | 更像一种优化提示,不代表绝对不能写 |
timeout = 5 |
超时时间控制 | 防止事务长期占资源 |
rollbackFor = Exception.class |
指定回滚异常类型 | 显式兜住受检异常 |
5.5.13 常见误区
-
误区 1:加了
@Transactional就一定回滚。错。还要看代理、异常类型、调用方式、事务管理器是否生效。
-
误区 2:事务应该尽量加在 DAO 层。
错。事务通常对业务过程负责,所以一般放在 Service 层。
-
误区 3:隔离级别越高越好。
错。隔离级别是正确性和性能的权衡,不是越高越先进。
5.5.14 简短总结
Spring 事务的本质不是注解,而是“用 AOP 在业务方法边界统一管理数据库操作的一致性”。
5.6 模块 5:Spring MVC
5.6.1 本模块解决的问题
Web 开发里最核心的问题是:
浏览器发来的 HTTP 请求,怎样被准确地接收、分发、处理、返回?
如果没有统一框架,这些工作都要自己写:
- URL 匹配
- 参数解析
- 调方法
- 返回页面或 JSON
- 异常处理
Spring MVC 的价值,就是把这套流程标准化。
5.6.2 先讲直觉:MVC 在做什么
MVC 可以先从角色分工理解:
| 角色 | 含义 | 通俗理解 |
|---|---|---|
| Model | 数据模型 | 业务数据本身 |
| View | 视图 | 页面、展示结果 |
| Controller | 控制器 | 接收请求并协调业务处理 |
在现代前后端分离项目里,很多时候 View 不在服务端渲染,而是直接返回 JSON,所以你会更常看到:
Controller 负责接请求,Service 负责做业务,最终返回 JSON。
5.6.3 Spring MVC 的完整请求流程
这是必须掌握的主线。
客户端发送 HTTP 请求
↓
DispatcherServlet(前端控制器)统一接收
↓
HandlerMapping 找到哪个 Controller 方法能处理
↓
HandlerAdapter 调用目标方法
↓
Controller 执行业务(通常再调用 Service)
↓
返回 ModelAndView 或数据对象
↓
ViewResolver 解析视图(返回页面时)
↓
或 HttpMessageConverter 转为 JSON(返回数据时)
↓
响应客户端
最核心的一句话:
DispatcherServlet 是 Spring MVC 的总调度入口。
5.6.4 核心概念
| 概念 | 专业术语/写法 | 通俗理解 |
|---|---|---|
| 前端控制器 | DispatcherServlet |
所有请求先到总前台 |
| 处理器映射器 | HandlerMapping |
查“这个请求该谁处理” |
| 处理器适配器 | HandlerAdapter |
按合适方式去调用目标方法 |
| 控制器 | @Controller / @RestController |
真正接业务请求的入口类 |
| 视图解析器 | ViewResolver |
决定返回哪个页面模板 |
| 消息转换器 | HttpMessageConverter |
把对象转成 JSON/XML 等响应体 |
| 拦截器 | HandlerInterceptor |
在请求处理前后做通用处理 |
5.6.5 最常用注解
| 注解 | 作用 | 示例 |
|---|---|---|
@RequestMapping |
通用请求映射 | 类或方法级路径映射 |
@GetMapping |
处理 GET | 查询接口 |
@PostMapping |
处理 POST | 新增接口 |
@PathVariable |
取路径参数 | /users/{id} |
@RequestParam |
取查询参数 | ?page=1 |
@RequestBody |
接收请求体 JSON | POST/PUT JSON 接口 |
@ResponseBody |
返回响应体 | 返回 JSON |
@RestController |
@Controller + @ResponseBody |
REST 风格接口 |
@ExceptionHandler |
统一异常处理 | 控制器级异常处理 |
@ControllerAdvice |
全局增强 | 全局异常、绑定、数据处理 |
5.6.6 一个最常见接口例子
@RestController
@RequestMapping("/users")
public class UserController {
@GetMapping("/{id}")
public UserVO getById(@PathVariable Long id) {
return userService.getById(id);
}
@PostMapping
public String create(@RequestBody UserCreateDTO dto) {
userService.create(dto);
return "ok";
}
}
这段代码说明了三件事:
/users/{id}的 GET 请求映射到getById- 请求路径里的
id被绑定到方法参数 - 返回的对象会被转成响应数据返回
5.6.7 @Controller 和 @RestController 的区别
| 对比项 | @Controller |
@RestController |
|---|---|---|
| 默认语义 | 返回视图 | 返回响应体数据 |
| 常见场景 | 服务端页面渲染 | 前后端分离接口 |
是否等价于 @ResponseBody |
否 | 是 |
一句话:
想返回页面,多半用
@Controller;想返回 JSON,多半用@RestController。
5.6.8 参数绑定怎么理解
HTTP 请求的数据可能来自不同位置:
| 数据来源 | 常用注解 | 例子 |
|---|---|---|
| 路径 | @PathVariable |
/users/10 |
| 查询字符串 | @RequestParam |
/users?page=1 |
| 请求体 JSON | @RequestBody |
{"name":"Tom"} |
| 表单参数 | 可直接接收或配合注解 | 表单提交 |
理解这个表之后,参数绑定基本不会乱。
5.6.9 拦截器、过滤器、AOP 的区别
这是一个非常经典的对比题。
| 技术 | 作用层次 | 典型场景 |
|---|---|---|
| Filter | Servlet 规范层 | 编码处理、跨域、底层过滤 |
| Interceptor | Spring MVC 层 | 登录校验、请求日志、接口耗时 |
| AOP | Spring Bean 方法层 | 事务、Service 日志、方法增强 |
记忆口诀:
- Filter 管请求进入 Web 容器
- Interceptor 管 Spring MVC 请求流程
- AOP 管 Bean 方法执行
5.6.10 异常处理为什么要统一做
如果每个接口都自己写:
- try
- catch
- 返回错误码
那么控制器会非常乱。
所以常见做法是:
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public Map<String, Object> handle(Exception e) {
return Map.of("code", 500, "msg", e.getMessage());
}
}
这体现的是:
把重复的错误处理从每个 Controller 方法里抽出来,统一管理。
5.6.11 常见误区
-
误区 1:Spring MVC 只是写接口注解。
错。真正核心是理解请求调度链。
-
误区 2:
@RestController返回对象时,Spring 会自动“猜”怎么转。不完全对。背后依赖消息转换器。
-
误区 3:Controller 可以直接写全部业务。
错。Controller 负责接请求,不应承担复杂业务决策。
5.6.12 简短总结
Spring MVC 的本质,是把 HTTP 请求从“手工处理”变成“统一入口、统一分发、统一返回”的标准流程。
5.7 模块 6:Spring Boot
5.7.1 本模块解决的问题
学完 Spring、MVC、事务之后,很多人会发现:
- 能用是能用
- 但配置很繁琐
- 起一个项目要配很多 XML 和基础设施
Spring Boot 要解决的问题就是:
怎样让一个 Spring 项目尽量少写配置、快速启动、开箱即用。
5.7.2 先讲直觉:Boot 为什么能省事
传统 Spring 项目常常要手工配置:
- 扫描路径
- DispatcherServlet
- 视图解析器
- 数据源
- JSON 转换器
- Tomcat
- 各种基础 Bean
而 Boot 的思路是:
如果你引入了某类依赖,而且当前环境满足条件,我就默认帮你把常见配置做好。
这就叫 约定优于配置。
5.7.3 核心概念
| 概念 | 专业术语/写法 | 通俗理解 |
|---|---|---|
| Starter | spring-boot-starter-* |
一组常用依赖打包套餐 |
| Auto Configuration | 自动配置 | 条件满足时自动帮你装好 Bean |
| Embedded Server | 内嵌服务器 | 应用自己带 Tomcat/Jetty/Undertow |
| Externalized Configuration | 外部化配置 | 配置写在 application.yml 等外部文件里 |
| Profile | 环境配置 | 开发、测试、生产用不同配置 |
| Actuator | 监控运维组件 | 暴露健康检查、指标信息 |
5.7.4 一个最小 Spring Boot 启动类
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
@SpringBootApplication 常被视为三个核心能力的组合:
@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan
不要求你第一天就会背全,但一定要知道它至少做了三件事:
- 声明这是配置入口
- 开启自动配置
- 开始组件扫描
5.7.5 自动配置的因果逻辑
很多人会问:
“为什么我只加了
starter-web,就能直接写接口了?”
因为背后的逻辑通常是:
你引入了 spring-boot-starter-web
↓
相关 Web 依赖进入项目
↓
自动配置类发现“Web 条件满足”
↓
自动注册 DispatcherServlet、消息转换器、Tomcat 等常见组件
↓
你只需要写 Controller
所以你可以把 Boot 的自动配置记成一个简单公式:
Boot 运行效果 = Starter 依赖 + 条件装配 + 默认配置 + 你的显式覆盖
5.7.6 application.yml 的作用
Spring Boot 强调外部化配置,典型写法:
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://localhost:3306/demo
username: root
password: 123456
它的作用是:
- 不把配置写死在代码里
- 环境变了,改配置即可
- 便于开发、测试、生产切换
5.7.7 Profile 怎么理解
不同环境通常配置不同:
- 开发环境:本地数据库、日志详细
- 测试环境:联调地址、测试账号
- 生产环境:正式数据库、日志收敛
所以有了 Profile:
spring:
profiles:
active: dev
你要记住它的本质:
Profile 是让同一套程序,在不同运行环境下加载不同配置。
5.7.8 Starter 为什么重要
假设你要做 Web 项目。
如果没有 Starter,你需要自己找:
- Spring MVC 依赖
- JSON 依赖
- Tomcat 依赖
- 日志依赖
- 版本兼容
有了 Starter 后:
- 引一组约定好的依赖
- 大量减少版本冲突和手工选择成本
这就是 Boot 的工程价值。
5.7.9 Boot 和 Spring 的关系
这题非常高频。
| 对比项 | Spring | Spring Boot |
|---|---|---|
| 关注点 | 核心框架能力 | 快速装配和工程启动 |
| 重点 | IoC、AOP、事务、MVC | 自动配置、Starter、快速运行 |
| 关系 | 基础 | 在基础上做工程化增强 |
一句话:
Spring Boot 不是替代 Spring,而是让你更高效地使用 Spring。
5.7.10 常见误区
-
误区 1:Boot 不需要懂 Spring。
错。不会 Spring,Boot 只是“能跑但不会排错”。
-
误区 2:自动配置意味着不能自定义。
错。自动配置只是默认值,你仍然可以覆盖或排除。
-
误区 3:Starter 越多越好。
错。依赖越多,自动配置越复杂,启动和维护成本也可能更高。
5.7.11 简短总结
Spring Boot 的本质是让 Spring 项目“少配置、快启动、好维护”,但底层核心仍然是 Spring 容器和自动装配机制。
5.8 模块 7:工程实践、测试与排错
5.8.1 本模块解决的问题
很多同学学到这里会出现一个断层:
- 知识点听懂了
- 注解也会写了
- 但一进项目就乱
原因是没把 Spring 当成“工程组织系统”来理解。
本模块解决的是:
怎样把前面的知识落到一个可运行、可测试、可排错的项目里。
5.8.2 推荐的项目分层
最常见、最稳妥的分层是:
Controller:接请求、做参数校验和结果返回
Service:写业务逻辑、控制事务边界
DAO / Mapper:和数据库交互
Config:配置类
Entity / DTO / VO:数据对象
每层各司其职,最重要的因果关系是:
- Controller 不应塞业务细节
- Service 不应直接承担 HTTP 细节
- DAO 不应承担业务编排
5.8.3 常见测试方式
| 测试方式 | 常见注解/工具 | 适用场景 |
|---|---|---|
| 单元测试 | JUnit、Mock | 测某个类逻辑 |
| Spring 集成测试 | @SpringBootTest |
测容器、配置、依赖注入是否正常 |
| MVC 测试 | MockMvc |
测接口映射、参数、返回 |
5.8.4 @SpringBootTest 是干什么的
它会尽可能把 Spring Boot 上下文启动起来,让你在测试环境中验证:
- Bean 能否正确注入
- 配置是否正确
- 业务链路能否跑通
但也要知道:
- 它通常比纯单元测试更重
- 启动更慢
- 不适合滥用到每个小方法测试
5.8.5 常见排错路径
场景一:启动时报 Bean 找不到
优先检查:
- 类是否在扫描路径下
- 是否加了组件注解或
@Bean - 是否有多个同类型 Bean 冲突
- 条件装配是否未满足
场景二:事务不生效
优先检查:
- 方法调用是否经过代理
- 是否是 Spring 管理的 Bean
- 异常是否被吞掉
- 事务注解是否放在合理层次
场景三:接口返回 404 / 400 / 500
优先理解:
- 404:通常是路径没匹配到
- 400:通常是参数绑定或请求格式不对
- 500:通常是服务端代码异常
场景四:自动配置不符合预期
优先检查:
- 引入了什么 Starter
- 哪些条件成立了
- 是否被你自己的 Bean 覆盖
- 配置项是否写对
5.8.6 面试和考试的高频主线
如果只能抓最值钱的内容,优先掌握这些:
- Spring 的核心是什么:IoC + AOP
- Bean 生命周期
@Autowired按什么装配@Component和@Bean的区别- AOP 原理:代理
- 事务原理与失效场景
- Spring MVC 请求流程
- Spring Boot 自动配置与 Starter
5.8.7 简短总结
Spring 学得会不算过关,能把它落进分层、测试、排错、面试表达里,才算真正掌握。
5.9 模块 8:Spring 的边界与后续扩展
5.9.1 这一模块解决的问题
很多小白会把 Spring 学成“无限大”,什么都往里装,最后边界模糊。
这一节的作用就是告诉你:
哪些是 Spring 主干内容,哪些是下一阶段再学。
5.9.2 主干和扩展怎么划分
| 层次 | 内容 |
|---|---|
| Spring 主干 | IoC、DI、AOP、事务、MVC、Boot |
| 常见整合 | MyBatis、Redis、消息队列、日志、校验 |
| 安全体系 | Spring Security |
| 分布式体系 | Spring Cloud |
| 数据访问进阶 | JPA、事务传播深层细节、多数据源 |
5.9.3 学习边界建议
如果你现在是小白,不要一开始就跳到:
- 微服务
- 网关
- 分布式事务
- OAuth2
- 注册中心
因为如果 IoC、AOP、事务、MVC 都不牢,后面会像在沙地上盖楼。
5.9.4 简短总结
先把 Spring 主干学透,再扩展到 Security、Cloud、微服务,学习效率最高。
6. 学习路径
6.1 快速入门阶段
学习目标
- 建立 Spring 整体框架
- 知道 Spring 解决什么问题
- 能分清 IoC、AOP、MVC、Boot 各自职责
学习内容
- Spring 的整体定位
- IoC 与 DI 基础
- Bean、容器、注解的基本概念
- MVC 请求流转总览
- Boot 的整体作用
推荐时间分配
- 2 到 4 天
- 每天 1.5 到 3 小时
产出结果
- 能口头讲清楚 Spring 主线
- 能画出 Spring 知识地图
- 能说出
IoC -> AOP -> 事务 -> MVC -> Boot的因果关系
自测标准
如果你能在不看资料的情况下回答下面问题,说明入门过关:
- Spring 为什么会出现?
- 什么是 IoC?什么是 DI?
- 什么是 Bean?
- AOP 在解决什么问题?
- Boot 和 Spring 是什么关系?
6.2 核心掌握阶段
学习目标
- 把最核心的高频内容真正理解
- 能解释关键原理
- 能写出基本项目结构
学习内容
- IoC 容器与自动装配
- Bean 生命周期与作用域
- AOP 代理机制
- 声明式事务
- Spring MVC 核心流程
- Boot 自动配置与 Starter
推荐时间分配
- 7 到 14 天
- 每天 2 到 4 小时
产出结果
- 能独立写出 Controller / Service / DAO 分层
- 能解释事务为什么失效
- 能看懂常见 Spring Boot 项目结构
- 能把主要注解按类别归类理解,而不是死背
自测标准
你至少应该能完成这些任务:
- 写一个最小的 Boot Web 项目
- 写一个带
@Transactional的 Service 方法 - 解释
@Component和@Bean的区别 - 解释
@Controller和@RestController的区别 - 画出 MVC 请求流程图
6.3 刷题巩固阶段
学习目标
- 把知识转成考试得分与面试表达
- 强化高频易错点
学习内容
- Bean 生命周期题
- AOP 概念题
- 事务传播、隔离级别题
- Spring MVC 流程题
- Boot 自动配置与 Starter 题
- 注解辨析题、易混对比题
推荐时间分配
- 5 到 10 天
- 每天 1.5 到 3 小时
产出结果
- 形成自己的高频题模板答案
- 能快速定位易混概念差异
- 能把原理题说成有逻辑的完整答案
自测标准
如果你能做到下面这些,说明刷题有效:
- 看到“事务失效”题,能直接列出 5 个高频原因
- 看到“Spring MVC 工作流程”题,能按顺序写出关键组件
- 看到“单例 Bean 是否线程安全”题,不会答错
- 看到“Boot 为什么简化开发”题,能答出 Starter + 自动配置 + 内嵌服务器
6.4 考前冲刺阶段
学习目标
- 查漏补缺
- 压缩记忆
- 把易混点和高频题彻底打稳
学习内容
- 一页纸速查表反复过
- 高频概念表背关键词
- 高频规则/方法表背触发条件与适用场景
- 自测题和错题回看
推荐时间分配
- 2 到 4 天
- 每天 1 到 2 小时高强度复盘
产出结果
- 一套自己的“考前口袋笔记”
- 一套能快速复述的主线答案
自测标准
考前最后一遍,至少要能脱稿回答:
- Spring 核心是什么?
- IoC 和 AOP 分别解决什么问题?
- 事务为什么依赖 AOP?
- MVC 的统一入口是谁?
- Boot 的自动配置是怎么触发的?
7. 一页纸速查表
| 主题 | 最关键一句话 |
|---|---|
| Spring 总定位 | 管对象、做解耦、织入横切、承接请求、快速装配 |
| Spring 核心 | IoC + AOP |
| IoC | 对象交给容器创建和管理 |
| DI | 容器把依赖注入给目标对象 |
| Bean | 被 Spring 管理的对象 |
| 容器 | 常见主力是 ApplicationContext |
| 单例 Bean | 默认作用域,但不代表线程安全 |
| AOP | 用代理给业务方法统一增强 |
| 事务 | AOP 的典型应用,控制提交和回滚 |
| 事务放哪层 | 一般放 Service 层 |
| MVC 统一入口 | DispatcherServlet |
@Controller |
常用于页面控制器 |
@RestController |
常用于返回 JSON 的接口控制器 |
| Boot 作用 | 快速启动、自动配置、约定优于配置 |
| Starter | 一组常用依赖打包套餐 |
| 自动配置 | 条件满足时自动往容器注册 Bean |
| 排错主线 | 先看“Bean 有没有被容器管理、调用有没有经过代理、路径有没有匹配” |
8. 高频概念表
| 概念 | 一句话解释 | 高频考法 |
|---|---|---|
| IoC | 控制权交给容器 | 问本质、问作用 |
| DI | 容器注入依赖 | 问注入方式、优缺点 |
| Bean | 容器管理对象 | 问作用域、生命周期 |
| ApplicationContext | 高级容器 | 问和 BeanFactory 区别 |
| AOP | 横切逻辑统一增强 | 问原理、问使用场景 |
| Proxy | 代理对象 | 问事务/AOP 为什么能生效 |
| Advice | 增强动作 | 问几种通知区别 |
| Transaction | 一组要么全成功要么全失败的操作 | 问 ACID、传播、隔离 |
| Propagation | 多事务调用关系规则 | 问 REQUIRED 和 REQUIRES_NEW |
| Isolation | 并发事务隔离程度 | 问脏读、不可重复读、幻读 |
| DispatcherServlet | MVC 前端控制器 | 问请求流程 |
| HttpMessageConverter | 消息转换器 | 问对象为什么能转 JSON |
| Starter | 依赖套餐 | 问 Boot 为什么方便 |
| AutoConfiguration | 自动配置 | 问 Boot 原理 |
| Profile | 多环境配置 | 问 dev/test/prod 切换 |
9. 高频规则 / 方法表
| 主题 | 规则 / 方法 | 适用条件 | 常见错误 |
|---|---|---|---|
| 依赖注入 | 优先使用构造器注入 | 依赖是必需的 | 滥用字段注入 |
| Bean 注册 | 自己写的组件用 @Component 系列;第三方对象常用 @Bean |
视对象来源决定 | 两者混为一谈 |
| Bean 作用域 | 默认 singleton |
多数无状态业务组件 | 误以为单例必线程安全 |
| AOP 增强 | 必须经过代理对象 | Spring 托管 Bean 的外部调用 | 同类内部直接调用失效 |
| 事务回滚 | 默认主要对运行时异常回滚 | 未显式指定 rollbackFor 时 |
以为所有异常都回滚 |
| 事务边界 | 放在 Service 层最常见 | 一个完整业务动作包含多步操作 | 放到 Controller 或 DAO 导致边界混乱 |
| MVC 路由 | 请求路径要与映射注解一致 | URL、方法、参数都匹配 | 把 404 当成业务异常 |
| JSON 绑定 | 用 @RequestBody 接请求体 |
前端传 JSON | 把 JSON 当查询参数接 |
| Boot 配置 | application.yml 管外部配置 |
环境切换、参数变更 | 配置写进代码里 |
| Profile 切换 | 按环境激活不同配置 | dev/test/prod | 一个配置文件硬撑所有环境 |
10. 易混知识点对比表
| 易混点 | A | B | 核心区别 |
|---|---|---|---|
| IoC vs DI | IoC 是思想 | DI 是实现方式之一 | 一个偏理念,一个偏落地 |
BeanFactory vs ApplicationContext |
基础容器 | 高级容器 | 后者功能更完整、开发更常用 |
@Component vs @Bean |
扫描注册类 | 显式注册方法返回对象 | 使用场景不同 |
@Autowired vs @Resource |
常按类型注入 | 常按名称语义理解 | 实际开发里都能用,但理解重点不同 |
singleton vs prototype |
容器通常一份 | 每次获取新建 | 不是“高级低级”关系 |
| AOP vs Interceptor | Bean 方法级增强 | MVC 请求级增强 | 作用层次不同 |
| Filter vs Interceptor | Servlet 层 | Spring MVC 层 | 所在层次不同 |
@Controller vs @RestController |
偏视图 | 偏 JSON 响应 | 返回语义不同 |
| Spring vs Spring Boot | 核心框架 | 工程化增强 | Boot 建立在 Spring 之上 |
| 事务传播 vs 隔离级别 | 解决事务嵌套关系 | 解决并发可见性问题 | 关注点不同 |
11. 典型题型与解法表
| 题型 | 出题方式 | 作答思路 |
|---|---|---|
| 概念解释题 | 什么是 IoC / AOP / Bean | 先说解决的问题,再说定义,再说价值 |
| 对比题 | @Component 和 @Bean 区别 |
从位置、注册方式、适用场景三个角度答 |
| 原理题 | 事务为什么能生效 | 先说 AOP 代理,再说事务拦截器,再说提交回滚 |
| 流程题 | Spring MVC 工作流程 | 按 DispatcherServlet -> HandlerMapping -> Controller -> 返回响应 顺序答 |
| 易错题 | 单例 Bean 是否线程安全 | 先说“不一定”,再解释单例和线程安全不是一回事 |
| 故障题 | 事务为什么失效 | 列举代理未生效、异常被吞、非 Spring Bean、内部调用等原因 |
| 工程题 | 为什么 Boot 开发更高效 | 说 Starter、自动配置、内嵌服务器、外部化配置 |
| 场景题 | 哪层该加事务 | 说事务边界属于业务过程,通常放在 Service 层 |
12. 自测题
12.1 选择 / 简答题
- Spring 的两个最核心能力是什么?
- 为什么说 IoC 的本质不是“少写
new”,而是“控制权转移”? @Component和@Bean的适用场景分别是什么?- 单例 Bean 一定线程安全吗?为什么?
- AOP 为什么能在不改业务代码的情况下增强方法?
- 为什么声明式事务通常依赖代理?
REQUIRED和REQUIRES_NEW的关键区别是什么?- 为什么事务通常放在 Service 层,而不是 Controller 层?
- Spring MVC 的统一入口组件是谁?
@Controller和@RestController的核心区别是什么?- Spring Boot 为什么能减少大量配置?
- 为什么“同类内部方法调用”常常会导致事务或 AOP 失效?
12.2 场景题
- 有一个下单方法,里面依次执行“扣库存、写订单、记日志”。如果第二步失败,第一步不能保留结果。你会把事务放在哪一层?为什么?
- 某个接口地址写成了
GET /users/1,但项目返回 404。你会按什么顺序排查? - 项目里有两个实现类都实现了
PayStrategy,此时@Autowired注入失败,你会怎么解决?
13. 自测题答案与解析
13.1 标准答案
1. Spring 的两个最核心能力是什么?
答案:IoC 和 AOP。
解析:IoC 负责对象管理和解耦,AOP 负责横切逻辑增强。
2. 为什么说 IoC 的本质不是“少写 new”,而是“控制权转移”?
答案:因为关键不在于语法上少写对象创建,而在于对象创建、依赖装配、生命周期管理的控制权从业务类转交给容器。
解析:少写 new 只是表面现象,真正价值是降低耦合、提升可维护性与可测试性。
3. @Component 和 @Bean 的适用场景分别是什么?
答案:@Component 适合直接标在你自己写的组件类上;@Bean 适合在配置类中手工注册对象,尤其适合第三方类或需要精细构造的对象。
解析:一个偏“自动扫描发现”,一个偏“显式声明生产”。
4. 单例 Bean 一定线程安全吗?为什么?
答案:不一定。
解析:单例只表示容器里通常只有一个实例,不代表它在多线程下没有共享状态问题。
5. AOP 为什么能在不改业务代码的情况下增强方法?
答案:因为 Spring 通过代理对象包裹目标对象,在方法调用前后执行增强逻辑。
解析:增强逻辑不一定写进原方法体内,而是由代理统一织入。
6. 为什么声明式事务通常依赖代理?
答案:因为事务开启、提交、回滚通常发生在方法调用边界,Spring 需要借助代理在调用前后拦截并处理事务。
解析:没有代理,就很难在不侵入业务代码的情况下统一管理事务。
7. REQUIRED 和 REQUIRES_NEW 的关键区别是什么?
答案:REQUIRED 是有事务就加入,没有才新建;REQUIRES_NEW 是无论外部有没有事务,都新开一个独立事务。
解析:前者倾向复用,后者强调隔离。
8. 为什么事务通常放在 Service 层,而不是 Controller 层?
答案:因为事务边界对应完整业务动作,而 Service 层最适合组织多个 DAO 操作形成一个业务单元。
解析:Controller 更关注请求接入,DAO 更关注单步数据库交互。
9. Spring MVC 的统一入口组件是谁?
答案:DispatcherServlet。
解析:所有请求先进入它,再由它分发到合适的处理器。
10. @Controller 和 @RestController 的核心区别是什么?
答案:@Controller 更偏向返回视图;@RestController 更偏向直接返回响应体数据,如 JSON。
解析:后者相当于默认带了 @ResponseBody 语义。
11. Spring Boot 为什么能减少大量配置?
答案:因为它通过 Starter、自动配置、内嵌服务器、外部化配置等机制,帮开发者完成大量默认装配。
解析:你只需要关注业务,基础设施由 Boot 优先帮你配好。
12. 为什么“同类内部方法调用”常常会导致事务或 AOP 失效?
答案:因为内部直接调用通常不会经过 Spring 生成的代理对象。
解析:AOP 和事务增强大多依赖“从代理进入目标方法”这一过程。
13. 下单方法事务放在哪一层?为什么?
答案:放在 Service 层。
解析:扣库存、写订单、记日志合起来才是一个完整业务动作,所以事务应该包住这个业务过程。
14. 接口返回 404,怎么排查?
答案:
- 检查请求路径和请求方法是否与注解匹配。
- 检查 Controller 是否被扫描到。
- 检查类上和方法上的
@RequestMapping是否组合正确。 - 检查应用是否启动成功、上下文路径是否变化。
解析:404 通常优先说明“路由没匹配到”,而不是业务执行错了。
15. PayStrategy 有多个实现导致注入失败,怎么处理?
答案:可以使用 @Qualifier 指定 Bean 名称,或用 @Primary 指定默认实现。
解析:因为 @Autowired 默认按类型装配,当同类型 Bean 不止一个时需要进一步消歧。
14. 最后总结
如果把 Spring 真正学明白,你脑子里应该留下的不是一堆注解,而是下面这条主线:
- Spring 先解决“对象该谁管理”,所以有 IoC / DI。
- 然后解决“公共逻辑别污染业务”,所以有 AOP。
- 再解决“多步数据库操作要一致”,所以有声明式事务。
- 再解决“HTTP 请求怎么统一接进系统”,所以有 Spring MVC。
- 最后解决“项目怎么快速装起来”,所以有 Spring Boot。
真正的高分掌握,不是会背几个注解,而是你能回答这五个问题:
- 为什么需要容器?
- 为什么事务依赖 AOP?
- 为什么 MVC 要有统一入口?
- 为什么 Boot 能减少配置?
- 为什么项目里要做分层?
当你能把这五个“为什么”讲通,Spring 就不再是一堆零散知识点,而是一套完整的工程框架。
最后送你一句最有用的学习判断标准:
如果你能从“问题是什么”一路讲到“Spring 用什么机制解决、为什么这样设计、容易在哪出错”,那你就是真的掌握了。
更多推荐

所有评论(0)