1. 学科一句话概括

Spring 是一套用来“管理对象、解耦模块、统一横切能力、承接 Web 请求、组织企业应用”的 Java 框架体系。

如果把一个 Java 后端项目比作一家公司:

  • Java 类 是员工。
  • 对象创建与依赖关系 是招聘和组织架构。
  • 日志、事务、权限 是所有部门都要遵守的共性制度。
  • HTTP 请求 是外部客户的需求单。
  • Spring 做的事,就是把这家公司从“各自为战”变成“有组织、有流程、可扩展”的系统。

一句话再压缩就是:

Spring 的本质,是把原本散落在代码各处的“对象管理、流程控制、公共能力”集中收拢,交给框架统一组织。


2. 先明确 Spring 在学什么

2.1 这门学科研究什么

Spring 研究的不是“某一个 API 怎么调”,而是:

  • Java 企业应用应该如何组织
  • 对象之间怎样解耦
  • 公共逻辑怎样复用
  • 请求怎样流转
  • 项目怎样快速启动、配置、扩展和维护

所以,Spring 的学习重点不是背注解,而是理解:

  1. 为什么要把对象交给容器管理。
  2. 为什么日志、事务、权限这些能力不应该写死在业务代码里。
  3. 为什么 Web 请求需要统一入口和统一调度。
  4. 为什么 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 看成“很多注解的堆积”,而要看成一套层层递进的工程答案:

  1. 先解决对象管理,于是有 IoC。
  2. 再解决公共逻辑复用,于是有 AOP。
  3. 再解决数据一致性,于是有事务。
  4. 再解决 Web 请求处理,于是有 MVC。
  5. 最后解决工程搭建效率,于是有 Boot。

这就是 Spring 最重要的因果顺序。


4. 推荐学习顺序

4.1 最适合小白的顺序

  1. 先理解 Spring 在解决什么工程问题
  2. 学习 IoC / DI
  3. 学习 Bean 生命周期、作用域、配置方式
  4. 学习 AOP
  5. 学习声明式事务
  6. 学习 Spring MVC
  7. 学习 Spring Boot 自动配置与项目结构
  8. 做一个完整的小项目并调试常见错误

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 依赖,会出现三个问题:

  1. 强耦合:换实现类要改源码。
  2. 难测试:很难注入假对象或 Mock。
  3. 难管理:对象创建时机、数量、配置散落各处。

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 容器管理的对象 被公司正式登记在册的员工
容器 BeanFactoryApplicationContext 管理所有 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 BeanFactoryApplicationContext

概念 说明 通俗理解
BeanFactory 最基础的 IoC 容器 能“存对象”的基础仓库
ApplicationContext 更常用、更高级的容器 不只是仓库,还是具备事件、国际化、AOP 整合等能力的完整运营中心

考试和实际开发里,你几乎都把 ApplicationContext 看成主流容器

5.2.8 苏格拉底式问答:为什么 IoC 会让系统更好

问:如果 OrderService 自己 new OrderDaoImpl(),有什么问题?

答:OrderService 被绑死在 OrderDaoImpl 上,想换成 OrderDaoMockOrderDaoJdbcOrderDaoMyBatis 都要改源码。

问:如果容器注入接口实现,会发生什么变化?

答: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 singletonprototype 这个对象是一份共用,还是按需多份
Initialization 初始化 对象创建后做收尾准备
Destruction 销毁 容器关闭前做资源释放
BeanPostProcessor Bean 后处理器 在 Bean 初始化前后插一手的扩展点
Lazy 延迟加载 不急着创建,真用到时再建
Circular Dependency 循环依赖 A 依赖 B,B 又依赖 A

5.3.3 Bean 生命周期主线

面试和考试最爱问的一条线就是 Bean 生命周期。

先记主干顺序:

读取 Bean 定义
    ↓
实例化 Bean
    ↓
属性注入
    ↓
Aware 回调
    ↓
BeanPostProcessor 前置处理
    ↓
初始化方法
    ↓
BeanPostProcessor 后置处理
    ↓
Bean 可用
    ↓
容器关闭时销毁

你不需要死背每个接口名字,但一定要抓住三个阶段:

  1. 创建出来
  2. 注入依赖并初始化好
  3. 在需要时被增强,在结束时被销毁

5.3.4 用生活类比理解 Bean 生命周期

把 Bean 想成一个新员工入职:

  1. 实例化:人先招进来。
  2. 依赖注入:给他电脑、工位、账号。
  3. 初始化:培训、开权限、加载资源。
  4. 后处理器增强:可能给他套上额外制度,比如代理、日志、事务。
  5. 正式上岗:开始处理工作。
  6. 销毁:离职时清理资源和连接。

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
  • 容器会陷入“你等我、我等你”的困境

