Spring Boot 的本质:它不是"新的 Java 语言",也不是"替代 Spring 的东西",而是一个帮助你更快搭建、运行、配置、交付 Spring 应用 的工程化框架。
你可以把它理解成:Spring 提供能力,Spring Boot 把这些能力按企业开发常见场景"预组装"好了。
-
它是干什么的
用更少配置,快速开发 Web 接口、后台服务、管理系统、微服务、定时任务、数据服务。
-
它解决什么问题
解决传统 Spring 项目里"配置繁琐、依赖组合复杂、启动麻烦、环境切换乱、部署成本高"的问题。
-
它和哪些相关技术有关系
| 技术 | 关系 |
|---|---|
| Java | 语言基础 |
| Spring Framework | Spring Boot 的底座,核心是 IoC/AOP |
| Spring MVC | 做 Web 请求处理 |
| Spring Data / MyBatis / JPA | 做数据库访问 |
| Maven / Gradle | 管依赖、构建项目 |
| MySQL / Redis | 常见数据存储 |
| Tomcat / Jetty | 内嵌 Web 容器 |
| Docker / Linux / Nginx | 部署与运维 |
| Spring Cloud | 微服务体系,建立在 Boot 之上 |
-
学它之前需要哪些前置知识
必须具备:Java 基础、面向对象、注解、集合、异常、Maven、HTTP/JSON、SQL 基础。
最好具备:IDEA 调试、日志阅读、基本 Linux 命令。
-
真正重要的 核心内容是什么
必须吃透:IoC/DI、Bean、自动装配、Starter、配置文件、Web 开发、参数绑定、异常处理、数据库访问、事务、日志、Profile、多环境、启动流程。 -
哪些内容初学者容易陷入、但不值得一开始深挖
先知道,后深入:Spring 源码细节、AOT/Native、WebFlux、复杂 Security/OAuth2、自定义 Starter、自动装配源码深层实现、微服务全家桶、JVM 调优细节。 -
学习本质
不是背注解,而是搞清楚:容器什么时候创建对象、为什么能自动注入、为什么少写配置也能跑起来。
-
设计哲学
约定优于配置;默认可用;自动装配;组合常见能力;允许你覆盖默认行为。
学习顺序
-
入门认知
先搞清 Spring、Spring MVC、Spring Boot 分工,能跑起一个最小服务。
-
核心概念
重点掌握 IoC、DI、Bean、容器、配置类、Starter、自动装配。
-
Web 开发主线
掌握 Controller、请求映射、参数绑定、JSON、校验、统一返回、异常处理。
-
数据访问主线
掌握数据源、事务、MyBatis/JPA 二选一、分页、常见 SQL 问题。
-
核心机制
掌握启动流程、自动装配条件、配置优先级、Bean 生命周期、常见扩展点。
-
工程实践
日志、Profile、多环境配置、测试、打包部署、监控、Actuator。
-
常见问题与排错
启动失败、端口冲突、Bean 注入失败、循环依赖、配置不生效、数据库连不上。
-
面试与项目闭环
把"会写"变成"会解释、会权衡、会定位问题"。
学习顺序方法论:
先"跑起来",再"理解容器",再"会写接口",再"连数据库",最后"看原理和排错"。
初学者最容易学偏的地方:一上来抠源码、背很多注解、做微服务,却不会解释最基础的启动和注入。
第一阶段:入门认知
1. 学什么
这一阶段只做一件事:建立对 Spring Boot 的正确直觉。
要搞懂 4 个问题:它是什么、和 Spring 什么关系、一个项目长什么样、一个接口为什么能跑起来。
2. 为什么重要
如果这一步没站稳,后面学自动装配、事务、配置、数据库时会全乱。
很多人会写 @RestController,但说不清"为什么一启动就能接 HTTP 请求",这就是典型基础不牢。
3. 核心概念
先用一句话区分几个常混概念:
| 概念 | 直觉理解 | 技术定义 |
|---|---|---|
| Spring Framework | 基础设施工厂 | 提供 IoC、AOP、事务等核心能力 |
| Spring MVC | Web 层框架 | 处理 HTTP 请求到 Controller |
| Spring Boot | 快速装配器 | 用自动配置和 Starter 快速搭建 Spring 应用 |
| Spring Cloud | 微服务工具箱 | 服务注册、配置中心、网关等 |
再记住 6 个入门术语:
| 术语 | 你可以怎么理解 |
|---|---|
| Bean | 交给 Spring 容器管理的对象 |
| IoC 容器 | 帮你创建和管理对象的"总装配中心" |
| DI | 需要什么依赖,不自己 new,由容器注入 |
| Starter | 一组场景化依赖打包,比如 web、jdbc |
| 自动装配 | 根据依赖和条件,自动创建常用 Bean |
| 内嵌服务器 | 应用自己带 Tomcat,直接启动就能提供 HTTP 服务 |
必须吃透:Bean、容器、Starter、自动装配、启动类。
先知道,后深入:条件注解、自动装配源码扫描细节。
4. 原理解释
类比理解:
传统 Spring 像"你自己买零件组装厨房";Spring Boot 像"开发商给你装好了常用厨房,但你仍然可以换灶台"。
@SpringBootApplication 很关键,它本质上把三件事组合起来了:
- 配置类入口
- 开启自动装配
- 开启组件扫描
一个最小 Boot Web 应用启动时,简化流程是:
main 方法
SpringApplication.run(...)
创建 ApplicationContext
扫描组件与配置类
加载 Starter 对应自动配置
创建并注入 Bean
启动内嵌 Tomcat
开始监听 HTTP 请求
请求进来后的简化流程是:
浏览器/客户端
Tomcat
DispatcherServlet
Controller
返回 JSON/页面
设计思想:
- 大量默认配置,降低上手成本。
- 按条件装配,避免把所有东西都装进来。
- 你可以覆盖默认配置,所以"自动"不等于"不可控"。
5. 示例
最小可运行示例:
java
@SpringBootApplication
@RestController
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@GetMapping("/hello")
public Map<String, Object> hello() {
return Map.of("msg", "hello spring boot");
}
}
核心依赖通常只要一个:
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
运行后访问:http://localhost:8080/hello
你现在先只需要理解三件事:
main是启动入口。@RestController表示这是处理 HTTP 请求的组件。
@RestController = @Controller + @ResponseBody
@Controller 表示这是一个 Spring MVC 控制器组件。
@ResponseBody 表示:方法返回值直接写入 HTTP 响应体。
有 @ResponseBody:返回值直接进响应体
没有 @ResponseBody:返回值通常按视图名处理- 之所以不用自己部署 Tomcat,是因为 Boot 带了内嵌服务器。
6. 工程实践
真实工程场景:
公司要你 10 分钟内起一个"用户服务"的骨架,先提供 /ping 和 /users/health 两个接口,让前端、测试、运维先联调。
明确操作步骤:
- 创建项目,选
web依赖。 - 写启动类。
- 新建
controller包,写一个最小接口。 - 在
application.yml配置应用名和端口。 - 启动应用,确认日志里看到 Tomcat 端口。
- 用浏览器或
curl验证接口。 - 提交第一个可运行版本。
建议你形成的项目结构直觉:
text
src/main/java
├─ controller
├─ service
├─ config
└─ DemoApplication
src/main/resources
└─ application.yml
企业里通常怎么用:
先用 Boot 快速搭骨架,再逐步加数据库、日志、校验、权限、监控,而不是一开始就上复杂微服务。
7. 常见误区
- 以为 Spring Boot 替代了 Spring。错,Boot 是站在 Spring 上面的。
- 以为能跑起来全靠"魔法"。错,本质是容器 + 自动装配。
- 以为有
starter-web就只多了一个依赖。错,它通常带来一整套 Web 场景能力。 - 启动类乱放位置。启动类应尽量放在根包,否则扫描范围可能不对。
- 把 Boot 学成"背注解"。真正要学的是对象管理、配置装配、请求处理流程。
8. 面试题
-
Spring、Spring MVC、Spring Boot 分别是什么关系?
Spring Framework:Java 企业开发基础框架,核心能力是 IoC/DI、AOP、事务管理 等
Spring MVC:Spring 里的 Web 模块,专门负责处理 HTTP 请求
Spring Boot:建立在 Spring Framework 之上的快速开发框架,通过 Starter + 自动装配 + 默认配置 帮你快速搭建 Spring 应用Spring 是底座,Spring MVC 是 Spring 里的 Web 模块,Spring Boot 是让 Spring 更容易用的工程化封装。
-
Spring Boot 为什么能做到"开箱即用"?
Spring Boot 能开箱即用,核心原因有 3 个:
Starter
把某个场景需要的依赖提前组合好,比如 Web、数据库、测试自动装配
启动时根据依赖、配置项、环境条件,自动创建常用 Bean约定优于配置
很多默认配置已经帮你准备好,你只在需要时覆盖因为 Spring Boot 通过 Starter 带来场景依赖,再通过自动装配按条件创建默认组件,所以能开箱即用。
-
@SpringBootApplication做了什么?@SpringBootApplication 主要等价于 3 个核心注解的组合:
@SpringBootConfiguration
表示这是 Spring Boot 配置类,本质上接近 @Configuration@EnableAutoConfiguration
开启自动装配@ComponentScan
开启组件扫描,扫描当前包及子包中的组件@SpringBootApplication = 配置类 + 自动装配 + 组件扫描。
-
为什么 Spring Boot 项目不需要单独部署 Tomcat?
因为 Spring Boot 通常使用内嵌式 Web 容器,比如内嵌 Tomcat,应用启动时容器一起启动,所以不需要再把项目单独部署到外部 Tomcat。
-
Starter 的作用是什么?
它的作用是:
帮你把某个功能场景需要的依赖提前组合好
减少你手动找依赖、配版本的成本
配合自动装配,让功能更容易直接可用
例如:spring-boot-starter-web:Web 开发常用依赖
spring-boot-starter-test:测试相关依赖
spring-boot-starter-jdbc:数据库连接相关依赖
一句话版:
Starter 负责"把常用零件打包带上"。 -
一个 HTTP 请求到达 Controller 之前,经过了哪些关键组件?
对 HTTP 请求流程的入门版理解
你只要先知道:浏览器发请求
Spring Boot 接住请求
Spring MVC 找到对应的 @GetMapping
执行方法
把返回值返回给浏览器更标准的主干流程:
浏览器 / 前端发出 HTTP 请求
Tomcat 接收请求
请求进入 DispatcherServlet
DispatcherServlet 通过 HandlerMapping 找到对应 Controller 方法
再通过 HandlerAdapter 调用目标方法
然后才真正进入 Controller
如果说"到达 Controller 之前"的关键组件,最常答的是:Tomcat
DispatcherServlet
HandlerMapping
HandlerAdapter
一句话版:
请求先被 Tomcat 接收,再交给 DispatcherServlet,DispatcherServlet 通过 HandlerMapping 找到目标方法,再由 HandlerAdapter 调用 Controller。
9. 自测题
-
用你自己的话解释 Spring Boot 是干什么的,控制在 3 句话内。
Spring Boot 是建立在 Spring Framework 之上的快速开发框架。
它通过 Starter、自动装配和默认配置,帮我们更快搭建 Spring 应用。
它常用来开发 Web 接口、后台服务和企业级应用。 -
说出 Spring Framework、Spring MVC、Spring Boot 的区别。
Spring Framework:底层基础框架,核心能力是 IoC、AOP、事务管理。
Spring MVC:Spring 的 Web 模块,负责处理 HTTP 请求。
Spring Boot:在 Spring 基础上做快速开发和自动配置的框架。 -
解释什么是 Bean,什么是 IoC 容器。
Bean:交给 Spring 容器管理的对象。
IoC 容器:负责创建、管理、装配 Bean 的"对象工厂"。 -
写出一个最小 Spring Boot Web 应用需要哪些核心元素。
一个带 @SpringBootApplication 的启动类
一个 main 方法,调用 SpringApplication.run(...)
spring-boot-starter-web 依赖
一个 @RestController
至少一个 @GetMapping 或 @PostMapping 方法 -
说明
spring-boot-starter-web大致帮你准备了什么能力。spring-boot-starter-web 大致帮你准备了:
Spring MVC 处理 Web 请求的能力
内嵌 Tomcat
JSON 转换能力
常见 Web 开发依赖
你现在可以先把它理解成:Web 开发套餐包。
也就是你不用自己一个个找:
MVC
Tomcat
JSON 处理 -
动手题:自己创建一个
/ping接口,返回{ "ok": true }。 -
动手题:把端口改成
8081,重新启动并验证。 -
动手题:故意把启动类放到错误包路径,观察可能出现什么问题。
10. 学完标志
学完这一阶段后,你应该能做到:
- 能清楚解释 Spring、Spring MVC、Spring Boot 的关系。
- 能独立创建并运行一个最小 Spring Boot Web 项目。
- 能说明启动类、Starter、内嵌 Tomcat、Controller 的作用。
- 能从"容器自动装配"的角度解释为什么接口能跑起来。
- 能避开最基础的项目结构和扫描路径错误。
下一阶段我们会进入"核心概念",重点吃透:IoC、DI、Bean、配置类、Starter、自动装配。这是 Spring Boot 真正的分水岭。
第二阶段:核心概念
1. 学什么
这一阶段要建立 Spring Boot 最核心的认知骨架:
- 什么是 IoC,什么是 DI
- 什么是 Bean,谁在管理 Bean
@Component、@Service、@Controller、@Bean、@Configuration分别干什么- Starter 为什么能让你少配很多东西
- 自动装配到底是"自动"了什么
这一阶段你要从"会写注解"升级到"知道容器为什么这样工作"。
2. 为什么重要
Spring Boot 的绝大多数能力,本质上都建立在"容器管理对象"之上。
你后面学到的这些内容:
- Controller 为什么能被请求到
- Service 为什么能被自动注入
- 数据源为什么能自动创建
- 事务为什么能生效
- 配置为什么能自动绑定
背后都离不开 IoC、DI、Bean、自动装配。
如果这一层没吃透,你会出现 3 种典型问题:
- 代码能抄出来,但改一点就不会
- 启动报错时只会删依赖、重启、碰运气
- 面试时只能背注解名,解释不了机制
所以这一阶段是 必须吃透。
3. 核心概念
3.1 先讲直觉
类比理解:
- 没有 Spring 时,你自己采购零件、组装机器、接电接线。
- 有了 Spring 后,Spring 容器像一个"总装配车间",你只需要告诉它有哪些零件、它们怎么关联,容器会统一创建和装配。
3.2 核心定义
| 概念 | 直觉理解 | 技术定义 |
|---|---|---|
| IoC | 控制权交出去 | 对象的创建与管理交给 Spring 容器,而不是业务代码自己控制 |
| DI | 需要什么就给什么 | 容器在创建对象时,把它依赖的其他对象注入进去 |
| Bean | 被托管的对象 | 由 Spring 容器创建、管理、装配、销毁的对象 |
| ApplicationContext | 容器本体 | Spring 的核心上下文,负责 Bean 的注册、实例化、依赖注入、生命周期管理 |
| 组件扫描 | 自动找组件 | Spring 按包路径扫描带注解的类并注册成 Bean |
| 配置类 | Bean 生产工厂 | 使用 @Configuration + @Bean 显式定义 Bean |
| Starter | 场景依赖包 | 一组围绕某个场景预组合好的依赖集合 |
| 自动装配 | 按条件帮你配好 | Spring Boot 根据类路径、配置项、已有 Bean 等条件自动创建常用 Bean |
3.3 常见注解对比
| 注解 | 作用 | 常见位置 | 你该怎么理解 |
|---|---|---|---|
@Component |
通用组件注册 | 普通类 | "把这个类交给容器管理" |
@Service |
业务层组件 | Service 类 | 本质也是组件,语义更清晰 |
@Repository |
持久层组件 | DAO/Repository 类 | 语义上表示数据访问层 |
@Controller |
MVC 控制器 | 页面控制器 | 处理 Web 请求 |
@RestController |
返回 JSON 的控制器 | API 控制器 | @Controller + @ResponseBody |
@Configuration |
配置类 | 配置文件对应 Java 类 | 表示"这是一个定义 Bean 的配置类" |
@Bean |
注册一个 Bean | 方法上 | 把方法返回值放进容器 |
@Autowired |
自动注入依赖 | 字段/构造器/方法 | 让容器把需要的 Bean 注入进来 |
3.4 @Component 和 @Bean 的区别
| 对比项 | @Component |
@Bean |
|---|---|---|
| 用法 | 标在类上 | 标在方法上 |
| 适合对象 | 你自己写的类 | 第三方类或需要手动控制创建逻辑的对象 |
| 注册方式 | 扫描发现 | 显式声明 |
| 常见场景 | UserService、UserController |
ObjectMapper、RestTemplate、自定义工具对象 |
这两个概念很容易混。
一句话记住:
- 自己写的业务类,通常用
@Component体系。 - 不是你源码里的类,或者创建逻辑复杂时,用
@Bean。
4. 原理解释
4.1 IoC 和 DI 到底在解决什么问题
先看没有 Spring 的写法:
java
public class UserController {
private UserService userService = new UserService();
}
这会带来几个问题:
- Controller 自己决定依赖谁,耦合很死。
UserService再依赖别的对象,会一层层new下去。- 测试时很难替换依赖。
- 对象创建时机、作用范围、配置来源都分散在代码里。
Spring 的思路是:
- 你声明"我需要一个
UserService"。 - 容器统一负责创建
UserService。 - 容器再把它注入给
UserController。
这就是:
- IoC:对象控制权反转给容器。
- DI:容器把依赖注入给对象。
4.2 容器是怎么工作的
简化步骤拆解:
- 应用启动,创建
ApplicationContext。 - 容器扫描启动类所在包及其子包。
- 找到带
@Component、@Service、@Controller等注解的类。 - 把这些类注册为 Bean 定义。
- 根据依赖关系实例化 Bean。
- 把依赖对象注入进去。
- 完成初始化后,整个应用进入可用状态。
用流程图看更直观:
启动应用
创建 ApplicationContext
扫描组件
注册 BeanDefinition
实例化 Bean
依赖注入
初始化完成
业务代码开始提供服务
4.3 为什么推荐构造器注入
Spring 支持多种注入方式:
| 方式 | 示例 | 是否推荐 | 原因 |
|---|---|---|---|
| 字段注入 | @Autowired private UserService userService; |
不推荐 | 不利于测试,也不利于暴露必需依赖 |
| Setter 注入 | setUserService(...) |
一般 | 适合可选依赖 |
| 构造器注入 | 通过构造方法注入 | 推荐 | 依赖明确、便于测试、更符合不可变设计 |
推荐写法:
java
@Service
public class UserService {
}
@RestController
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
}
设计思想:
- 必需依赖应该在对象创建时一次性满足。
- 一个对象不能"先半成品创建,再晚点补依赖"。
- 构造器让依赖关系显式化,更适合大型工程。
4.4 Starter 和自动装配为什么能省配置
以 spring-boot-starter-web 为例,它帮你做了两层事情:
- 帮你引入 Web 场景需要的一组依赖。
- 当 Spring Boot 发现这些依赖存在时,自动配置 MVC、JSON 转换、Tomcat 等常见 Bean。
你可以把它理解成:
- Starter 负责"把常用零件打包带上"。
- 自动装配负责"看到这些零件后帮你装起来"。
4.5 自动装配的简化运行逻辑
自动装配不是无脑全配,而是"按条件配置"。
典型判断条件包括:
- 类路径下是否存在某个类
- 配置文件里是否启用了某项功能
- 容器里是否已经有同类型 Bean
- 当前是不是 Web 应用
所以自动装配的底层设计思想是:
- 给你合理默认值
- 不和你手写配置抢控制权
- 满足条件才生效,避免无意义装配
你可以把它理解成一句话:
Spring Boot 不是"什么都自动配",而是"满足条件时帮你配常用默认实现"。
5. 示例
5.1 最小可运行示例
java
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
java
package com.example.demo.service;
import org.springframework.stereotype.Service;
@Service
public class UserService {
public String getUserName() {
return "zhangsan";
}
}
java
package com.example.demo.controller;
import com.example.demo.service.UserService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
@RestController
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("/user")
public Map<String, Object> getUser() {
return Map.of("name", userService.getUserName());
}
}
这个例子里发生了什么:
UserService被@Service标记,注册成 Bean。UserController也是一个 Bean。- 容器发现
UserController构造器需要UserService。 - 容器把
UserService注入进去。 - 访问
/user时,Controller 调用 Service 返回结果。
5.2 @Bean 示例
java
@Configuration
public class AppConfig {
@Bean
public LocalDateTime startupTime() {
return LocalDateTime.now();
}
}
这里的意思是:
AppConfig是配置类。startupTime()的返回值会被注册进容器。- 之后别的 Bean 可以注入这个
LocalDateTime。
6. 工程实践
6.1 真实工程场景
你要做一个"用户中心"接口模块,常见分层是:
controller接收请求service写业务逻辑repository或mapper访问数据库config统一放配置类
为什么企业喜欢这种方式:
- 每层职责清晰
- 方便测试和替换实现
- 容器能统一管理这些对象
- 出问题时更容易按层排查
6.2 明确操作步骤
如果你现在要自己练一遍,建议按这 7 步走:
- 新建一个 Spring Boot 项目,引入
spring-boot-starter-web。 - 建
controller、service、config三个包。 - 写一个
UserService,返回固定用户名。 - 写一个
UserController,通过构造器注入UserService。 - 启动项目,访问
/user。 - 新建一个
AppConfig,定义一个@Bean。 - 在 Controller 或 Service 里注入这个
@Bean,验证容器托管生效。
6.3 你在企业里必须掌握的"必须会"
必须掌握:
- 会区分
@Component和@Bean - 会写构造器注入
- 知道 Bean 是容器管理的对象
- 知道启动类扫描范围影响注入是否成功
- 知道 Starter 和自动装配是两层概念
知道即可:
- 很多冷门注解
- 自动装配源码中更深层的导入细节
- 所有 Bean 生命周期回调接口
7. 常见误区
-
以为 IoC 和 DI 是两个完全无关的概念。
其实 DI 是 IoC 的一种实现方式,IoC 是思想,DI 是落地手段。
-
以为
@Service比@Component更"高级"。不是,本质都能注册 Bean,区别主要是语义分层。
-
以为
@Autowired就等于 Spring 核心。不是,核心是容器管理和依赖装配,
@Autowired只是注入手段之一。 -
以为引了 Starter,就一定什么都配好了。
不是,还要满足自动装配条件,而且有些配置你仍然要自己写。
-
以为 Bean 就是"一个普通对象"。
不完整。Bean 是被容器管理、可参与注入、生命周期可控的对象。
-
以为扫描不到 Bean 是偶发 bug。
很多时候是启动类包路径不对,或类没有被容器管理。
8. 面试题
-
什么是 IoC?什么是 DI?它们是什么关系?
IoC 是控制反转,意思是"对象的创建和依赖关系的管理"不再由业务代码自己控制,而是交给 Spring 容器。
DI 是依赖注入,指容器把对象依赖的其他对象注入进去。
关系上,IoC 是思想,DI 是最常见的实现方式。
-
Bean 是什么?和普通
new出来的对象有什么区别?Bean 就是被 Spring 容器管理的对象。它和普通 new 出来的对象最大区别是:
Bean 由容器负责创建、依赖注入、生命周期回调,还可能被 AOP 代理;
普通 new 出来的对象只是普通 Java 对象,不受容器管理。 -
@Component和@Bean有什么区别?@Component 是标在类上的,通常配合组件扫描把这个类注册成 Bean。
@Bean 是标在方法上的,表示把这个方法返回的对象交给 Spring 管理。
一般自己写的业务类常用 @Component、@Service、@Controller;
第三方类、复杂初始化逻辑更适合用 @Bean。 -
Spring 容器启动时大致做了哪些事?
Spring 容器启动时大致会做这些事:
创建 ApplicationContext,
读取配置和环境信息,
扫描组件,
注册 BeanDefinition,
执行自动装配条件判断,
实例化单例 Bean,
完成依赖注入,
执行初始化回调和后置处理器,
最后启动 Web 服务器并对外提供服务。
-
为什么推荐构造器注入,而不是字段注入?
推荐构造器注入而不是字段注入,
因为构造器注入依赖更明确,对象在创建时就是完整状态,字段也可以用 final,更方便测试,也更容易发现循环依赖。
字段注入的问题是依赖隐藏、不利于单元测试,也不够规范。
-
Starter 和自动装配分别是什么?
Starter 是一组场景化依赖的打包方案,
比如 spring-boot-starter-web,帮你把 Web 开发常用依赖一次性带进来。
自动装配是 Spring Boot 根据当前类路径、配置项、已有 Bean 等条件,自动帮你创建合适 Bean 的机制。简单说,Starter 解决"把包带进来",自动装配解决"把对象配起来"。
-
自动装配为什么不会把所有东西都无脑创建?
自动装配不会无脑创建所有东西,因为它是"有条件"的。
常见条件有:
类路径里是否存在某个类、
配置文件里是否开启某个功能、
容器里是否已经有同类型 Bean。像 @ConditionalOnClass、@ConditionalOnMissingBean、@ConditionalOnProperty
就是在控制这件事,目的是给合理默认值,同时允许你覆盖。 -
为什么有时候明明写了
@Service,却注入失败?写了 @Service 但注入失败,常见原因有:
这个类没被扫描到、
你自己 new 了对象而不是让 Spring 管、
接口有多个实现导致歧义、
依赖的下游 Bean 本身创建失败、
条件装配没生效。排查时先看启动报错类型最有效:
NoSuchBeanDefinitionException 通常是没找到 Bean;
NoUniqueBeanDefinitionException 通常是同类型 Bean 太多;
BeanCreationException 通常是 Bean 创建过程中内部又出错了。
9. 自测题
-
用你自己的话解释 IoC 和 DI,要求能让一个没学过 Spring 的同学听懂。
IoC 就是原本对象由你自己 new,现在交给 Spring 帮你创建和管理。
DI 就是这个对象需要别的对象时,不用自己去找,Spring 直接把依赖塞给它。
所以:IoC 是思想,DI 是实现这种思想的常见方式。 -
写出
@Component、@Service、@Controller、@Bean的区别。标准答案
@Component:通用组件注解,标在类上,把类注册成 Bean
@Service:业务层组件,本质上也是组件注解,只是语义更清晰
@Controller:Web 控制器组件,负责接收请求
@Bean:标在方法上,把方法返回值注册成 Bean,适合第三方类或自定义创建逻辑一句话总结
@Component / @Service / @Controller 是"类级注册",@Bean 是"方法返回值注册"。 -
为什么构造器注入更适合工程项目?
构造器注入更适合工程项目,因为:
依赖关系是显式的,一眼能看出这个类依赖什么
必需依赖可以在对象创建时一次性满足
单元测试更方便,直接 new 时传 mock 对象即可
比字段注入更清晰、更稳,也更符合工程化设计 -
如果
UserController注入UserService失败,你会从哪些方向排查?UserService 有没有被 Spring 管理
比如有没有 @Service / @Component包路径是否在启动类扫描范围内
有没有自己手动 new UserController() 或 new UserService()
是否存在多个同类型 Bean 导致歧义
UserService 自己是不是创建失败了
比如它依赖数据库、别的 Bean,而那些先炸了 -
spring-boot-starter-web和自动装配分别负责什么?spring-boot-starter-web:是 Web 场景的依赖集合,通常带来 Spring MVC、内嵌 Tomcat、JSON 处理等能力
自动装配:是在启动时根据类路径、配置项、是否已有 Bean、当前是否 Web 环境等条件,自动注册默认组件 -
动手题:写一个
OrderService和OrderController,通过构造器注入返回固定订单信息。 -
动手题:写一个
@Configuration类,注册一个自定义Clock或LocalDateTimeBean。 -
动手题:故意把
Service类放到扫描范围之外,观察报错信息并解释原因。UserService 没被注册成 Bean
UserController 注入失败
常见报错是 NoSuchBeanDefinitionException
根因是启动类默认只扫描它所在包及子包
10. 学完标志
学完这一阶段后,你应该能做到:
- 能准确解释 IoC、DI、Bean、容器、Starter、自动装配。
- 能区分
@Component、@Service、@Bean、@Configuration的使用场景。 - 能用构造器注入写出基础的 Controller + Service 结构。
- 能从"容器扫描、注册、实例化、注入"的流程解释应用为什么能运行。
- 遇到基础的 Bean 注入失败问题时,知道优先排查包路径、注解、依赖类型、是否被容器托管。
- 能把 Spring Boot 的"方便"解释成 Starter + 自动装配,而不是一句"它会自动帮我配"。
下一阶段会进入 Web 开发主线,重点讲:请求是怎么进来的、参数是怎么绑定的、JSON 是怎么返回的、异常和校验怎么统一处理 。
这会把你从"理解容器"推进到"真正能写业务接口"。
第三阶段:Web 开发主线
1. 学什么
这一阶段我们把 Spring Boot 最常用的 Web 开发能力打通,重点掌握:
- 什么是
@RestController、@RequestMapping、@GetMapping、@PostMapping - 请求参数有哪些常见来源,分别怎么接
- 返回值为什么能自动变成 JSON
- 一个 HTTP 请求进入 Spring Boot 后,大致经历了什么流程
- 如何写出最基础但像样的接口结构
这一阶段的目标不是"会抄接口",而是建立一条完整链路:
客户端发请求 -> Spring MVC 接住 -> 参数绑定 -> 调业务 -> 返回 JSON
必须吃透:
@RestController和@Controller区别@RequestParam、@PathVariable、@RequestBody区别DispatcherServlet的职责- JSON 转换为什么能自动发生
先知道,后深入:
- 拦截器、过滤器、参数解析器的全部扩展细节
HttpMessageConverter全部实现类HandlerMethodArgumentResolver源码级执行细节
2. 为什么重要
你学习 Spring Boot,最早真正"产生产出"的能力就是写接口。
企业里最常见的工作就是:
- 提供 REST API 给前端、移动端、其他服务调用
- 接收参数,做校验,调用 Service,返回 JSON
- 出现 400、404、405、415、500 时能看懂问题在哪一层
如果这部分没掌握,你会出现这些问题:
- 搞不清参数该用
@RequestParam还是@RequestBody - 只会写接口,不知道请求怎么被 Spring 接住
- 报 404、405、400 时只能改来改去试错
- 面试时说不清
DispatcherServlet和 Spring MVC 的关系
这一阶段是你从"理解框架"迈向"会写业务"的关键转折点。
3. 核心概念
3.1 先用直觉理解
你可以把 Spring MVC 理解成一个"请求调度中心"。
- 浏览器或前端把请求发过来
- Spring MVC 先判断该交给哪个 Controller
- 再把请求里的路径、查询参数、请求体解析成 Java 对象
- Controller 调 Service
- 最后把 Java 返回值转成 JSON 发回去
3.2 核心概念定义
| 概念 | 直觉理解 | 技术定义 |
|---|---|---|
| Spring MVC | Web 请求处理框架 | Spring 中负责 HTTP 请求映射、参数绑定、返回处理的模块 |
DispatcherServlet |
总调度员 | Spring MVC 的前端控制器,统一接收并分发请求 |
| Controller | 接口入口 | 负责接收请求、调用业务、返回结果 |
| HandlerMapping | 路由表 | 根据请求路径和方法找到目标处理器 |
| HandlerAdapter | 调用器 | 负责真正执行 Controller 方法 |
| 参数绑定 | 自动拆请求 | 把 URL、Query、Header、Body 等内容转换成方法参数 |
| 消息转换器 | Java 和 JSON 翻译器 | 在 Java 对象与 HTTP 请求/响应体之间做转换 |
| REST API | 面向资源的接口风格 | 通常通过 HTTP 方法 + 路径 + JSON 来设计接口 |
3.3 常见 Web 注解对比
| 注解 | 作用 | 典型场景 |
|---|---|---|
@RestController |
返回 JSON 的控制器 | 后端接口开发 |
@Controller |
MVC 控制器 | 返回页面模板时更常见 |
@RequestMapping |
定义通用请求映射 | 类上定义公共路径,方法上定义细粒度路径 |
@GetMapping |
处理 GET 请求 | 查询接口 |
@PostMapping |
处理 POST 请求 | 新增、复杂查询、提交表单/JSON |
@PutMapping |
处理 PUT 请求 | 更新接口 |
@DeleteMapping |
处理 DELETE 请求 | 删除接口 |
@PathVariable |
接路径参数 | /users/1 里的 1 |
@RequestParam |
接查询参数 | /users?page=1 |
@RequestBody |
接请求体 JSON | 前端传来的 JSON 对象 |
@RequestHeader |
接请求头 | Token、设备信息、追踪信息 |
3.4 最容易混淆的几个概念
| 容易混淆项 | 区别 |
|---|---|
@Controller vs @RestController |
前者默认用于页面控制器;后者默认把返回值写到响应体里,常用于 JSON 接口 |
@RequestParam vs @PathVariable |
一个取查询参数,一个取路径参数 |
@RequestBody vs 普通对象参数 |
前者从请求体读 JSON;后者更多依赖表单参数或查询参数绑定 |
| 404 vs 405 | 404 是没找到路径;405 是路径有,但 HTTP 方法不匹配 |
| 400 vs 415 | 400 往往是参数不合法或绑定失败;415 往往是请求体类型不支持,比如 Content-Type 不对 |
4. 原理解释
4.1 一个请求是怎么进来的
先看主干流程:
客户端发起 HTTP 请求
Tomcat 接收请求
DispatcherServlet
HandlerMapping 查找目标方法
HandlerAdapter 调用 Controller
参数绑定与类型转换
执行业务逻辑
返回 Java 对象
HttpMessageConverter 转成 JSON
响应给客户端
这条流程里最关键的点有 4 个:
- Tomcat 负责接收网络层面的 HTTP 请求
DispatcherServlet负责把请求交给 Spring MVC 处理- 参数绑定负责把 HTTP 世界转换成 Java 世界
- 消息转换器负责把 Java 返回值再转回 HTTP/JSON 世界
4.2 DispatcherServlet 为什么重要
类比理解:
DispatcherServlet 就像医院导诊台。
- 病人来了,不是直接冲进科室
- 导诊台先判断该挂哪个科
- 再把病人送到对应医生
- 看完后把结果再交出去
技术定义:
DispatcherServlet 是 Spring MVC 的前端控制器,统一拦截进入应用的 Web 请求,并协调请求映射、参数解析、方法执行、结果处理等流程。
设计思想:
- 用一个统一入口管理所有请求
- 把"路由""参数解析""结果处理"拆成独立组件
- 方便扩展,不把所有逻辑写死在一个地方
4.3 参数绑定为什么这么方便
Spring MVC 会根据方法参数上的注解和参数类型决定"从哪里取值"。
例如:
@PathVariable Long id-> 从路径中取值@RequestParam Integer page-> 从查询参数中取值@RequestBody UserCreateRequest req-> 从 JSON 请求体中取值
它背后的设计思想是:
- Controller 方法应该表达业务意图,而不是手动解析原始 HTTP 文本
- 框架负责做大部分重复劳动
- 你只需要声明"我要什么参数"
4.4 为什么返回对象能自动变成 JSON
当你写:
java
return Map.of("name", "zhangsan");
或者:
java
return userDto;
Spring MVC 会把这个 Java 对象交给消息转换器处理。
在 Web 场景中,常见是用 Jackson 把对象序列化成 JSON。
简化过程:
- Controller 返回 Java 对象
- Spring 判断这不是页面,而是响应体内容
- 找到合适的
HttpMessageConverter - 把对象转成 JSON 字符串
- 写入 HTTP 响应体
这就是为什么 @RestController 如此高频:
它的设计目标就是让你默认走"返回数据而不是返回页面"的开发模式。
4.5 类比总结
| 环节 | 类比 |
|---|---|
| Tomcat | 门卫,先接住请求 |
DispatcherServlet |
总调度台 |
HandlerMapping |
通讯录,查谁处理 |
| Controller | 接待窗口 |
| Service | 真正办事的人 |
HttpMessageConverter |
翻译员,把对象翻译成 JSON |
5. 示例
5.1 最小可运行示例
java
@RestController
@RequestMapping("/users")
public class UserController {
@GetMapping("/ping")
public Map<String, Object> ping() {
return Map.of("ok", true);
}
}
访问:
text
GET /users/ping
返回:
json
{
"ok": true
}
5.2 路径参数示例
java
@GetMapping("/{id}")
public Map<String, Object> getUserById(@PathVariable Long id) {
return Map.of("id", id, "name", "user-" + id);
}
请求:
text
GET /users/100
5.3 查询参数示例
java
@GetMapping
public Map<String, Object> listUsers(
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer size) {
return Map.of("page", page, "size", size);
}
请求:
text
GET /users?page=2&size=20
5.4 请求体 JSON 示例
java
public class UserCreateRequest {
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
java
@PostMapping
public Map<String, Object> createUser(@RequestBody UserCreateRequest request) {
return Map.of(
"message", "created",
"name", request.getName(),
"age", request.getAge()
);
}
请求:
http
POST /users
Content-Type: application/json
{
"name": "tom",
"age": 20
}
5.5 一个更像工程代码的最小结构
java
@Service
public class UserService {
public Map<String, Object> getUser(Long id) {
return Map.of("id", id, "name", "zhangsan");
}
}
java
@RestController
@RequestMapping("/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("/{id}")
public Map<String, Object> getUser(@PathVariable Long id) {
return userService.getUser(id);
}
}
这个例子里你要观察两件事:
- Controller 负责接请求和组织响应,不写复杂业务
- Service 负责业务逻辑,这样分层更清晰
6. 工程实践
6.1 真实工程场景
你要做一个"用户管理模块",常见接口有:
GET /users/{id}查询用户详情GET /users?page=1&size=10分页查用户POST /users新增用户PUT /users/{id}修改用户DELETE /users/{id}删除用户
这就是典型 REST 风格接口。
企业里为什么喜欢这种风格:
- 路径表达资源
- HTTP 方法表达操作语义
- 接口风格统一,前后端更容易协作
- 便于文档化、测试和网关治理
6.2 推荐的 Controller 编写原则
必须掌握:
- Controller 只做请求接收、参数解析、结果返回
- 业务逻辑放 Service
- 不要把数据库操作直接堆在 Controller
- 路径命名尽量用复数资源名,如
/users、/orders
工程上更稳的做法:
- 请求对象和返回对象单独定义,不直接暴露数据库实体
- 参数命名统一,响应结构统一
- 对外接口返回 JSON,不混杂页面逻辑
6.3 明确操作步骤
你可以按这个顺序自己练习一遍:
- 创建
UserController,定义类级别路径/users - 写一个
GET /users/ping接口 - 写一个
GET /users/{id},练习@PathVariable - 写一个
GET /users?page=1&size=10,练习@RequestParam - 写一个
POST /users,练习@RequestBody - 用 Postman、Apifox 或 curl 分别测试
- 故意发错请求,观察 404、405、400 的区别
6.4 常见 HTTP 状态码先建立直觉
| 状态码 | 常见含义 | 你第一反应该排查什么 |
|---|---|---|
| 200 | 成功 | 基本正常 |
| 400 | 请求参数错误 | 参数格式、必填项、JSON 结构 |
| 404 | 找不到资源 | 路径写错、映射没注册 |
| 405 | 方法不允许 | GET/POST/PUT/DELETE 用错 |
| 415 | 媒体类型不支持 | Content-Type 不对,常见于 JSON 请求 |
| 500 | 服务器内部错误 | 后端代码异常、空指针、数据库等问题 |
7. 常见误区
-
以为
@RestController只是"少写一点代码"。不只是语法简化,它体现的是默认返回数据而不是页面的开发模式。
-
以为 GET 和 POST 只是名字不同。
不是,HTTP 方法本身表达语义,也会影响前后端协作和接口设计规范。
-
以为
@RequestBody能接所有参数。不是,它主要从请求体里读 JSON;路径参数和查询参数不是这样取的。
-
以为 404 就是服务没启动。
不一定,很多时候只是路径映射没写对。
-
以为接口返回对象自动变 JSON 是 Java 自带能力。
不是,是 Spring MVC + Jackson 这类消息转换机制在工作。
-
以为 Controller 里写 SQL 也没事,先跑起来再说。
短期能跑,长期会让接口层、业务层、数据层彻底耦合,后面维护会非常痛苦。
-
以为接口能访问就代表设计合理。
能访问只是最低标准,分层、命名、参数设计、错误处理一样重要。
8. 面试题
@Controller和@RestController有什么区别?@RequestParam、@PathVariable、@RequestBody分别适用于什么场景?- 一个 HTTP 请求进入 Spring MVC 后,大致执行流程是什么?
DispatcherServlet是什么?为什么说它是前端控制器?- 为什么 Controller 返回 Java 对象时,前端能收到 JSON?
- 404、405、400、415 分别常见于哪些问题?
- 为什么一般不建议在 Controller 里直接写业务逻辑和数据库代码?
- Spring MVC 和 Tomcat 的关系是什么?
9. 自测题
- 用你自己的话解释:一个请求从浏览器到 Controller,再到 JSON 响应,中间发生了什么。
- 写出一个包含路径参数、查询参数、请求体参数的接口示例。
- 如果前端调用
POST /users返回 415,你会优先排查什么? - 如果访问
/users/1返回 404,你会怎么定位? - 为什么
@RestController更适合前后端分离项目? - 动手题:实现一个
/orders/{id}查询接口。 - 动手题:实现一个
POST /orders接口,接收 JSON 请求体。 - 动手题:分别故意制造 404、405、400,记录现象并总结区别。
10. 学完标志
学完这一阶段后,你应该能做到:
- 能独立写出基础的 REST 风格接口。
- 能正确使用
@GetMapping、@PostMapping、@PathVariable、@RequestParam、@RequestBody。 - 能解释
DispatcherServlet在请求处理中的角色。 - 能说明 Controller 返回值为什么会被转成 JSON。
- 遇到基础的 404、405、400、415 问题时,知道从路径、方法、参数、请求头几个方向快速排查。
- 能按 Controller -> Service 的分层方式写出更像工程项目的接口代码。
下一阶段会进入数据访问主线,重点讲:Spring Boot 如何连接数据库、MyBatis/JPA 怎么选、事务为什么能回滚、常见 SQL 和连接问题怎么排查。这会让你从"会写接口"进到"会写真实业务"。
第四阶段:数据访问主线
1. 学什么
这一阶段要解决一个核心问题:
Spring Boot 写好的接口,怎么真正把数据存进数据库、再查出来。
你需要重点掌握:
- Spring Boot 如何连接数据库
- 数据源是什么,连接池在干什么
- MyBatis 和 JPA 分别是什么,怎么选
- Mapper/Repository 在项目里扮演什么角色
- 事务是什么,为什么会回滚
- 常见数据库连接、SQL、事务问题怎么排查
学习建议:
- 入门阶段先走 MyBatis 主线,更容易建立 SQL 和数据访问的直觉
- JPA 先知道核心思想和适用场景,后面再深入
必须吃透:
- 数据源
- 连接池
- MyBatis 基本用法
- 事务和
@Transactional - 常见数据库连接错误排查
先知道,后深入:
- 多数据源
- 分库分表
- 复杂缓存一致性
- JPA 高级映射
- 分布式事务
2. 为什么重要
你前面学了 Controller 和 Service,但如果不会数据访问,就只能返回假数据。
企业里的绝大多数后端业务,最终都绕不开:
- 查用户
- 存订单
- 更新状态
- 做分页
- 保证一组操作要么都成功,要么都失败
如果这部分不扎实,最容易出现这些问题:
- SQL 能写,但不会接入 Spring Boot
- 能查到数据,但不会做事务
- 启动就报数据库连接失败,不知道先查配置还是网络
- 面试时说不清
@Transactional为什么会生效
所以这一阶段是"从写 Demo 到写真实业务"的关键一步。
3. 核心概念
3.1 先用直觉理解
你可以把数据库访问这条链路理解成:
- 业务代码想查或改数据
- 它不会直接和数据库裸连到底
- 中间要经过数据源、连接池、访问框架
- 事务负责兜底,保证一组操作的一致性
3.2 核心定义
| 概念 | 直觉理解 | 技术定义 |
|---|---|---|
| 数据源 DataSource | 数据库连接入口 | 提供数据库连接的统一抽象 |
| 连接池 | 可复用连接仓库 | 预先维护一批数据库连接,避免每次现建现关 |
| JDBC | Java 连数据库的基础协议 | Java 提供的标准数据库访问接口 |
| ORM | 对象和表的映射思路 | 用对象方式操作关系型数据库 |
| MyBatis | 半自动 SQL 框架 | SQL 你自己写,映射和执行框架帮你处理 |
| JPA | ORM 规范 | 描述对象关系映射的一套标准,常见实现是 Hibernate |
| Mapper / Repository | 数据访问层 | 专门负责查库、写库的组件 |
| 事务 | 一组操作的原子单位 | 要么全部成功,要么全部失败 |
| 回滚 | 撤销未完成操作 | 事务失败时恢复到执行前状态 |
3.3 MyBatis 和 JPA 怎么选
| 对比项 | MyBatis | JPA |
|---|---|---|
| SQL 控制权 | 高,SQL 自己写 | 低一些,更强调对象映射 |
| 上手难度 | 对有 SQL 基础的人更友好 | 需要理解实体映射、持久化上下文 |
| 性能调优直觉 | 更直接 | 需要更理解 ORM 行为 |
| 复杂 SQL | 很适合 | 复杂 SQL 写起来不如 MyBatis 直接 |
| 开发效率 | 中等 | 简单 CRUD 效率高 |
| 企业常见场景 | 国内业务系统很多 | 标准化后台、领域建模较强的系统更多 |
当前阶段建议:
- 先主学 MyBatis
- 知道 JPA 是"面向对象操作数据库"的思路
- 面试时能说出取舍,而不是站队
3.4 Spring Boot 里常见的数据访问分层
| 层 | 作用 |
|---|---|
| Controller | 接收请求,返回结果 |
| Service | 写业务流程、事务边界 |
| Mapper / Repository | 负责执行 SQL 或 ORM 操作 |
| Database | 持久化存储 |
一句话记住:
- Controller 不直接写 SQL
- Mapper 不写业务流程
- 事务通常放在 Service 层
4. 原理解释
4.1 Spring Boot 是怎么连上数据库的
简化流程:
application.yml 配置数据库信息
Spring Boot 创建 DataSource
连接池初始化
MyBatis/JPA 获取连接
执行 SQL
把结果映射成 Java 对象
返回给 Service/Controller
Spring Boot 自动配置通常会根据你引入的依赖和配置项,自动创建:
DataSource- 事务管理器
- MyBatis 或 JPA 相关组件
4.2 为什么需要连接池
如果每次请求都现建数据库连接,代价很高:
- 连接建立慢
- 数据库压力大
- 并发一高就顶不住
所以连接池的核心思想是:
- 先准备好一些连接
- 业务来了直接借用
- 用完归还,不是销毁
你可以把它理解成"共享单车停车点":
- 不需要每次现造一辆车
- 用的时候拿一辆
- 用完再停回去
4.3 MyBatis 的工作流程
MyBatis 的核心特点是:
SQL 你来写,框架帮你执行并把结果映射成对象。
简化流程:
- 你定义 Mapper 接口
- 你写 SQL
- MyBatis 根据 Mapper 生成代理对象
- 你调用接口方法
- MyBatis 执行 SQL
- 把结果映射成 Java 对象返回
它为什么适合初学者:
- 你能直接看到 SQL
- 容易理解数据库行为
- 出问题更容易顺着 SQL 排查
4.4 事务为什么重要
看一个典型场景:转账。
- A 扣 100
- B 加 100
如果第一步成功、第二步失败,就出大问题。
所以这两个操作必须放在一个事务里。
事务的核心目标:
- 保证数据一致性
- 避免只做一半
在 Spring 里最常见的写法是:
java
@Transactional
public void transfer() {
// 扣款
// 加款
}
简化原理:
- 方法执行前开启事务
- 方法内部多条数据库操作共享同一事务
- 如果执行成功,提交事务
- 如果发生符合规则的异常,回滚事务
4.5 @Transactional 为什么能生效
这背后仍然是 Spring 的 AOP 思想。
你可以先记住简化版:
- Spring 给目标对象创建代理
- 代理在调用方法前后插入事务逻辑
- 不是你代码自己显式写了 begin/commit/rollback
- 而是框架帮你织入了这层能力
这也是为什么事务问题经常和 AOP、代理、自调用失效有关。
5. 示例
5.1 最小可运行配置示例
Maven 依赖常见组合:
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
application.yml 示例:
yaml
spring:
datasource:
url: jdbc:mysql://localhost:3306/demo?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
configuration:
map-underscore-to-camel-case: true
5.2 表结构示例
sql
CREATE TABLE user (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) NOT NULL,
age INT NOT NULL
);
5.3 MyBatis 最小示例
实体类:
java
public class User {
private Long id;
private String name;
private Integer age;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
Mapper:
java
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
@Mapper
public interface UserMapper {
@Select("select id, name, age from user where id = #{id}")
User findById(Long id);
@Insert("insert into user(name, age) values(#{name}, #{age})")
int insert(User user);
}
Service:
java
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserService {
private final UserMapper userMapper;
public UserService(UserMapper userMapper) {
this.userMapper = userMapper;
}
public User getUser(Long id) {
return userMapper.findById(id);
}
@Transactional
public void createUser(User user) {
userMapper.insert(user);
}
}
Controller:
java
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {
return userService.getUser(id);
}
@PostMapping
public String createUser(@RequestBody User user) {
userService.createUser(user);
return "ok";
}
}
5.4 事务回滚示例
java
@Transactional
public void createTwoUsers() {
User u1 = new User();
u1.setName("a");
u1.setAge(18);
userMapper.insert(u1);
int x = 1 / 0;
User u2 = new User();
u2.setName("b");
u2.setAge(20);
userMapper.insert(u2);
}
如果事务生效,这个方法执行后:
- 第一条插入不会最终落库
- 因为中间抛出异常,整个事务回滚
6. 工程实践
6.1 真实工程场景
做"用户注册"时,常见业务流程可能是:
- 往
user表插入用户 - 往
user_profile表插入扩展资料 - 往
user_account表初始化账户
这 3 步要么都成功,要么都失败,所以通常放到一个事务里。
这就是事务在企业里最常见的落地方式。
6.2 MyBatis 在企业里的常见写法
- 简单 SQL 可以注解写在 Mapper 上
- 复杂 SQL 一般放到 XML
- Service 层负责事务和业务编排
- 分页通常配合分页插件或手写分页 SQL
当前阶段你先掌握:
@Mapper- 基本
select/insert/update/delete - Service 调 Mapper
@Transactional
6.3 明确操作步骤
你可以按这条练习路径走:
- 安装并启动 MySQL
- 创建一个
demo数据库 - 建
user表 - 在 Spring Boot 项目里引入 MyBatis 和 MySQL 驱动
- 配好
application.yml - 写
User实体类 - 写
UserMapper - 写
UserService - 写
UserController - 用 Postman/Apifox 测试查询和新增
- 再写一个故意抛异常的方法,验证事务回滚
6.4 企业里通常怎么选 MyBatis 和 JPA
常见经验判断:
- 业务 SQL 比较复杂、报表多、调优要求高,常偏向 MyBatis
- CRUD 很多、领域模型清晰、希望减少模板代码,常偏向 JPA
- 有些公司两者并用,但这要求团队规范更强
你面试时更稳的回答不是"哪个好",而是:
要看业务复杂度、团队习惯、SQL 控制诉求和维护成本。
7. 常见误区
-
以为会写 SQL 就等于会做 Spring Boot 数据访问。
不是,中间还有数据源、连接池、事务、映射框架。
-
以为
@Transactional一加就一定回滚。不是,异常类型、自调用、代理边界都会影响事务生效。
-
以为事务应该加在 Controller。
通常不推荐,事务边界更适合放在 Service 层。
-
以为数据库连不上一定是账号密码错。
还可能是端口、数据库没启动、驱动不匹配、URL 参数错误、防火墙、权限问题。
-
以为 MyBatis 比 JPA "低级"。
不是,它只是更偏显式 SQL 控制。
-
以为查不到数据一定是代码问题。
也可能是连错库、连错环境、事务未提交、SQL 条件不对。
-
以为连接池是可有可无的优化。
不是,在真实应用里它几乎是必需基础设施。
8. 面试题
- Spring Boot 是怎么自动创建数据源的?
- 什么是连接池?为什么要用连接池?
- MyBatis 和 JPA 有什么区别?你会怎么选?
@Mapper的作用是什么?- 事务是什么?为什么事务通常放在 Service 层?
@Transactional为什么能生效?- 哪些情况下事务可能不生效?
- 数据库连接失败时,你通常怎么排查?
9. 自测题
- 用你自己的话解释数据源、连接池、事务分别是什么。
- 为什么说 MyBatis 更适合初学阶段建立数据库直觉?
- 如果项目启动报数据库连接失败,你会按什么顺序排查?
- 为什么事务更适合加在 Service,而不是 Controller?
@Transactional和 Spring AOP 有什么关系?- 动手题:完成
GET /users/{id}的查库版本。 - 动手题:完成
POST /users的入库版本。 - 动手题:写一个插两张表的方法,中途故意抛异常,验证事务回滚。
- 动手题:故意把数据库密码改错,观察启动报错并提炼关键词。
- 动手题:故意让 SQL 表名写错,观察异常类型并记录定位思路。
10. 学完标志
学完这一阶段后,你应该能做到:
- 能说清 Spring Boot 连接数据库的大致链路。
- 能解释数据源、连接池、Mapper、事务分别在系统里的作用。
- 能用 MyBatis 写出基本的查库和入库代码。
- 能解释为什么事务通常写在 Service 层。
- 能从 AOP 代理角度理解
@Transactional的基本生效机制。 - 遇到数据库连接失败、SQL 执行失败、事务不回滚等问题时,知道第一轮该怎么排查。
- 面试时能说出 MyBatis 和 JPA 的取舍,而不是只会背定义。
下一阶段会进入核心机制,重点讲:Spring Boot 启动流程、自动装配条件、配置优先级、Bean 生命周期、常见扩展点。这一阶段会把你前面学到的"会用"真正连到"为什么这样设计"。
第五阶段:核心机制
1. 学什么
这一阶段的目标是把你前面已经会用的 Spring Boot 能力,统一串成一个"运行模型"。
重点学习:
- Spring Boot 应用启动时到底发生了什么
- 自动装配是怎么被触发的
- 自动装配为什么不是"无脑全配"
- 配置文件为什么能覆盖默认值,优先级怎么判断
- Bean 生命周期大致经过哪些阶段
- 常见扩展点有哪些,分别在什么场景下用
这一阶段不是要求你马上抠源码,而是先建立正确框架:
启动 -> 创建容器 -> 加载配置 -> 自动装配 -> 创建 Bean -> 注入依赖 -> 启动 Web 容器
必须吃透:
SpringApplication.run()的作用- 自动装配的条件化思想
- 配置优先级
- Bean 生命周期主干
- 为什么你能"覆盖默认配置"
先知道,后深入:
DeferredImportSelectorBeanFactoryPostProcessor和BeanPostProcessor全部细节EnvironmentPostProcessor- 自动装配导入源码链路的全部实现类
2. 为什么重要
前面几阶段你已经会:
- 写接口
- 注入 Bean
- 连数据库
- 开事务
但如果不知道核心机制,你会卡在这些典型问题上:
- 为什么某个配置明明写了却不生效
- 为什么某个 Bean 明明有依赖却没被创建
- 为什么引了依赖后功能突然自动可用了
- 为什么自己写的 Bean 可以覆盖 Spring Boot 默认 Bean
- 为什么有些扩展点写错时机会完全无效
这阶段会帮你从"使用者"升级到"能解释、能排错、能做设计判断"的层次。
3. 核心概念
3.1 先用直觉理解
你可以把 Spring Boot 应用启动理解成"搭一个会自己装配的工厂":
- 先把工厂本体建起来
- 读取配置和原材料
- 看你引入了哪些模块
- 满足条件的组件自动装进去
- 没写的用默认值
- 你自己手写的优先级通常更高
3.2 核心概念定义
| 概念 | 直觉理解 | 技术定义 |
|---|---|---|
SpringApplication |
启动总控器 | Spring Boot 提供的应用启动入口协调器 |
ApplicationContext |
容器本体 | 负责 Bean 管理、配置读取、事件发布等核心能力 |
| 自动装配 | 条件化默认安装 | 根据依赖、配置和上下文自动注册 Bean |
| 条件注解 | 装不装的开关 | 如 @ConditionalOnClass、@ConditionalOnMissingBean 等,决定配置是否生效 |
| Environment | 配置环境 | 封装配置属性、Profile、系统变量等信息 |
| Profile | 环境分组 | 用于区分 dev、test、prod 等不同环境配置 |
| Bean 生命周期 | Bean 的一生 | 从定义、实例化、注入、初始化到销毁的完整过程 |
| 扩展点 | 插件插口 | 允许你在不同阶段自定义 Spring Boot 行为的机制 |
3.3 常见条件注解直觉对比
| 注解 | 意思 | 常见用途 |
|---|---|---|
@ConditionalOnClass |
类路径里存在某个类才生效 | 依赖在时才装配某模块 |
@ConditionalOnMissingBean |
容器里没有这个 Bean 才生效 | 提供默认 Bean,但允许用户覆盖 |
@ConditionalOnBean |
容器里有某个 Bean 才生效 | 基于已有组件继续扩展 |
@ConditionalOnProperty |
配置项满足条件才生效 | 开关型配置 |
@ConditionalOnWebApplication |
当前是 Web 应用才生效 | 只在 Web 场景装配 MVC 相关组件 |
这几个注解是理解自动装配的关键入口。
你不一定现在就会写,但必须能看懂它们表达的设计意图。
4. 原理解释
4.1 SpringApplication.run() 到底做了什么
你平时看到的是:
java
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
但这行代码背后做的事并不少。
简化主干流程可以理解为:
main 方法
创建 SpringApplication
准备运行环境 Environment
创建 ApplicationContext
加载配置类和自动配置类
注册 BeanDefinition
实例化 Bean 并注入依赖
刷新容器 refresh()
启动内嵌 Web 容器
应用启动完成
你现在先抓住 4 个关键点:
- 创建环境对象,读取各种配置来源
- 创建 Spring 容器
- 加载你的配置类和自动配置类
- 完成 Bean 创建后启动应用
4.2 自动装配为什么会发生
Spring Boot 的便利,核心在于:
- 你引入了某个 Starter
- 这个 Starter 背后带来自动配置类
- 启动时 Spring Boot 会尝试加载这些自动配置类
- 只有条件满足时,自动配置里的 Bean 才会注册
所以自动装配的逻辑不是:
"我帮你全都配上。"
而是:
"如果你已经有这个场景需要的依赖和条件,我就给你装一个默认实现。"
4.3 为什么默认配置能被你覆盖
这是 Spring Boot 设计得很漂亮的一点。
很多自动配置类会这样写设计意图:
- 如果你没定义这个 Bean,我来给你一个默认的
- 如果你自己定义了,我就退后
这通常通过 @ConditionalOnMissingBean 表达。
所以:
- Spring Boot 提供默认值
- 你保留最终控制权
- 框架既方便,又不强制
这也是"约定优于配置"不是"禁止配置"的原因。
4.4 配置文件为什么会有优先级
Spring Boot 允许配置来自多个地方:
application.ymlapplication-dev.yml- 命令行参数
- 环境变量
- JVM 参数
- 外部配置文件
为什么要有优先级?
因为企业里经常需要:
- 同一份代码在不同环境使用不同配置
- 部署时临时覆盖某个参数
- 不把敏感信息写死在项目文件里
直觉上记住:
越外部、越临时、越运行时指定的配置,优先级通常越高。
4.5 Bean 生命周期大致分几步
先不要陷入全部细节,你先记主干:
- Bean 定义被注册
- Bean 被实例化
- 依赖注入
- 执行初始化回调
- Bean 可供业务使用
- 容器关闭时执行销毁回调
简化流程图:
注册 BeanDefinition
实例化
依赖注入
初始化
进入可用状态
容器关闭时销毁
为什么这很重要?
因为很多问题都跟"时机"有关:
- 你在 Bean 还没初始化完就用了它
- 你想修改 Bean,但修改时机太晚
- 你以为配置会影响 Bean,实际上 Bean 早就创建完了
4.6 常见扩展点是干什么的
先建立直觉,不求现在全会。
| 扩展点 | 大致用途 |
|---|---|
CommandLineRunner |
应用启动完成后执行一段初始化逻辑 |
ApplicationRunner |
和上面类似,但参数封装更友好 |
@ConfigurationProperties |
把配置批量绑定成对象 |
BeanPostProcessor |
在 Bean 初始化前后做增强 |
ApplicationListener |
监听应用生命周期事件 |
当前阶段,你最应该先掌握的是:
@ConfigurationPropertiesCommandLineRunner- 自动装配条件的阅读能力
5. 示例
5.1 配置优先级直觉示例
假设 application.yml 中有:
yaml
server:
port: 8080
如果你启动时传:
bash
--server.port=9090
最终通常会以运行时参数为准。
这体现的就是"外部覆盖内部"的设计。
5.2 @ConfigurationProperties 示例
java
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "app.user")
public class UserProperties {
private String defaultName;
private Integer defaultAge;
public String getDefaultName() {
return defaultName;
}
public void setDefaultName(String defaultName) {
this.defaultName = defaultName;
}
public Integer getDefaultAge() {
return defaultAge;
}
public void setDefaultAge(Integer defaultAge) {
this.defaultAge = defaultAge;
}
}
yaml
app:
user:
default-name: tom
default-age: 20
你可以把它理解成:
- 把零散配置映射成一个 Java 对象
- 以后业务里直接注入这个对象使用
- 比零散
@Value更适合工程项目
5.3 CommandLineRunner 示例
java
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
@Component
public class StartupRunner implements CommandLineRunner {
@Override
public void run(String... args) {
System.out.println("应用启动完成,开始执行初始化逻辑");
}
}
这个组件会在应用启动完成后执行。
常见用途:
- 打印启动信息
- 初始化演示数据
- 做启动后自检
5.4 覆盖默认 Bean 的直觉示例
假设某个自动配置类会在你没定义时提供一个默认组件。
如果你自己声明了同类型 Bean:
java
@Configuration
public class CustomConfig {
@Bean
public ObjectMapper objectMapper() {
return new ObjectMapper();
}
}
很多情况下,Spring Boot 默认配置会退后,让你自己的 Bean 生效。
这就是"默认配置可覆盖"。
6. 工程实践
6.1 真实工程场景
企业项目最常见的几种机制型需求:
- 开发、测试、生产使用不同数据库配置
- 应用启动后执行预热或初始化逻辑
- 某个组件需要默认实现,但允许项目按需替换
- 某些功能只在某个配置开关打开时才启用
这些需求背后,对应的都是你这一阶段学的机制:
- Profile
- 配置优先级
- 自动装配条件
- 扩展点
6.2 明确操作步骤
建议你亲手做这 5 个小实验:
- 写一个
application.yml,再用命令行参数覆盖其中一个值 - 新建
application-dev.yml和application-prod.yml,切换 Profile - 写一个
@ConfigurationProperties类,绑定自定义配置 - 写一个
CommandLineRunner,验证启动后逻辑执行 - 写一个自定义
@Bean,观察是否覆盖默认 Bean
6.3 工程方法论
初学者最容易学偏的点:
- 一上来就钻自动装配源码
- 背很多条件注解名字,但说不出设计目的
- 配置不生效时只会试错,不会判断优先级和加载来源
正确学习顺序应该是:
- 先理解"启动时容器要干什么"
- 再理解"自动装配为什么成立"
- 再理解"配置为什么能覆盖"
- 最后再看生命周期和扩展点
7. 常见误区
-
以为自动装配就是框架偷偷帮你做魔法。
不是,本质是条件判断 + 默认注册。
-
以为引了 Starter,就一定所有配置都生效。
不是,很多功能还依赖类路径、配置项、Web 环境等条件。
-
以为
application.yml一定是最终配置。不是,环境变量、命令行、外部配置都可能覆盖它。
-
以为 Bean 创建就是
new一下那么简单。不是,中间还有定义注册、依赖注入、初始化回调、后处理器等过程。
-
以为配置覆盖失败一定是写错键名。
有时是 Profile 没激活、优先级更高的配置覆盖了它,或 Bean 创建时机早于你预期。
-
以为扩展点越底层越高级。
不一定,工程上更重要的是选对时机和合适的扩展点。
8. 面试题
SpringApplication.run()大致做了哪些事情?- Spring Boot 自动装配是怎么实现"开箱即用"的?
- 自动装配为什么不会和用户自定义配置冲突?
@ConditionalOnMissingBean的作用是什么?- Spring Boot 配置优先级一般怎么理解?
- Bean 生命周期大致有哪些阶段?
@ConfigurationProperties和@Value有什么区别?CommandLineRunner和ApplicationRunner有什么用途?
9. 自测题
- 用你自己的话把 Spring Boot 启动主流程讲一遍。
- 为什么说自动装配本质上是"条件成立时提供默认实现"?
- 如果某个配置写在
application.yml里却不生效,你会从哪些角度排查? - 为什么你自定义的 Bean 往往能覆盖默认 Bean?
@ConfigurationProperties为什么比到处写@Value更适合工程项目?- 动手题:写一个
UserProperties配置类并成功绑定。 - 动手题:切换
dev和prodProfile,验证不同配置加载。 - 动手题:写一个
CommandLineRunner,打印一段启动日志。 - 动手题:给一个功能增加开关配置,并用条件化思想解释它为什么能按需启用。
10. 学完标志
学完这一阶段后,你应该能做到:
- 能从主干角度解释 Spring Boot 应用启动流程。
- 能说清自动装配依赖"条件成立 + 默认配置 + 用户可覆盖"的设计思想。
- 能理解配置来源不止一个,并能按优先级思路排查配置不生效问题。
- 能说明 Bean 生命周期的大致阶段以及"时机"为什么重要。
- 能使用
@ConfigurationProperties、Profile、CommandLineRunner这类常见机制型能力。 - 面试时能把 Spring Boot 的"方便"解释成可理解的工程设计,而不是抽象的"自动魔法"。
下一阶段会进入工程实践,重点讲:日志、Profile、多环境配置、测试、打包部署、监控、Actuator、项目结构和开发规范。这一阶段会把你从"知道原理"推进到"能按企业方式写项目"。
第六阶段:工程实践
1. 学什么
这一阶段解决的问题不是"框架会不会用",而是:
一个 Spring Boot 项目在企业里到底该怎么写、怎么测、怎么配、怎么跑、怎么管。
重点学习:
- 项目结构怎么组织更清晰
- 日志怎么打才有排查价值
- 多环境配置怎么管理
- 测试怎么做,单元测试和集成测试怎么区分
- 项目怎么打包和部署
- 运行中的应用怎么监控
- Actuator 是什么,有什么用
必须吃透:
- 日志
- Profile 与多环境配置
- 基础测试
- 打包部署
- Actuator 基础能力
先知道,后深入:
- 容器编排
- 分布式链路追踪
- 灰度发布
- 高级监控告警体系
- 大规模 CI/CD 细节
2. 为什么重要
很多人学 Spring Boot 时只学"能跑",但企业真正看重的是:
- 代码是否易维护
- 出问题能不能查
- 配置是否可控
- 发布是否稳定
- 上线后有没有可观测性
如果没有工程实践意识,常见后果是:
- 日志全是
System.out.println - 开发环境配置直接带进生产
- 改个端口、数据库地址都要改代码
- 上线后接口挂了,不知道看哪
- 没有测试,改一个点怕牵一片
所以这一阶段决定你是否开始像一个"能进项目"的工程师。
3. 核心概念
3.1 项目结构直觉
一个可维护的 Spring Boot 项目,通常不是把所有类都堆在一起,而是按职责分层。
常见结构:
text
src/main/java
├─ controller
├─ service
├─ mapper / repository
├─ entity / domain
├─ dto / vo
├─ config
├─ exception
└─ DemoApplication
src/main/resources
├─ application.yml
├─ application-dev.yml
├─ application-prod.yml
└─ mapper
3.2 核心概念定义
| 概念 | 直觉理解 | 技术定义 |
|---|---|---|
| 日志 Logging | 程序运行记录 | 用于记录程序行为、错误、关键业务过程的信息 |
| Profile | 环境切换器 | Spring 提供的多环境配置机制 |
| 单元测试 | 测一个小单元 | 聚焦某个类或方法,尽量少依赖外部系统 |
| 集成测试 | 测系统协作 | 多个组件一起验证整体行为 |
| 打包 | 生成可运行产物 | 通常生成 jar 包供部署运行 |
| 部署 | 让应用在线上跑起来 | 把构建产物放到目标环境并启动 |
| Actuator | 应用体检面板 | 提供健康检查、指标、配置信息等管理端点 |
| 可观测性 | 看得见系统状态 | 通过日志、指标、追踪等方式了解系统运行情况 |
3.3 日志级别先建立直觉
| 级别 | 常见用途 |
|---|---|
ERROR |
已发生错误,需要排查 |
WARN |
有风险或非预期,但程序未必挂 |
INFO |
关键业务流程、启动信息 |
DEBUG |
调试细节,开发阶段常用 |
TRACE |
更细粒度跟踪,一般不常开 |
3.4 多环境配置最容易混的点
| 文件/方式 | 作用 |
|---|---|
application.yml |
通用默认配置 |
application-dev.yml |
开发环境配置 |
application-test.yml |
测试环境配置 |
application-prod.yml |
生产环境配置 |
spring.profiles.active |
指定当前启用哪个环境 |
一句话记住:
- 通用项放默认配置
- 差异项放环境配置
- 敏感信息尽量不要硬编码进仓库
4. 原理解释
4.1 为什么日志不能只靠打印
很多初学者一开始爱写:
java
System.out.println("user created");
它的问题是:
- 没有统一格式
- 不能方便区分级别
- 不利于检索和聚合
- 线上排查很痛苦
日志框架的设计思想是:
- 统一输出格式
- 区分级别
- 可以控制输出位置和输出量
- 便于后续接入日志平台
4.2 为什么要做多环境配置
同一个项目在不同环境里的差异通常很多:
- 数据库地址不同
- Redis 地址不同
- 日志级别不同
- 某些开关在生产要关闭
如果你把这些都写死,部署会非常危险。
所以 Spring Boot 用 Profile 帮你做环境切分。
设计思想:
- 代码尽量一套
- 环境差异配置化
- 发布时只切环境,不改代码
4.3 为什么测试要分层
不是所有测试都该直接启动整个项目。
- 单元测试更快,适合验证一个类的逻辑
- 集成测试更真实,适合验证接口、数据库、容器协作
这背后的设计思想是:
- 快速反馈和真实验证都要有
- 不同层的问题应该用不同粒度的测试抓
4.4 Spring Boot 为什么适合打包部署
Spring Boot 很适合工程化交付,因为:
- 自带内嵌 Tomcat
- 可以直接打成可运行
jar - 部署时只要有 JDK,就能启动
这大大降低了传统 Java Web 项目的部署复杂度。
4.5 Actuator 的设计思想
应用上线后,最怕的是:
- 你不知道它活着没有
- 你不知道连接池满没满
- 你不知道配置是不是按预期加载
- 你不知道线程、内存、请求情况
Actuator 提供的就是"应用自我暴露管理信息"的能力。
它本质上是在应用内部提供一些管理端点。
5. 示例
5.1 日志正确写法示例
java
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@Slf4j
@Service
public class UserService {
public void createUser(String name) {
log.info("开始创建用户, name={}", name);
try {
// 业务逻辑
log.info("用户创建成功, name={}", name);
} catch (Exception e) {
log.error("用户创建失败, name={}", name, e);
throw e;
}
}
}
如果你暂时不用 Lombok,也可以:
java
private static final Logger log = LoggerFactory.getLogger(UserService.class);
5.2 多环境配置示例
application.yml
yaml
spring:
profiles:
active: dev
application-dev.yml
yaml
server:
port: 8080
logging:
level:
root: info
application-prod.yml
yaml
server:
port: 8081
logging:
level:
root: warn
5.3 基础测试示例
Service 单元测试直觉示例:
java
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
class MathServiceTest {
@Test
void add_shouldReturnSum() {
MathService service = new MathService();
assertEquals(3, service.add(1, 2));
}
}
Spring Boot 集成测试示例:
java
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class DemoApplicationTests {
@Test
void contextLoads() {
}
}
5.4 打包与运行示例
Maven 打包:
bash
mvn clean package
运行 jar:
bash
java -jar target/demo-0.0.1-SNAPSHOT.jar
指定环境运行:
bash
java -jar target/demo-0.0.1-SNAPSHOT.jar --spring.profiles.active=prod
5.5 Actuator 示例
引入依赖:
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
配置:
yaml
management:
endpoints:
web:
exposure:
include: health,info
常见端点:
/actuator/health/actuator/info
访问 /actuator/health 可以快速查看应用健康状态。
6. 工程实践
6.1 真实工程场景
你接手一个用户系统,企业里常见要求是:
- 开发、测试、生产三套环境分开
- 用户新增接口失败时,要能从日志中定位
- 发版时打包成 jar,服务器直接启动
- 运维要有健康检查接口
- 基础业务逻辑要有测试兜底
这时候你会用到:
- Profile
- 规范日志
- Maven 打包
- Actuator
- JUnit + Spring Boot Test
6.2 推荐的工程规范
必须掌握:
- 不用
System.out.println代替日志 - 不把环境差异写死在代码里
- 不把 Controller、Service、Mapper 混写
- 每次改核心逻辑至少补最基础测试
- 线上服务至少暴露健康检查端点
更像中级工程师的做法:
- 日志打印关键上下文,不打无意义废话
- 错误日志要带异常栈
- 配置项集中管理,避免满地
@Value - 对外接口、配置、部署命令都可复现
6.3 明确操作步骤
你可以做这一组工程化练习:
- 给现有项目补上
application-dev.yml和application-prod.yml - 给核心 Service 加日志
- 写一个最基础的单元测试
- 写一个
@SpringBootTest测试验证容器能启动 - 引入 Actuator 并开放
health - 用 Maven 打包
- 本地通过
java -jar启动一次 - 用不同 Profile 分别运行验证配置差异
6.4 企业里通常怎么用
- 开发环境更关注调试便利,日志级别会更详细
- 测试环境更接近生产,但常保留测试数据
- 生产环境更强调稳定、安全、可观测性
- 发布一般不会手改代码,而是切配置和部署参数
- 健康检查通常会接入负载均衡、监控平台或容器平台
7. 常见误区
-
以为日志越多越好。
不是,日志要有信息密度和上下文,不是把所有变量都乱打一遍。
-
以为开发环境配置能直接搬到生产。
非常危险,尤其是数据库地址、日志级别、调试开关。
-
以为测试就是写个
contextLoads()。这只是最基础的容器加载测试,不代表业务逻辑就安全。
-
以为能
mvn package就算会部署。真正工程里还要考虑环境变量、日志路径、启动参数、健康检查。
-
以为 Actuator 只是"顺手加一个依赖"。
它是应用可观测性的基础入口之一。
-
以为日志只在报错时才打。
关键流程、关键输入、关键结果也需要合理记录。
8. 面试题
- Spring Boot 项目常见的工程目录如何划分?
- 为什么不推荐使用
System.out.println打日志? - Spring Boot 如何做多环境配置?
application.yml和application-prod.yml是什么关系?- 单元测试和集成测试有什么区别?
- Spring Boot 项目通常怎么打包和运行?
- 什么是 Actuator?常见端点有哪些?
- 线上问题排查时,日志、健康检查、配置管理分别起什么作用?
9. 自测题
- 你会如何设计一个基础的 Spring Boot 项目目录结构?
- 如果要排查"用户注册失败",你希望日志里至少出现哪些信息?
- 为什么多环境配置不能靠复制三份项目来解决?
@SpringBootTest适合解决什么问题?- 如果服务打成 jar 后启动失败,你会从哪些方向排查?
- 动手题:给项目加上
dev和prod两套配置。 - 动手题:为某个 Service 补充规范日志。
- 动手题:补一个基础单元测试和一个集成测试。
- 动手题:引入 Actuator,并验证
/actuator/health可访问。 - 动手题:本地打包并通过
java -jar启动一次项目。
10. 学完标志
学完这一阶段后,你应该能做到:
- 能按常见企业分层方式组织 Spring Boot 项目结构。
- 能用规范日志替代随意打印,并知道关键日志应该打什么。
- 能用 Profile 管理多环境配置。
- 能写最基础的单元测试和 Spring Boot 集成测试。
- 能把项目打成 jar 并用命令行运行。
- 能引入 Actuator 并理解健康检查的意义。
- 对"项目上线后怎么观察、怎么排查、怎么切环境"有基本工程直觉。
下一阶段会进入常见问题与排错,重点讲:启动失败、Bean 注入失败、端口冲突、配置不生效、事务不生效、数据库连不上、接口报错时该怎么系统定位。这一阶段会把你的知识真正变成排障能力。
第七阶段:常见问题与排错
1. 学什么
这一阶段的核心目标是:
把你从"知道知识点"训练成"遇到问题能定位"的人。
重点学习:
- Spring Boot 项目启动失败时怎么查
- Bean 注入失败怎么查
- 端口冲突怎么查
- 配置不生效怎么查
- 数据库连不上怎么查
- 事务不生效怎么查
- 接口 404、400、405、415、500 怎么查
- 如何建立一套通用排障方法论
必须吃透:
- 看异常要先看根因
- 先缩小问题层级,再定位细节
- 配置、扫描路径、依赖、代理、环境,是高频故障源
- 日志和报错关键词比盲猜更重要
先知道,后深入:
- 线程 dump
- JVM 级问题定位
- 线上性能分析器
- 复杂中间件故障协同排查
2. 为什么重要
真实工作里,框架的价值不只体现在"会写",更体现在:
- 服务起不来你能不能救
- 接口报错你能不能快速缩圈
- 某个配置不生效你能不能找准原因
- 别人说"事务失效了",你是不是知道先看哪里
面试官也很爱问这类问题,因为它最能区分:
- 只做过教程的人
- 真正在项目里排过问题的人
这一阶段你要学会的是:系统化排查,而不是凭感觉乱试。
3. 核心概念
3.1 排障的本质
排错不是"记住所有异常",而是做两件事:
- 先判断问题出在哪一层
- 再在那一层里找关键证据
Spring Boot 常见问题大致可分 6 层:
| 层 | 常见问题 |
|---|---|
| 启动层 | 应用起不来、类加载失败、配置错误 |
| 容器层 | Bean 注册失败、注入失败、循环依赖 |
| Web 层 | 404、405、400、415、参数绑定异常 |
| 数据层 | 数据库连不上、SQL 报错、事务失效 |
| 配置层 | Profile 错、配置没加载、优先级覆盖 |
| 运行层 | 日志不足、环境差异、线上行为和本地不同 |
3.2 通用排查模型
你以后看到问题,优先按这个顺序思考:
- 现象是什么
- 发生在启动时还是运行时
- 属于哪一层
- 日志里最底层的异常是什么
- 最近改了什么
- 哪个最小实验可以快速验证猜想
一句话总结:
先分类,再看根因,再做最小验证。
3.3 常见错误类型对比
| 现象 | 常见根因 |
|---|---|
| 启动失败 | 配置错误、依赖缺失、Bean 创建失败、数据库连接失败 |
NoSuchBeanDefinitionException |
容器里没有这个 Bean |
BeanCreationException |
Bean 在创建过程中出错 |
| 404 | 路径没匹配到 |
| 405 | 请求方法不匹配 |
| 400 | 参数绑定失败、请求格式错误 |
| 415 | Content-Type 不支持 |
| 数据库连接失败 | URL、账号密码、端口、驱动、网络问题 |
| 事务不回滚 | 代理未生效、异常类型不对、自调用问题 |
4. 原理解释
4.1 为什么看异常要看"最底层根因"
很多 Spring Boot 异常会一层包一层:
- 表面看到的是
BeanCreationException - 往下翻可能是
UnsatisfiedDependencyException - 再往下可能才是真正根因,比如数据库连不上
所以排查时不要只看第一行异常名。
真正有价值的是:
Caused by- 最底层异常
- 关键报错关键词
这就是为什么很多人看了半天 Spring 异常,越看越晕。
因为他们在看"包装层",不是"根因层"。
4.2 为什么要先判断问题属于哪一层
比如一个接口请求失败,可能是:
- 路径没映射到
- 参数没绑定上
- Service 抛异常
- 数据库查挂了
- 配置指向错环境
如果你不先分层,就会:
- 一会改 Controller
- 一会改配置
- 一会改 SQL
- 最后全改乱了
正确做法是先问自己:
- 请求有没有到 Controller
- 参数是否绑定成功
- Service 是否执行到
- SQL 是否发出
- 事务是否提交
4.3 排障为什么要做"最小验证"
工程上最忌讳一次改很多地方。
因为这样你不知道究竟是哪个修改解决了问题。
最小验证的意思是:
- 先只改一个最可能的点
- 再验证现象是否变化
- 让问题缩小,而不是扩大
这是一种非常重要的工程习惯。
5. 示例
5.1 启动失败排查示例
常见报错:
text
APPLICATION FAILED TO START
第一轮排查顺序:
- 看最底部
Caused by - 判断是配置问题、Bean 问题还是数据库问题
- 看最近改动了依赖、配置还是代码
- 如果涉及数据库,先确认数据库本身能否连通
常见根因示例:
- 端口被占用
- 数据库账号密码错
- Bean 注入失败
- 某个配置类写错
5.2 Bean 注入失败示例
典型异常:
text
NoSuchBeanDefinitionException
第一轮排查顺序:
- 这个类有没有加
@Component/@Service/@Repository/@Controller - 它是不是在启动类扫描路径下
- 是不是接口注入但没有对应实现类
- 是不是有多个同类型 Bean 导致歧义
- 是不是这个对象根本不该靠扫描,而应该用
@Bean
高频根因:
- 包路径不在扫描范围
- 少了注解
- 注入的是接口,但没有实现
- 有多个实现但没指定
5.3 端口冲突示例
常见报错关键词:
text
Port 8080 was already in use
排查思路:
- 改配置换端口
- 查哪个进程占了端口
- 关掉占用进程或改当前服务端口
你要建立一个习惯:
看到这类报错,不要怀疑代码,先怀疑环境。
5.4 配置不生效示例
比如你明明写了:
yaml
server:
port: 8081
结果服务还是跑在 8080。
第一轮排查顺序:
- 当前激活的是哪个 Profile
- 是否有更高优先级配置覆盖
- 配置键名是否写对
- 配置文件是否真的被加载
- 是否写到了错误文件位置
高频原因:
application-prod.yml覆盖了默认值- 启动参数传了
--server.port=8080 - 当前并没有启用你以为的环境
5.5 数据库连接失败示例
常见报错关键词:
text
Communications link failure
Access denied for user
Unknown database
第一轮排查顺序:
- 数据库服务是否启动
- IP、端口、库名是否正确
- 用户名密码是否正确
- 驱动版本是否匹配
- 本机能否手工连上数据库
一个很重要的习惯:
先验证数据库本身能连,再怀疑 Spring Boot 配置。
5.6 事务不生效示例
典型现象:
- 方法里抛异常了
- 但前面的数据库操作没有回滚
第一轮排查顺序:
- 方法是否加了
@Transactional - 方法是不是
public - 是否通过 Spring 代理调用,而不是类内部自调用
- 抛出的异常类型是否会触发回滚
- 数据库表引擎是否支持事务
高频根因:
- 自调用导致代理失效
- 抛的是受检异常但没配置回滚规则
- 根本没走 Spring 管理的 Bean
5.7 Web 接口错误排查示例
404
排查顺序:
- 路径是否写对
- Controller 是否被扫描到
- 映射注解是否写对
- 类级别路径和方法级别路径拼起来是不是正确
405
排查顺序:
- 路径通常是对的
- 先看 HTTP 方法是否错了
- 比如接口定义了
@PostMapping,你却发了 GET
400
排查顺序:
- 参数名是否匹配
- 参数类型能否转换
- JSON 结构是否符合对象字段
- 必填参数是否缺失
415
排查顺序:
Content-Type是否为application/json- 是否用
@RequestBody接 JSON - 前端是否真的按 JSON 发送
500
排查顺序:
- 先看后端异常栈
- 判断是业务异常、空指针、SQL 问题还是外部服务问题
- 不要只看状态码,状态码只能告诉你"服务器出错了"
6. 工程实践
6.1 真实工程场景
你在公司里最常遇到的不是"从零写一个框架",而是:
- 拉下项目启动失败
- 某接口昨天还能用今天 500
- 开发环境正常,测试环境不正常
- 加了
@Transactional却没回滚 - 改完配置后服务行为完全不符合预期
这时候最重要的不是记忆力,而是排查顺序。
6.2 推荐排查方法论
必须掌握:
- 先看日志和异常,不凭感觉猜
- 先看根因异常,不只看表层异常
- 先判断问题层次
- 先做最小验证
- 先排高频问题:路径、注解、配置、环境、依赖、代理
6.3 一个通用排障模板
以后你可以套这个模板:
- 现象:具体报什么错,何时发生
- 范围:只在本地、测试、生产,还是所有环境
- 分层:启动层、Web 层、数据层、配置层、事务层
- 证据:日志关键词、异常栈、HTTP 状态码、SQL 日志
- 假设:最可能的 2 到 3 个原因
- 验证:做最小改动或最小复现
- 结论:根因是什么,如何修复,如何防止复发
6.4 明确操作步骤
建议你做一组"故意制造错误"的练习:
- 故意改错数据库密码
- 故意让端口冲突
- 故意把 Service 放出扫描范围
- 故意把
@PostMapping接口用 GET 调 - 故意发错
Content-Type - 故意制造事务自调用失效
- 每次都按"现象 -> 根因 -> 修复 -> 复盘"记录
这组训练非常有价值,比只看教程强很多。
7. 常见误区
-
看到异常第一反应是上网搜整段报错。
可以搜,但先自己分层和提炼关键词,不然容易被带偏。
-
只看第一行异常。
Spring 报错经常层层包装,最关键的常常在最后一个
Caused by。 -
问题一来先改很多地方。
这样很容易把简单问题改复杂,还失去定位依据。
-
默认相信"代码没问题,一定是环境"。
也不能这样,应该用证据判断,不要先入为主。
-
事务不生效就只盯着
@Transactional。很多时候是代理、自调用、异常类型、调用路径的问题。
-
看到 404 就重启项目。
404 更多时候是路径映射和请求地址问题,不一定和启动有关。
8. 面试题
- Spring Boot 项目启动失败时,你通常怎么排查?
NoSuchBeanDefinitionException常见原因有哪些?- 配置不生效时你会优先看什么?
- 数据库连接失败时有哪些高频根因?
- 为什么
@Transactional有时候不生效? - 404、405、400、415、500 分别代表什么,怎么排查?
- 你如何区分问题发生在 Web 层、Service 层还是数据层?
- 遇到复杂异常栈时,你如何提取真正根因?
9. 自测题
- 用你自己的话总结一套 Spring Boot 通用排障流程。
- 如果项目启动时报
NoSuchBeanDefinitionException,你会怎么查? - 如果接口调用返回 415,你会从哪些点定位?
- 如果事务没有回滚,你会优先排查哪几个方向?
- 为什么说"先分类,再看根因,再做最小验证"是排障核心方法?
- 动手题:制造一个 Bean 注入失败并修复。
- 动手题:制造一个端口冲突并修复。
- 动手题:制造一个 400 或 415 并记录分析过程。
- 动手题:制造一个数据库连接失败并提炼日志关键词。
- 动手题:制造一个事务不生效场景并解释原因。
10. 学完标志
学完这一阶段后,你应该能做到:
- 遇到 Spring Boot 常见错误时,不会立刻慌,而是能先分层判断。
- 能从异常栈里提取根因,而不是只看表层异常。
- 能系统排查 Bean 注入失败、配置不生效、数据库连接失败、事务不生效等高频问题。
- 能根据 404、405、400、415、500 快速缩小问题范围。
- 能把"修好问题"升级为"总结问题、减少复发"的工程习惯。
- 面试时能把排障思路讲成一套方法,而不是零散经验。
下一阶段会进入面试与评估,重点讲:高频面试考点、典型问法、答题层次、项目题怎么答、如何判断自己已经达到初级到中级水平。这一阶段会把你前面的知识整理成可输出、可评估的能力。
第八阶段:面试与评估 + 学习总结与知识闭环
1. 学什么
这一阶段不再新增太多零散知识点,而是做三件最重要的事:
- 把前面学过的内容压缩成可输出的面试答案
- 建立一套可执行的评估体系,判断自己现在处在哪个水平
- 把知识整理成"会解释、会做题、会实战、会复盘"的闭环
这一阶段的目标是:
从"我学过 Spring Boot"变成"我能证明我会 Spring Boot"。
重点内容:
- 高频面试考点怎么答
- 项目题、原理题、排错题常见问法
- 入门、初级、中级的达标标准
- 自测题、面试题、实战任务、项目任务
- 评分标准
- 根据错误结果如何反向补课
必须吃透:
- 能用自己的话解释核心概念
- 能把原理和实战联系起来
- 能把排障方法讲成结构化答案
- 能把学过的内容组织成项目表达
先知道,后深入:
- 高级架构设计题
- 大规模微服务治理题
- 深层源码追踪型面试题
2. 为什么重要
很多人学完一堆知识,最后还是卡在两个地方:
- 面试时说不出来
- 实际做项目时不知道自己到底会到什么程度
这说明问题不是"没学",而是:
- 知识没有形成主线
- 没有做输出训练
- 没有明确的评估标准
企业面试官通常不会只问"定义是什么",而会继续追问:
- 为什么这样设计
- 实际项目里怎么用
- 出问题怎么排查
- 你是怎么权衡的
所以这一阶段的重点不是背题,而是把知识转成"结构化表达能力"。
3. 核心概念
3.1 Spring Boot 面试最常考的几类题
| 类型 | 常见问法 |
|---|---|
| 基础定义题 | Spring Boot 是什么?和 Spring 有什么关系? |
| 核心机制题 | 自动装配怎么实现?配置为什么能覆盖默认值? |
| Web 开发题 | 请求是怎么进 Controller 的?@RequestBody 和 @RequestParam 区别? |
| 数据访问题 | MyBatis 和 JPA 怎么选?事务为什么能回滚? |
| 排障题 | 事务不生效怎么办?配置不生效怎么办? |
| 工程实践题 | 多环境怎么配?日志怎么打?项目怎么部署? |
| 项目题 | 你做过什么项目?Spring Boot 在里面承担什么角色? |
3.2 一个好答案通常长什么样
一个质量较高的 Spring Boot 面试答案,通常有 4 层:
- 先给一句结论
- 再讲核心定义
- 再讲原理或设计思想
- 最后结合项目场景或排障经验
例如回答"什么是 Spring Boot"时,不要只说:
"它是一个 Java 框架。"
更好的结构是:
- Spring Boot 是建立在 Spring 之上的快速开发框架
- 它通过 Starter 和自动装配降低配置成本
- 核心思想是约定优于配置和条件化默认装配
- 企业里常用它快速搭建 Web 服务、管理系统和微服务基础骨架
3.3 学习闭环的本质
真正有效的学习闭环,不是"看完了",而是做到这 4 件事:
- 能解释
- 能写代码
- 能排错
- 能复盘和补短板
如果缺少其中任何一个,知识都不牢。
4. 原理解释
4.1 为什么面试很爱问 Spring Boot
因为 Spring Boot 非常适合作为"综合能力探针"。
面试官通过它可以顺着一条线判断你:
- Java 基础是否扎实
- 是否理解 IoC / AOP
- 是否会 Web 开发
- 是否会数据库和事务
- 是否有工程实践经验
- 是否真的排过问题
所以 Spring Boot 面试看起来像在问框架,实际上是在问:
你是不是一个能独立做后端业务的人。
4.2 为什么"能答原理"比"背注解"更重要
因为注解只是表象,原理才决定你能不能:
- 解释为什么能跑
- 解释为什么失效
- 解释为什么这么设计
- 在新场景里做迁移
比如:
- 会背
@Transactional不够 - 你要知道它依赖代理和 AOP
- 你要知道自调用可能失效
- 你要知道事务边界为什么通常放在 Service
这就是从"会用"到"会解释"的差别。
4.3 为什么要按等级评估自己
因为"我会 Spring Boot"这句话太模糊。
更有用的说法应该是:
- 我现在达到入门还是初级
- 哪些是稳定会的
- 哪些是知道但做不稳
- 哪些是面试时还答不完整
这会让你的学习更像工程迭代,而不是模糊堆积。
5. 示例
5.1 高频面试题示例答案模板
题目:Spring Boot 为什么能做到开箱即用?
推荐答法:
- 结论:Spring Boot 通过 Starter 和自动装配实现开箱即用。
- Starter 负责把某个场景需要的依赖组合好,比如 Web、数据库。
- 自动装配会在启动时根据类路径、配置项、是否已有 Bean 等条件,自动注册一批默认组件。
- 它不是无脑全配,而是条件成立才配置,同时用户还可以通过自定义 Bean 和配置覆盖默认行为。
- 在项目里,这让我们能快速启动一个 Web 服务,而不需要像传统 Spring 那样写大量样板配置。
5.2 项目题回答模板
题目:你在项目里是怎么用 Spring Boot 的?
推荐答法结构:
- 项目背景:这是一个什么系统
- 技术角色:Spring Boot 在系统里负责什么
- 你负责的模块
- 关键技术点:比如接口开发、事务、MyBatis、多环境配置、日志
- 你解决过的一个问题
- 你的思考或优化
示例框架:
text
我做的是一个用户管理和订单处理的后台系统,Spring Boot 主要作为整个服务的基础框架。
我负责用户模块和订单模块的接口开发,主要用了 Spring MVC 做 REST API,MyBatis 做数据库访问,Service 层用事务保证多表操作一致性。
工程上我们通过 Profile 管理多环境配置,用日志和 Actuator 做基础排查。
我印象比较深的是处理过一次事务不生效的问题,最后定位是类内部自调用导致代理没生效。
5.3 自我表达训练示例
你可以把每个核心主题都练成"30 秒版"和"3 分钟版"。
例如"自动装配":
30 秒版:
- 自动装配是 Spring Boot 根据依赖、配置和上下文条件,自动注册默认 Bean 的机制。
- 核心思想是条件成立才配置,并允许用户覆盖默认实现。
- 它让项目能快速开箱即用。
3 分钟版:
- 先解释 Starter 和自动装配关系
- 再解释条件注解
- 再讲
@ConditionalOnMissingBean为什么能让用户覆盖 - 最后讲一个 Web 或数据源场景
6. 工程实践
6.1 入门 / 初级 / 中级达标标准
入门达标标准
达到以下标准,说明你已经入门:
- 能独立创建 Spring Boot 项目并启动
- 能写基础 REST 接口
- 能解释 Spring、Spring MVC、Spring Boot 的关系
- 知道 Bean、IoC、DI 是什么
- 会基础
application.yml配置
初级达标标准
达到以下标准,说明你已经具备初级开发能力:
- 能完成 Controller + Service + Mapper 的基础业务链路
- 能接 MySQL,写基本 CRUD
- 能使用事务并解释基本原理
- 能做多环境配置
- 能写基础测试
- 能排查常见 404、400、Bean 注入失败、数据库连接失败
中级达标标准
达到以下标准,说明你已经接近中级工程师理解深度:
- 能解释 Spring Boot 启动流程和自动装配主干机制
- 能解释配置优先级、Bean 生命周期、条件装配思想
- 能系统排查事务失效、配置失效、复杂启动失败
- 能讲清 MyBatis/JPA 取舍、事务边界设计、工程分层规范
- 能把项目经验组织成结构化表达
- 能从工程角度讨论日志、部署、监控、可维护性
6.2 一套自测题
- Spring Boot 和 Spring Framework 的关系是什么?
- 什么是 Bean?什么是 IoC 和 DI?
@Component和@Bean有什么区别?- 一个请求是如何到达 Controller 的?
@RequestParam、@PathVariable、@RequestBody分别适合什么场景?- MyBatis 和 JPA 有什么区别?
@Transactional为什么能生效?- Spring Boot 启动流程大致是什么?
- 自动装配为什么能做到开箱即用?
- 配置不生效时你会怎么排查?
6.3 一套面试题
- Spring Boot 的核心优势是什么?
@SpringBootApplication做了什么?- Starter 和自动装配分别是什么?
- 为什么构造器注入比字段注入更推荐?
- 事务为什么一般写在 Service 层?
- 为什么事务有时不生效?
- Spring MVC 中
DispatcherServlet的作用是什么? - 为什么 Controller 返回对象能自动变成 JSON?
- 多环境配置一般怎么做?
- 你在项目里怎么做日志、监控和排错?
6.4 一套实战任务
建议你做一个"用户管理 + 订单管理"练习系统,至少完成:
- 用户新增、查询、修改、删除接口
- 订单新增、查询接口
- MyBatis 接 MySQL
- Service 层事务
- Profile 区分 dev/prod
- 日志
- 基础测试
- Actuator 健康检查
6.5 一套项目任务
如果你想从"练习项目"再往上走一步,建议做这个项目:
项目名建议:springboot-mall-lite
模块建议:
- 用户模块
- 商品模块
- 订单模块
- 登录鉴权基础版
- 全局异常处理
- 统一返回结构
- 配置分环境
- 日志和健康检查
项目目标:
- 能跑通完整业务链路
- 能展示分层设计
- 能体现事务、配置、日志、排障能力
- 能拿来做简历项目和面试讲解素材
6.6 评分标准
你回答问题或完成任务时,可以按这个标准自评:
| 维度 | 分值 | 评价标准 |
|---|---|---|
| 概念准确性 | 20 | 定义是否正确,有无混淆 |
| 原理理解 | 20 | 能否说明为什么这样设计 |
| 实战能力 | 20 | 能否写代码、跑通场景 |
| 排障能力 | 20 | 能否定位并解释常见问题 |
| 表达结构 | 10 | 回答是否有层次 |
| 工程意识 | 10 | 是否考虑日志、测试、配置、部署 |
评分参考:
- 90-100:已接近中级理解深度
- 75-89:初级较扎实,可继续补机制和项目表达
- 60-74:入门到初级过渡期,主线已建立但不稳定
- 60 以下:建议回到核心概念、Web、数据访问三条主线重练
7. 常见误区
-
以为面试就是背八股。
真正好的回答一定有"定义 + 原理 + 场景 + 排障"。
-
以为能看懂笔记就等于会。
没有输出训练,面试时通常说不出来。
-
以为刷很多题就会了。
如果没有项目和代码支撑,答案很容易空。
-
以为自己"差不多会了",但没有明确标准。
没有达标标准,就很难知道下一步补什么。
-
只练短答案,不练结构化表达。
面试官一追问,你就容易断层。
-
只看正确答案,不记录错误原因。
错误结果本身就是最好的补课入口。
8. 面试题
- Spring Boot 相比传统 Spring 的核心价值是什么?
- 你如何理解 IoC、DI、AOP 在 Spring Boot 项目中的作用?
- 自动装配的设计思想是什么?
- 为什么 Spring Boot 能快速启动一个 Web 项目?
- 一个请求从进入系统到返回 JSON,经历了哪些关键组件?
- 事务为什么可能失效?你实际会怎么查?
- Spring Boot 项目如何管理多环境配置?
- 你在项目里如何组织分层、日志和监控?
- MyBatis 和 JPA 你会怎么选?
- 如果让你从零搭一个 Spring Boot 项目,你会怎么做?
9. 自测题
- 用 1 分钟介绍 Spring Boot。
- 用 2 分钟解释自动装配。
- 用 2 分钟解释事务为什么能回滚。
- 用 2 分钟讲一个请求进入 Controller 的流程。
- 用 2 分钟讲一次你如何排查"配置不生效"问题。
- 用 3 分钟讲一个你可以独立完成的 Spring Boot 小项目。
- 现场实操题:从零起一个用户管理接口。
- 现场实操题:接数据库并完成新增 + 查询。
- 现场实操题:故意制造一个事务问题并解释。
- 复盘题:你目前最薄弱的 3 个点是什么,准备怎么补?
10. 学完标志
学完这一阶段后,你应该能做到:
- 能把 Spring Boot 的核心知识压缩成结构化面试答案。
- 能判断自己目前是入门、初级还是接近中级。
- 能用自测题、面试题、实战任务、项目任务来验证真实水平。
- 能按评分标准判断自己弱在概念、原理、实战、排障还是表达。
- 能根据错误结果反向补课,而不是盲目重复学习。
- 已经形成完整知识闭环:会理解、会解释、会上手、会排错、能应对基础到中级面试。
错误结果如何反向补课
这是整套学习里最重要的"复盘机制"。
如果你错在概念定义
回补顺序:
- 重新看第二阶段核心概念
- 把 IoC、DI、Bean、Starter、自动装配重新用自己的话解释一遍
- 要求自己能不看笔记口述
如果你错在 Web 主线
回补顺序:
- 回到第三阶段
- 重写
@PathVariable、@RequestParam、@RequestBody示例 - 重新画一遍请求处理流程
如果你错在数据访问和事务
回补顺序:
- 回到第四阶段
- 重做 MyBatis CRUD 和事务回滚实验
- 重点解释事务为什么能生效、为什么会失效
如果你错在机制题
回补顺序:
- 回到第五阶段
- 重点复习启动流程、条件装配、配置优先级
- 把"自动装配为什么不是魔法"重新讲一遍
如果你错在排障题
回补顺序:
- 回到第七阶段
- 做"故意制造错误"的训练
- 每次按"现象 -> 分层 -> 根因 -> 验证 -> 修复"写复盘
如果你错在表达
回补顺序:
- 每个主题准备 30 秒版、1 分钟版、3 分钟版
- 录音复述
- 删掉空话,只保留"定义 + 原理 + 场景 + 排障"
Spring Boot 学习总结
到这里,你应该已经把 Spring Boot 最重要、最常用、最有实战价值的 80% 主干内容串起来了。
你现在的知识地图应该是:
- 知道 Spring Boot 是建立在 Spring 之上的快速开发框架
- 理解 IoC、DI、Bean、自动装配、配置优先级这些底层主线
- 会写 Web 接口
- 会接数据库、会做事务
- 会做基本工程实践
- 会做基础排障
- 能应对基础到中级面试中的高频问题
下一步深入方向
如果你要继续进阶,建议按这个顺序:
- Spring Security
- 全局异常处理与统一响应封装
- 参数校验
Validation - Redis
- 缓存设计
- 消息队列
- Spring Cloud / 微服务
- Spring 源码与自动装配源码
一句话总结这门技术的学习本质:
Spring Boot 不是背注解,而是理解"容器如何管理对象、框架如何按条件装配能力、工程系统如何稳定运行"。