对初学者,记住这几个结论就够了:

  1. 循环依赖通常说明设计本身值得反思。
  2. 构造器注入下的循环依赖更容易直接暴露问题。
  3. 不要把“Spring 能不能兜底处理”当成设计合理。

5.3.10 常见误区

  • 误区 1:单例 Bean 一定线程安全。

    错。只要有共享可变状态,就可能不安全。

  • 误区 2:prototype 一定更高级。

    错。大多数业务组件应该仍然是无状态单例。

  • 误区 3:扫描不到 Bean 只是 IDE 问题。

    错。常见原因是包路径不在扫描范围内,或者条件不满足。

5.3.11 简短总结

IoC 让容器“管对象”,Bean 生命周期则让你知道容器“到底怎么管”。


5.4 模块 3:AOP

5.4.1 本模块解决的问题

业务代码里经常有一些“所有方法都想加,但又不属于业务本身”的逻辑,比如:

  • 日志记录
  • 权限校验
  • 性能统计
  • 事务控制
  • 审计追踪

如果把这些逻辑写进每个方法,代码会出现两个问题:

  1. 重复:很多地方都写类似代码。
  2. 污染:业务方法看起来不像业务,而像“业务 + 各种手续”。

于是 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 事务为什么存在

经典转账例子:

  1. A 账户扣 100
  2. 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 默认规则里,最常见的考试点是:

默认对运行时异常 RuntimeExceptionError 回滚;对受检异常不一定默认回滚。

所以很多人会写:

@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 readOnlytimeoutrollbackFor 怎么看

属性 作用 正确理解
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";
    }
}

这段代码说明了三件事:

  1. /users/{id} 的 GET 请求映射到 getById
  2. 请求路径里的 id 被绑定到方法参数
  3. 返回的对象会被转成响应数据返回

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

不要求你第一天就会背全,但一定要知道它至少做了三件事:

  1. 声明这是配置入口
  2. 开启自动配置
  3. 开始组件扫描

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 找不到

优先检查:

  1. 类是否在扫描路径下
  2. 是否加了组件注解或 @Bean
  3. 是否有多个同类型 Bean 冲突
  4. 条件装配是否未满足
场景二:事务不生效

优先检查:

  1. 方法调用是否经过代理
  2. 是否是 Spring 管理的 Bean
  3. 异常是否被吞掉
  4. 事务注解是否放在合理层次
场景三:接口返回 404 / 400 / 500

优先理解:

  • 404:通常是路径没匹配到
  • 400:通常是参数绑定或请求格式不对
  • 500:通常是服务端代码异常
场景四:自动配置不符合预期

优先检查:

  1. 引入了什么 Starter
  2. 哪些条件成立了
  3. 是否被你自己的 Bean 覆盖
  4. 配置项是否写对

5.8.6 面试和考试的高频主线

如果只能抓最值钱的内容,优先掌握这些:

  1. Spring 的核心是什么:IoC + AOP
  2. Bean 生命周期
  3. @Autowired 按什么装配
  4. @Component@Bean 的区别
  5. AOP 原理:代理
  6. 事务原理与失效场景
  7. Spring MVC 请求流程
  8. 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 的因果关系

自测标准

如果你能在不看资料的情况下回答下面问题,说明入门过关:

  1. Spring 为什么会出现?
  2. 什么是 IoC?什么是 DI?
  3. 什么是 Bean?
  4. AOP 在解决什么问题?
  5. Boot 和 Spring 是什么关系?

6.2 核心掌握阶段

学习目标

  • 把最核心的高频内容真正理解
  • 能解释关键原理
  • 能写出基本项目结构

学习内容

  • IoC 容器与自动装配
  • Bean 生命周期与作用域
  • AOP 代理机制
  • 声明式事务
  • Spring MVC 核心流程
  • Boot 自动配置与 Starter

推荐时间分配

  • 7 到 14 天
  • 每天 2 到 4 小时

产出结果

  • 能独立写出 Controller / Service / DAO 分层
  • 能解释事务为什么失效
  • 能看懂常见 Spring Boot 项目结构
  • 能把主要注解按类别归类理解,而不是死背

自测标准

你至少应该能完成这些任务:

  1. 写一个最小的 Boot Web 项目
  2. 写一个带 @Transactional 的 Service 方法
  3. 解释 @Component@Bean 的区别
  4. 解释 @Controller@RestController 的区别
  5. 画出 MVC 请求流程图

6.3 刷题巩固阶段

学习目标

  • 把知识转成考试得分与面试表达
  • 强化高频易错点

学习内容

  • Bean 生命周期题
  • AOP 概念题
  • 事务传播、隔离级别题
  • Spring MVC 流程题
  • Boot 自动配置与 Starter 题
  • 注解辨析题、易混对比题

推荐时间分配

  • 5 到 10 天
  • 每天 1.5 到 3 小时

产出结果

  • 形成自己的高频题模板答案
  • 能快速定位易混概念差异
  • 能把原理题说成有逻辑的完整答案

自测标准

如果你能做到下面这些,说明刷题有效:

  1. 看到“事务失效”题,能直接列出 5 个高频原因
  2. 看到“Spring MVC 工作流程”题,能按顺序写出关键组件
  3. 看到“单例 Bean 是否线程安全”题,不会答错
  4. 看到“Boot 为什么简化开发”题,能答出 Starter + 自动配置 + 内嵌服务器

6.4 考前冲刺阶段

学习目标

  • 查漏补缺
  • 压缩记忆
  • 把易混点和高频题彻底打稳

学习内容

  • 一页纸速查表反复过
  • 高频概念表背关键词
  • 高频规则/方法表背触发条件与适用场景
  • 自测题和错题回看

推荐时间分配

  • 2 到 4 天
  • 每天 1 到 2 小时高强度复盘

产出结果

  • 一套自己的“考前口袋笔记”
  • 一套能快速复述的主线答案

自测标准

考前最后一遍,至少要能脱稿回答:

  1. Spring 核心是什么?
  2. IoC 和 AOP 分别解决什么问题?
  3. 事务为什么依赖 AOP?
  4. MVC 的统一入口是谁?
  5. 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 多事务调用关系规则 REQUIREDREQUIRES_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 选择 / 简答题

  1. Spring 的两个最核心能力是什么?
  2. 为什么说 IoC 的本质不是“少写 new”,而是“控制权转移”?
  3. @Component@Bean 的适用场景分别是什么?
  4. 单例 Bean 一定线程安全吗?为什么?
  5. AOP 为什么能在不改业务代码的情况下增强方法?
  6. 为什么声明式事务通常依赖代理?
  7. REQUIREDREQUIRES_NEW 的关键区别是什么?
  8. 为什么事务通常放在 Service 层,而不是 Controller 层?
  9. Spring MVC 的统一入口组件是谁?
  10. @Controller@RestController 的核心区别是什么?
  11. Spring Boot 为什么能减少大量配置?
  12. 为什么“同类内部方法调用”常常会导致事务或 AOP 失效?

12.2 场景题

  1. 有一个下单方法,里面依次执行“扣库存、写订单、记日志”。如果第二步失败,第一步不能保留结果。你会把事务放在哪一层?为什么?
  2. 某个接口地址写成了 GET /users/1,但项目返回 404。你会按什么顺序排查?
  3. 项目里有两个实现类都实现了 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. REQUIREDREQUIRES_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,怎么排查?
答案:

  1. 检查请求路径和请求方法是否与注解匹配。
  2. 检查 Controller 是否被扫描到。
  3. 检查类上和方法上的 @RequestMapping 是否组合正确。
  4. 检查应用是否启动成功、上下文路径是否变化。
    解析:404 通常优先说明“路由没匹配到”,而不是业务执行错了。

15. PayStrategy 有多个实现导致注入失败,怎么处理?
答案:可以使用 @Qualifier 指定 Bean 名称,或用 @Primary 指定默认实现。
解析:因为 @Autowired 默认按类型装配,当同类型 Bean 不止一个时需要进一步消歧。


14. 最后总结

如果把 Spring 真正学明白,你脑子里应该留下的不是一堆注解,而是下面这条主线:

  1. Spring 先解决“对象该谁管理”,所以有 IoC / DI。
  2. 然后解决“公共逻辑别污染业务”,所以有 AOP。
  3. 再解决“多步数据库操作要一致”,所以有声明式事务。
  4. 再解决“HTTP 请求怎么统一接进系统”,所以有 Spring MVC。
  5. 最后解决“项目怎么快速装起来”,所以有 Spring Boot。

真正的高分掌握,不是会背几个注解,而是你能回答这五个问题:

  • 为什么需要容器?
  • 为什么事务依赖 AOP?
  • 为什么 MVC 要有统一入口?
  • 为什么 Boot 能减少配置?
  • 为什么项目里要做分层?

当你能把这五个“为什么”讲通,Spring 就不再是一堆零散知识点,而是一套完整的工程框架。

最后送你一句最有用的学习判断标准:

如果你能从“问题是什么”一路讲到“Spring 用什么机制解决、为什么这样设计、容易在哪出错”,那你就是真的掌握了。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