【个人学习||spring boot】

Spring Boot 的本质:它不是"新的 Java 语言",也不是"替代 Spring 的东西",而是一个帮助你更快搭建、运行、配置、交付 Spring 应用 的工程化框架。

你可以把它理解成:Spring 提供能力,Spring Boot 把这些能力按企业开发常见场景"预组装"好了。

  1. 它是干什么的

    用更少配置,快速开发 Web 接口、后台服务、管理系统、微服务、定时任务、数据服务。

  2. 它解决什么问题

    解决传统 Spring 项目里"配置繁琐、依赖组合复杂、启动麻烦、环境切换乱、部署成本高"的问题。

  3. 它和哪些相关技术有关系

技术 关系
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 之上
  1. 学它之前需要哪些前置知识

    必须具备:Java 基础、面向对象、注解、集合、异常、Maven、HTTP/JSON、SQL 基础。

    最好具备:IDEA 调试、日志阅读、基本 Linux 命令。

  2. 真正重要的 核心内容是什么
    必须吃透:IoC/DI、Bean、自动装配、Starter、配置文件、Web 开发、参数绑定、异常处理、数据库访问、事务、日志、Profile、多环境、启动流程。

  3. 哪些内容初学者容易陷入、但不值得一开始深挖
    先知道,后深入:Spring 源码细节、AOT/Native、WebFlux、复杂 Security/OAuth2、自定义 Starter、自动装配源码深层实现、微服务全家桶、JVM 调优细节。

  4. 学习本质

    不是背注解,而是搞清楚:容器什么时候创建对象、为什么能自动注入、为什么少写配置也能跑起来。

  5. 设计哲学

    约定优于配置;默认可用;自动装配;组合常见能力;允许你覆盖默认行为。

学习顺序

  1. 入门认知

    先搞清 Spring、Spring MVC、Spring Boot 分工,能跑起一个最小服务。

  2. 核心概念

    重点掌握 IoC、DI、Bean、容器、配置类、Starter、自动装配。

  3. Web 开发主线

    掌握 Controller、请求映射、参数绑定、JSON、校验、统一返回、异常处理。

  4. 数据访问主线

    掌握数据源、事务、MyBatis/JPA 二选一、分页、常见 SQL 问题。

  5. 核心机制

    掌握启动流程、自动装配条件、配置优先级、Bean 生命周期、常见扩展点。

  6. 工程实践

    日志、Profile、多环境配置、测试、打包部署、监控、Actuator。

  7. 常见问题与排错

    启动失败、端口冲突、Bean 注入失败、循环依赖、配置不生效、数据库连不上。

  8. 面试与项目闭环

    把"会写"变成"会解释、会权衡、会定位问题"。

学习顺序方法论:

先"跑起来",再"理解容器",再"会写接口",再"连数据库",最后"看原理和排错"。

初学者最容易学偏的地方:一上来抠源码、背很多注解、做微服务,却不会解释最基础的启动和注入。

第一阶段:入门认知

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 一组场景化依赖打包,比如 webjdbc
自动装配 根据依赖和条件,自动创建常用 Bean
内嵌服务器 应用自己带 Tomcat,直接启动就能提供 HTTP 服务

必须吃透:Bean、容器、Starter、自动装配、启动类。
先知道,后深入:条件注解、自动装配源码扫描细节。

4. 原理解释

类比理解:

传统 Spring 像"你自己买零件组装厨房";Spring Boot 像"开发商给你装好了常用厨房,但你仍然可以换灶台"。

@SpringBootApplication 很关键,它本质上把三件事组合起来了:

  1. 配置类入口
  2. 开启自动装配
  3. 开启组件扫描

一个最小 Boot Web 应用启动时,简化流程是:
main 方法
SpringApplication.run(...)
创建 ApplicationContext
扫描组件与配置类
加载 Starter 对应自动配置
创建并注入 Bean
启动内嵌 Tomcat
开始监听 HTTP 请求

请求进来后的简化流程是:
浏览器/客户端
Tomcat
DispatcherServlet
Controller
返回 JSON/页面

设计思想:

  1. 大量默认配置,降低上手成本。
  2. 按条件装配,避免把所有东西都装进来。
  3. 你可以覆盖默认配置,所以"自动"不等于"不可控"。

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

你现在先只需要理解三件事:

  1. main 是启动入口。
  2. @RestController 表示这是处理 HTTP 请求的组件。
    @RestController = @Controller + @ResponseBody
    @Controller 表示这是一个 Spring MVC 控制器组件。
    @ResponseBody 表示:方法返回值直接写入 HTTP 响应体。
    有 @ResponseBody:返回值直接进响应体
    没有 @ResponseBody:返回值通常按视图名处理
  3. 之所以不用自己部署 Tomcat,是因为 Boot 带了内嵌服务器。

6. 工程实践

真实工程场景:

公司要你 10 分钟内起一个"用户服务"的骨架,先提供 /ping/users/health 两个接口,让前端、测试、运维先联调。

明确操作步骤:

  1. 创建项目,选 web 依赖。
  2. 写启动类。
  3. 新建 controller 包,写一个最小接口。
  4. application.yml 配置应用名和端口。
  5. 启动应用,确认日志里看到 Tomcat 端口。
  6. 用浏览器或 curl 验证接口。
  7. 提交第一个可运行版本。

建议你形成的项目结构直觉:

text 复制代码
src/main/java
  ├─ controller
  ├─ service
  ├─ config
  └─ DemoApplication
src/main/resources
  └─ application.yml

企业里通常怎么用:

先用 Boot 快速搭骨架,再逐步加数据库、日志、校验、权限、监控,而不是一开始就上复杂微服务。

7. 常见误区

  1. 以为 Spring Boot 替代了 Spring。错,Boot 是站在 Spring 上面的。
  2. 以为能跑起来全靠"魔法"。错,本质是容器 + 自动装配。
  3. 以为有 starter-web 就只多了一个依赖。错,它通常带来一整套 Web 场景能力。
  4. 启动类乱放位置。启动类应尽量放在根包,否则扫描范围可能不对。
  5. 把 Boot 学成"背注解"。真正要学的是对象管理、配置装配、请求处理流程。

8. 面试题

  1. 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 更容易用的工程化封装。

  2. Spring Boot 为什么能做到"开箱即用"?

    Spring Boot 能开箱即用,核心原因有 3 个:

    Starter
    把某个场景需要的依赖提前组合好,比如 Web、数据库、测试

    自动装配
    启动时根据依赖、配置项、环境条件,自动创建常用 Bean

    约定优于配置
    很多默认配置已经帮你准备好,你只在需要时覆盖

    因为 Spring Boot 通过 Starter 带来场景依赖,再通过自动装配按条件创建默认组件,所以能开箱即用。

  3. @SpringBootApplication 做了什么?

    @SpringBootApplication 主要等价于 3 个核心注解的组合:

    @SpringBootConfiguration
    表示这是 Spring Boot 配置类,本质上接近 @Configuration

    @EnableAutoConfiguration
    开启自动装配

    @ComponentScan
    开启组件扫描,扫描当前包及子包中的组件

    @SpringBootApplication = 配置类 + 自动装配 + 组件扫描。

  4. 为什么 Spring Boot 项目不需要单独部署 Tomcat?

    因为 Spring Boot 通常使用内嵌式 Web 容器,比如内嵌 Tomcat,应用启动时容器一起启动,所以不需要再把项目单独部署到外部 Tomcat。

  5. Starter 的作用是什么?

    它的作用是:

    帮你把某个功能场景需要的依赖提前组合好
    减少你手动找依赖、配版本的成本
    配合自动装配,让功能更容易直接可用
    例如:

    spring-boot-starter-web:Web 开发常用依赖
    spring-boot-starter-test:测试相关依赖
    spring-boot-starter-jdbc:数据库连接相关依赖
    一句话版:
    Starter 负责"把常用零件打包带上"。

  6. 一个 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. 自测题

  1. 用你自己的话解释 Spring Boot 是干什么的,控制在 3 句话内。

    Spring Boot 是建立在 Spring Framework 之上的快速开发框架。
    它通过 Starter、自动装配和默认配置,帮我们更快搭建 Spring 应用。
    它常用来开发 Web 接口、后台服务和企业级应用。

  2. 说出 Spring Framework、Spring MVC、Spring Boot 的区别。

    Spring Framework:底层基础框架,核心能力是 IoC、AOP、事务管理。
    Spring MVC:Spring 的 Web 模块,负责处理 HTTP 请求。
    Spring Boot:在 Spring 基础上做快速开发和自动配置的框架。

  3. 解释什么是 Bean,什么是 IoC 容器。

    Bean:交给 Spring 容器管理的对象。
    IoC 容器:负责创建、管理、装配 Bean 的"对象工厂"。

  4. 写出一个最小 Spring Boot Web 应用需要哪些核心元素。

    一个带 @SpringBootApplication 的启动类
    一个 main 方法,调用 SpringApplication.run(...)
    spring-boot-starter-web 依赖
    一个 @RestController
    至少一个 @GetMapping 或 @PostMapping 方法

  5. 说明 spring-boot-starter-web 大致帮你准备了什么能力。

    spring-boot-starter-web 大致帮你准备了:

    Spring MVC 处理 Web 请求的能力
    内嵌 Tomcat
    JSON 转换能力
    常见 Web 开发依赖
    你现在可以先把它理解成:

    Web 开发套餐包。

    也就是你不用自己一个个找:

    MVC
    Tomcat
    JSON 处理

  6. 动手题:自己创建一个 /ping 接口,返回 { "ok": true }

  7. 动手题:把端口改成 8081,重新启动并验证。

  8. 动手题:故意把启动类放到错误包路径,观察可能出现什么问题。

10. 学完标志

学完这一阶段后,你应该能做到:

  1. 能清楚解释 Spring、Spring MVC、Spring Boot 的关系。
  2. 能独立创建并运行一个最小 Spring Boot Web 项目。
  3. 能说明启动类、Starter、内嵌 Tomcat、Controller 的作用。
  4. 能从"容器自动装配"的角度解释为什么接口能跑起来。
  5. 能避开最基础的项目结构和扫描路径错误。

下一阶段我们会进入"核心概念",重点吃透:IoC、DI、Bean、配置类、Starter、自动装配。这是 Spring Boot 真正的分水岭。

第二阶段:核心概念

1. 学什么

这一阶段要建立 Spring Boot 最核心的认知骨架:

  1. 什么是 IoC,什么是 DI
  2. 什么是 Bean,谁在管理 Bean
  3. @Component@Service@Controller@Bean@Configuration 分别干什么
  4. Starter 为什么能让你少配很多东西
  5. 自动装配到底是"自动"了什么

这一阶段你要从"会写注解"升级到"知道容器为什么这样工作"。

2. 为什么重要

Spring Boot 的绝大多数能力,本质上都建立在"容器管理对象"之上。

你后面学到的这些内容:

  1. Controller 为什么能被请求到
  2. Service 为什么能被自动注入
  3. 数据源为什么能自动创建
  4. 事务为什么能生效
  5. 配置为什么能自动绑定

背后都离不开 IoC、DI、Bean、自动装配。

如果这一层没吃透,你会出现 3 种典型问题:

  1. 代码能抄出来,但改一点就不会
  2. 启动报错时只会删依赖、重启、碰运气
  3. 面试时只能背注解名,解释不了机制

所以这一阶段是 必须吃透

3. 核心概念

3.1 先讲直觉

类比理解:

  1. 没有 Spring 时,你自己采购零件、组装机器、接电接线。
  2. 有了 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
用法 标在类上 标在方法上
适合对象 你自己写的类 第三方类或需要手动控制创建逻辑的对象
注册方式 扫描发现 显式声明
常见场景 UserServiceUserController ObjectMapperRestTemplate、自定义工具对象

这两个概念很容易混。

一句话记住:

  1. 自己写的业务类,通常用 @Component 体系。
  2. 不是你源码里的类,或者创建逻辑复杂时,用 @Bean

4. 原理解释

4.1 IoC 和 DI 到底在解决什么问题

先看没有 Spring 的写法:

java 复制代码
public class UserController {
    private UserService userService = new UserService();
}

这会带来几个问题:

  1. Controller 自己决定依赖谁,耦合很死。
  2. UserService 再依赖别的对象,会一层层 new 下去。
  3. 测试时很难替换依赖。
  4. 对象创建时机、作用范围、配置来源都分散在代码里。

Spring 的思路是:

  1. 你声明"我需要一个 UserService"。
  2. 容器统一负责创建 UserService
  3. 容器再把它注入给 UserController

这就是:

  1. IoC:对象控制权反转给容器。
  2. DI:容器把依赖注入给对象。

4.2 容器是怎么工作的

简化步骤拆解:

  1. 应用启动,创建 ApplicationContext
  2. 容器扫描启动类所在包及其子包。
  3. 找到带 @Component@Service@Controller 等注解的类。
  4. 把这些类注册为 Bean 定义。
  5. 根据依赖关系实例化 Bean。
  6. 把依赖对象注入进去。
  7. 完成初始化后,整个应用进入可用状态。

用流程图看更直观:
启动应用
创建 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;
    }
}

设计思想:

  1. 必需依赖应该在对象创建时一次性满足。
  2. 一个对象不能"先半成品创建,再晚点补依赖"。
  3. 构造器让依赖关系显式化,更适合大型工程。

4.4 Starter 和自动装配为什么能省配置

spring-boot-starter-web 为例,它帮你做了两层事情:

  1. 帮你引入 Web 场景需要的一组依赖。
  2. 当 Spring Boot 发现这些依赖存在时,自动配置 MVC、JSON 转换、Tomcat 等常见 Bean。

你可以把它理解成:

  1. Starter 负责"把常用零件打包带上"。
  2. 自动装配负责"看到这些零件后帮你装起来"。

4.5 自动装配的简化运行逻辑

自动装配不是无脑全配,而是"按条件配置"。

典型判断条件包括:

  1. 类路径下是否存在某个类
  2. 配置文件里是否启用了某项功能
  3. 容器里是否已经有同类型 Bean
  4. 当前是不是 Web 应用

所以自动装配的底层设计思想是:

  1. 给你合理默认值
  2. 不和你手写配置抢控制权
  3. 满足条件才生效,避免无意义装配

你可以把它理解成一句话:

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());
    }
}

这个例子里发生了什么:

  1. UserService@Service 标记,注册成 Bean。
  2. UserController 也是一个 Bean。
  3. 容器发现 UserController 构造器需要 UserService
  4. 容器把 UserService 注入进去。
  5. 访问 /user 时,Controller 调用 Service 返回结果。

5.2 @Bean 示例

java 复制代码
@Configuration
public class AppConfig {

    @Bean
    public LocalDateTime startupTime() {
        return LocalDateTime.now();
    }
}

这里的意思是:

  1. AppConfig 是配置类。
  2. startupTime() 的返回值会被注册进容器。
  3. 之后别的 Bean 可以注入这个 LocalDateTime

6. 工程实践

6.1 真实工程场景

你要做一个"用户中心"接口模块,常见分层是:

  1. controller 接收请求
  2. service 写业务逻辑
  3. repositorymapper 访问数据库
  4. config 统一放配置类

为什么企业喜欢这种方式:

  1. 每层职责清晰
  2. 方便测试和替换实现
  3. 容器能统一管理这些对象
  4. 出问题时更容易按层排查

6.2 明确操作步骤

如果你现在要自己练一遍,建议按这 7 步走:

  1. 新建一个 Spring Boot 项目,引入 spring-boot-starter-web
  2. controllerserviceconfig 三个包。
  3. 写一个 UserService,返回固定用户名。
  4. 写一个 UserController,通过构造器注入 UserService
  5. 启动项目,访问 /user
  6. 新建一个 AppConfig,定义一个 @Bean
  7. 在 Controller 或 Service 里注入这个 @Bean,验证容器托管生效。

6.3 你在企业里必须掌握的"必须会"

必须掌握

  1. 会区分 @Component@Bean
  2. 会写构造器注入
  3. 知道 Bean 是容器管理的对象
  4. 知道启动类扫描范围影响注入是否成功
  5. 知道 Starter 和自动装配是两层概念

知道即可

  1. 很多冷门注解
  2. 自动装配源码中更深层的导入细节
  3. 所有 Bean 生命周期回调接口

7. 常见误区

  1. 以为 IoC 和 DI 是两个完全无关的概念。

    其实 DI 是 IoC 的一种实现方式,IoC 是思想,DI 是落地手段。

  2. 以为 @Service@Component 更"高级"。

    不是,本质都能注册 Bean,区别主要是语义分层。

  3. 以为 @Autowired 就等于 Spring 核心。

    不是,核心是容器管理和依赖装配,@Autowired 只是注入手段之一。

  4. 以为引了 Starter,就一定什么都配好了。

    不是,还要满足自动装配条件,而且有些配置你仍然要自己写。

  5. 以为 Bean 就是"一个普通对象"。

    不完整。Bean 是被容器管理、可参与注入、生命周期可控的对象。

  6. 以为扫描不到 Bean 是偶发 bug。

    很多时候是启动类包路径不对,或类没有被容器管理。

8. 面试题

  1. 什么是 IoC?什么是 DI?它们是什么关系?

    IoC 是控制反转,意思是"对象的创建和依赖关系的管理"不再由业务代码自己控制,而是交给 Spring 容器。

    DI 是依赖注入,指容器把对象依赖的其他对象注入进去。

    关系上,IoC 是思想,DI 是最常见的实现方式。

  2. Bean 是什么?和普通 new 出来的对象有什么区别?

    Bean 就是被 Spring 容器管理的对象。它和普通 new 出来的对象最大区别是:
    Bean 由容器负责创建、依赖注入、生命周期回调,还可能被 AOP 代理;
    普通 new 出来的对象只是普通 Java 对象,不受容器管理。

  3. @Component@Bean 有什么区别?

    @Component 是标在类上的,通常配合组件扫描把这个类注册成 Bean。

    @Bean 是标在方法上的,表示把这个方法返回的对象交给 Spring 管理。

    一般自己写的业务类常用 @Component、@Service、@Controller;
    第三方类、复杂初始化逻辑更适合用 @Bean。

  4. Spring 容器启动时大致做了哪些事?

    Spring 容器启动时大致会做这些事:

    创建 ApplicationContext,

    读取配置和环境信息,

    扫描组件,

    注册 BeanDefinition,

    执行自动装配条件判断,

    实例化单例 Bean,

    完成依赖注入,

    执行初始化回调和后置处理器,

    最后启动 Web 服务器并对外提供服务。

  5. 为什么推荐构造器注入,而不是字段注入?

    推荐构造器注入而不是字段注入,

    因为构造器注入依赖更明确,对象在创建时就是完整状态,字段也可以用 final,更方便测试,也更容易发现循环依赖。

    字段注入的问题是依赖隐藏、不利于单元测试,也不够规范。

  6. Starter 和自动装配分别是什么?

    Starter 是一组场景化依赖的打包方案,

    比如 spring-boot-starter-web,帮你把 Web 开发常用依赖一次性带进来。
    自动装配是 Spring Boot 根据当前类路径、配置项、已有 Bean 等条件,自动帮你创建合适 Bean 的机制。

    简单说,Starter 解决"把包带进来",自动装配解决"把对象配起来"。

  7. 自动装配为什么不会把所有东西都无脑创建?

    自动装配不会无脑创建所有东西,因为它是"有条件"的。

    常见条件有:
    类路径里是否存在某个类、
    配置文件里是否开启某个功能、
    容器里是否已经有同类型 Bean。

    像 @ConditionalOnClass、@ConditionalOnMissingBean、@ConditionalOnProperty
    就是在控制这件事,目的是给合理默认值,同时允许你覆盖。

  8. 为什么有时候明明写了 @Service,却注入失败?

    写了 @Service 但注入失败,常见原因有:
    这个类没被扫描到、
    你自己 new 了对象而不是让 Spring 管、
    接口有多个实现导致歧义、
    依赖的下游 Bean 本身创建失败、
    条件装配没生效。

    排查时先看启动报错类型最有效:
    NoSuchBeanDefinitionException 通常是没找到 Bean;
    NoUniqueBeanDefinitionException 通常是同类型 Bean 太多;
    BeanCreationException 通常是 Bean 创建过程中内部又出错了。

9. 自测题

  1. 用你自己的话解释 IoC 和 DI,要求能让一个没学过 Spring 的同学听懂。

    IoC 就是原本对象由你自己 new,现在交给 Spring 帮你创建和管理。
    DI 就是这个对象需要别的对象时,不用自己去找,Spring 直接把依赖塞给它。
    所以:IoC 是思想,DI 是实现这种思想的常见方式。

  2. 写出 @Component@Service@Controller@Bean 的区别。

    标准答案
    @Component:通用组件注解,标在类上,把类注册成 Bean
    @Service:业务层组件,本质上也是组件注解,只是语义更清晰
    @Controller:Web 控制器组件,负责接收请求
    @Bean:标在方法上,把方法返回值注册成 Bean,适合第三方类或自定义创建逻辑

    一句话总结
    @Component / @Service / @Controller 是"类级注册",@Bean 是"方法返回值注册"。

  3. 为什么构造器注入更适合工程项目?

    构造器注入更适合工程项目,因为:

    依赖关系是显式的,一眼能看出这个类依赖什么
    必需依赖可以在对象创建时一次性满足
    单元测试更方便,直接 new 时传 mock 对象即可
    比字段注入更清晰、更稳,也更符合工程化设计

  4. 如果 UserController 注入 UserService 失败,你会从哪些方向排查?

    UserService 有没有被 Spring 管理
    比如有没有 @Service / @Component

    包路径是否在启动类扫描范围内

    有没有自己手动 new UserController() 或 new UserService()

    是否存在多个同类型 Bean 导致歧义

    UserService 自己是不是创建失败了
    比如它依赖数据库、别的 Bean,而那些先炸了

  5. spring-boot-starter-web 和自动装配分别负责什么?

    spring-boot-starter-web:是 Web 场景的依赖集合,通常带来 Spring MVC、内嵌 Tomcat、JSON 处理等能力
    自动装配:是在启动时根据类路径、配置项、是否已有 Bean、当前是否 Web 环境等条件,自动注册默认组件

  6. 动手题:写一个 OrderServiceOrderController,通过构造器注入返回固定订单信息。

  7. 动手题:写一个 @Configuration 类,注册一个自定义 ClockLocalDateTime Bean。

  8. 动手题:故意把 Service 类放到扫描范围之外,观察报错信息并解释原因。

    UserService 没被注册成 Bean
    UserController 注入失败
    常见报错是 NoSuchBeanDefinitionException
    根因是启动类默认只扫描它所在包及子包

10. 学完标志

学完这一阶段后,你应该能做到:

  1. 能准确解释 IoC、DI、Bean、容器、Starter、自动装配。
  2. 能区分 @Component@Service@Bean@Configuration 的使用场景。
  3. 能用构造器注入写出基础的 Controller + Service 结构。
  4. 能从"容器扫描、注册、实例化、注入"的流程解释应用为什么能运行。
  5. 遇到基础的 Bean 注入失败问题时,知道优先排查包路径、注解、依赖类型、是否被容器托管。
  6. 能把 Spring Boot 的"方便"解释成 Starter + 自动装配,而不是一句"它会自动帮我配"。

下一阶段会进入 Web 开发主线,重点讲:请求是怎么进来的、参数是怎么绑定的、JSON 是怎么返回的、异常和校验怎么统一处理

这会把你从"理解容器"推进到"真正能写业务接口"。

第三阶段:Web 开发主线

1. 学什么

这一阶段我们把 Spring Boot 最常用的 Web 开发能力打通,重点掌握:

  1. 什么是 @RestController@RequestMapping@GetMapping@PostMapping
  2. 请求参数有哪些常见来源,分别怎么接
  3. 返回值为什么能自动变成 JSON
  4. 一个 HTTP 请求进入 Spring Boot 后,大致经历了什么流程
  5. 如何写出最基础但像样的接口结构

这一阶段的目标不是"会抄接口",而是建立一条完整链路:

客户端发请求 -> Spring MVC 接住 -> 参数绑定 -> 调业务 -> 返回 JSON

必须吃透

  1. @RestController@Controller 区别
  2. @RequestParam@PathVariable@RequestBody 区别
  3. DispatcherServlet 的职责
  4. JSON 转换为什么能自动发生

先知道,后深入

  1. 拦截器、过滤器、参数解析器的全部扩展细节
  2. HttpMessageConverter 全部实现类
  3. HandlerMethodArgumentResolver 源码级执行细节

2. 为什么重要

你学习 Spring Boot,最早真正"产生产出"的能力就是写接口。

企业里最常见的工作就是:

  1. 提供 REST API 给前端、移动端、其他服务调用
  2. 接收参数,做校验,调用 Service,返回 JSON
  3. 出现 400、404、405、415、500 时能看懂问题在哪一层

如果这部分没掌握,你会出现这些问题:

  1. 搞不清参数该用 @RequestParam 还是 @RequestBody
  2. 只会写接口,不知道请求怎么被 Spring 接住
  3. 报 404、405、400 时只能改来改去试错
  4. 面试时说不清 DispatcherServlet 和 Spring MVC 的关系

这一阶段是你从"理解框架"迈向"会写业务"的关键转折点。

3. 核心概念

3.1 先用直觉理解

你可以把 Spring MVC 理解成一个"请求调度中心"。

  1. 浏览器或前端把请求发过来
  2. Spring MVC 先判断该交给哪个 Controller
  3. 再把请求里的路径、查询参数、请求体解析成 Java 对象
  4. Controller 调 Service
  5. 最后把 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 个:

  1. Tomcat 负责接收网络层面的 HTTP 请求
  2. DispatcherServlet 负责把请求交给 Spring MVC 处理
  3. 参数绑定负责把 HTTP 世界转换成 Java 世界
  4. 消息转换器负责把 Java 返回值再转回 HTTP/JSON 世界

4.2 DispatcherServlet 为什么重要

类比理解:

DispatcherServlet 就像医院导诊台。

  1. 病人来了,不是直接冲进科室
  2. 导诊台先判断该挂哪个科
  3. 再把病人送到对应医生
  4. 看完后把结果再交出去

技术定义:

DispatcherServlet 是 Spring MVC 的前端控制器,统一拦截进入应用的 Web 请求,并协调请求映射、参数解析、方法执行、结果处理等流程。

设计思想:

  1. 用一个统一入口管理所有请求
  2. 把"路由""参数解析""结果处理"拆成独立组件
  3. 方便扩展,不把所有逻辑写死在一个地方

4.3 参数绑定为什么这么方便

Spring MVC 会根据方法参数上的注解和参数类型决定"从哪里取值"。

例如:

  1. @PathVariable Long id -> 从路径中取值
  2. @RequestParam Integer page -> 从查询参数中取值
  3. @RequestBody UserCreateRequest req -> 从 JSON 请求体中取值

它背后的设计思想是:

  1. Controller 方法应该表达业务意图,而不是手动解析原始 HTTP 文本
  2. 框架负责做大部分重复劳动
  3. 你只需要声明"我要什么参数"

4.4 为什么返回对象能自动变成 JSON

当你写:

java 复制代码
return Map.of("name", "zhangsan");

或者:

java 复制代码
return userDto;

Spring MVC 会把这个 Java 对象交给消息转换器处理。

在 Web 场景中,常见是用 Jackson 把对象序列化成 JSON。

简化过程:

  1. Controller 返回 Java 对象
  2. Spring 判断这不是页面,而是响应体内容
  3. 找到合适的 HttpMessageConverter
  4. 把对象转成 JSON 字符串
  5. 写入 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);
    }
}

这个例子里你要观察两件事:

  1. Controller 负责接请求和组织响应,不写复杂业务
  2. Service 负责业务逻辑,这样分层更清晰

6. 工程实践

6.1 真实工程场景

你要做一个"用户管理模块",常见接口有:

  1. GET /users/{id} 查询用户详情
  2. GET /users?page=1&size=10 分页查用户
  3. POST /users 新增用户
  4. PUT /users/{id} 修改用户
  5. DELETE /users/{id} 删除用户

这就是典型 REST 风格接口。

企业里为什么喜欢这种风格:

  1. 路径表达资源
  2. HTTP 方法表达操作语义
  3. 接口风格统一,前后端更容易协作
  4. 便于文档化、测试和网关治理

6.2 推荐的 Controller 编写原则

必须掌握

  1. Controller 只做请求接收、参数解析、结果返回
  2. 业务逻辑放 Service
  3. 不要把数据库操作直接堆在 Controller
  4. 路径命名尽量用复数资源名,如 /users/orders

工程上更稳的做法

  1. 请求对象和返回对象单独定义,不直接暴露数据库实体
  2. 参数命名统一,响应结构统一
  3. 对外接口返回 JSON,不混杂页面逻辑

6.3 明确操作步骤

你可以按这个顺序自己练习一遍:

  1. 创建 UserController,定义类级别路径 /users
  2. 写一个 GET /users/ping 接口
  3. 写一个 GET /users/{id},练习 @PathVariable
  4. 写一个 GET /users?page=1&size=10,练习 @RequestParam
  5. 写一个 POST /users,练习 @RequestBody
  6. 用 Postman、Apifox 或 curl 分别测试
  7. 故意发错请求,观察 404、405、400 的区别

6.4 常见 HTTP 状态码先建立直觉

状态码 常见含义 你第一反应该排查什么
200 成功 基本正常
400 请求参数错误 参数格式、必填项、JSON 结构
404 找不到资源 路径写错、映射没注册
405 方法不允许 GET/POST/PUT/DELETE 用错
415 媒体类型不支持 Content-Type 不对,常见于 JSON 请求
500 服务器内部错误 后端代码异常、空指针、数据库等问题

7. 常见误区

  1. 以为 @RestController 只是"少写一点代码"。

    不只是语法简化,它体现的是默认返回数据而不是页面的开发模式。

  2. 以为 GET 和 POST 只是名字不同。

    不是,HTTP 方法本身表达语义,也会影响前后端协作和接口设计规范。

  3. 以为 @RequestBody 能接所有参数。

    不是,它主要从请求体里读 JSON;路径参数和查询参数不是这样取的。

  4. 以为 404 就是服务没启动。

    不一定,很多时候只是路径映射没写对。

  5. 以为接口返回对象自动变 JSON 是 Java 自带能力。

    不是,是 Spring MVC + Jackson 这类消息转换机制在工作。

  6. 以为 Controller 里写 SQL 也没事,先跑起来再说。

    短期能跑,长期会让接口层、业务层、数据层彻底耦合,后面维护会非常痛苦。

  7. 以为接口能访问就代表设计合理。

    能访问只是最低标准,分层、命名、参数设计、错误处理一样重要。

8. 面试题

  1. @Controller@RestController 有什么区别?
  2. @RequestParam@PathVariable@RequestBody 分别适用于什么场景?
  3. 一个 HTTP 请求进入 Spring MVC 后,大致执行流程是什么?
  4. DispatcherServlet 是什么?为什么说它是前端控制器?
  5. 为什么 Controller 返回 Java 对象时,前端能收到 JSON?
  6. 404、405、400、415 分别常见于哪些问题?
  7. 为什么一般不建议在 Controller 里直接写业务逻辑和数据库代码?
  8. Spring MVC 和 Tomcat 的关系是什么?

9. 自测题

  1. 用你自己的话解释:一个请求从浏览器到 Controller,再到 JSON 响应,中间发生了什么。
  2. 写出一个包含路径参数、查询参数、请求体参数的接口示例。
  3. 如果前端调用 POST /users 返回 415,你会优先排查什么?
  4. 如果访问 /users/1 返回 404,你会怎么定位?
  5. 为什么 @RestController 更适合前后端分离项目?
  6. 动手题:实现一个 /orders/{id} 查询接口。
  7. 动手题:实现一个 POST /orders 接口,接收 JSON 请求体。
  8. 动手题:分别故意制造 404、405、400,记录现象并总结区别。

10. 学完标志

学完这一阶段后,你应该能做到:

  1. 能独立写出基础的 REST 风格接口。
  2. 能正确使用 @GetMapping@PostMapping@PathVariable@RequestParam@RequestBody
  3. 能解释 DispatcherServlet 在请求处理中的角色。
  4. 能说明 Controller 返回值为什么会被转成 JSON。
  5. 遇到基础的 404、405、400、415 问题时,知道从路径、方法、参数、请求头几个方向快速排查。
  6. 能按 Controller -> Service 的分层方式写出更像工程项目的接口代码。

下一阶段会进入数据访问主线,重点讲:Spring Boot 如何连接数据库、MyBatis/JPA 怎么选、事务为什么能回滚、常见 SQL 和连接问题怎么排查。这会让你从"会写接口"进到"会写真实业务"。

第四阶段:数据访问主线

1. 学什么

这一阶段要解决一个核心问题:

Spring Boot 写好的接口,怎么真正把数据存进数据库、再查出来。

你需要重点掌握:

  1. Spring Boot 如何连接数据库
  2. 数据源是什么,连接池在干什么
  3. MyBatis 和 JPA 分别是什么,怎么选
  4. Mapper/Repository 在项目里扮演什么角色
  5. 事务是什么,为什么会回滚
  6. 常见数据库连接、SQL、事务问题怎么排查

学习建议:

  1. 入门阶段先走 MyBatis 主线,更容易建立 SQL 和数据访问的直觉
  2. JPA 先知道核心思想和适用场景,后面再深入

必须吃透

  1. 数据源
  2. 连接池
  3. MyBatis 基本用法
  4. 事务和 @Transactional
  5. 常见数据库连接错误排查

先知道,后深入

  1. 多数据源
  2. 分库分表
  3. 复杂缓存一致性
  4. JPA 高级映射
  5. 分布式事务

2. 为什么重要

你前面学了 Controller 和 Service,但如果不会数据访问,就只能返回假数据。

企业里的绝大多数后端业务,最终都绕不开:

  1. 查用户
  2. 存订单
  3. 更新状态
  4. 做分页
  5. 保证一组操作要么都成功,要么都失败

如果这部分不扎实,最容易出现这些问题:

  1. SQL 能写,但不会接入 Spring Boot
  2. 能查到数据,但不会做事务
  3. 启动就报数据库连接失败,不知道先查配置还是网络
  4. 面试时说不清 @Transactional 为什么会生效

所以这一阶段是"从写 Demo 到写真实业务"的关键一步。

3. 核心概念

3.1 先用直觉理解

你可以把数据库访问这条链路理解成:

  1. 业务代码想查或改数据
  2. 它不会直接和数据库裸连到底
  3. 中间要经过数据源、连接池、访问框架
  4. 事务负责兜底,保证一组操作的一致性

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 效率高
企业常见场景 国内业务系统很多 标准化后台、领域建模较强的系统更多

当前阶段建议:

  1. 先主学 MyBatis
  2. 知道 JPA 是"面向对象操作数据库"的思路
  3. 面试时能说出取舍,而不是站队

3.4 Spring Boot 里常见的数据访问分层

作用
Controller 接收请求,返回结果
Service 写业务流程、事务边界
Mapper / Repository 负责执行 SQL 或 ORM 操作
Database 持久化存储

一句话记住:

  1. Controller 不直接写 SQL
  2. Mapper 不写业务流程
  3. 事务通常放在 Service 层

4. 原理解释

4.1 Spring Boot 是怎么连上数据库的

简化流程:
application.yml 配置数据库信息
Spring Boot 创建 DataSource
连接池初始化
MyBatis/JPA 获取连接
执行 SQL
把结果映射成 Java 对象
返回给 Service/Controller

Spring Boot 自动配置通常会根据你引入的依赖和配置项,自动创建:

  1. DataSource
  2. 事务管理器
  3. MyBatis 或 JPA 相关组件

4.2 为什么需要连接池

如果每次请求都现建数据库连接,代价很高:

  1. 连接建立慢
  2. 数据库压力大
  3. 并发一高就顶不住

所以连接池的核心思想是:

  1. 先准备好一些连接
  2. 业务来了直接借用
  3. 用完归还,不是销毁

你可以把它理解成"共享单车停车点":

  1. 不需要每次现造一辆车
  2. 用的时候拿一辆
  3. 用完再停回去

4.3 MyBatis 的工作流程

MyBatis 的核心特点是:

SQL 你来写,框架帮你执行并把结果映射成对象。

简化流程:

  1. 你定义 Mapper 接口
  2. 你写 SQL
  3. MyBatis 根据 Mapper 生成代理对象
  4. 你调用接口方法
  5. MyBatis 执行 SQL
  6. 把结果映射成 Java 对象返回

它为什么适合初学者:

  1. 你能直接看到 SQL
  2. 容易理解数据库行为
  3. 出问题更容易顺着 SQL 排查

4.4 事务为什么重要

看一个典型场景:转账。

  1. A 扣 100
  2. B 加 100

如果第一步成功、第二步失败,就出大问题。

所以这两个操作必须放在一个事务里。

事务的核心目标:

  1. 保证数据一致性
  2. 避免只做一半

在 Spring 里最常见的写法是:

java 复制代码
@Transactional
public void transfer() {
    // 扣款
    // 加款
}

简化原理:

  1. 方法执行前开启事务
  2. 方法内部多条数据库操作共享同一事务
  3. 如果执行成功,提交事务
  4. 如果发生符合规则的异常,回滚事务

4.5 @Transactional 为什么能生效

这背后仍然是 Spring 的 AOP 思想。

你可以先记住简化版:

  1. Spring 给目标对象创建代理
  2. 代理在调用方法前后插入事务逻辑
  3. 不是你代码自己显式写了 begin/commit/rollback
  4. 而是框架帮你织入了这层能力

这也是为什么事务问题经常和 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);
}

如果事务生效,这个方法执行后:

  1. 第一条插入不会最终落库
  2. 因为中间抛出异常,整个事务回滚

6. 工程实践

6.1 真实工程场景

做"用户注册"时,常见业务流程可能是:

  1. user 表插入用户
  2. user_profile 表插入扩展资料
  3. user_account 表初始化账户

这 3 步要么都成功,要么都失败,所以通常放到一个事务里。

这就是事务在企业里最常见的落地方式。

6.2 MyBatis 在企业里的常见写法

  1. 简单 SQL 可以注解写在 Mapper 上
  2. 复杂 SQL 一般放到 XML
  3. Service 层负责事务和业务编排
  4. 分页通常配合分页插件或手写分页 SQL

当前阶段你先掌握:

  1. @Mapper
  2. 基本 select/insert/update/delete
  3. Service 调 Mapper
  4. @Transactional

6.3 明确操作步骤

你可以按这条练习路径走:

  1. 安装并启动 MySQL
  2. 创建一个 demo 数据库
  3. user
  4. 在 Spring Boot 项目里引入 MyBatis 和 MySQL 驱动
  5. 配好 application.yml
  6. User 实体类
  7. UserMapper
  8. UserService
  9. UserController
  10. 用 Postman/Apifox 测试查询和新增
  11. 再写一个故意抛异常的方法,验证事务回滚

6.4 企业里通常怎么选 MyBatis 和 JPA

常见经验判断:

  1. 业务 SQL 比较复杂、报表多、调优要求高,常偏向 MyBatis
  2. CRUD 很多、领域模型清晰、希望减少模板代码,常偏向 JPA
  3. 有些公司两者并用,但这要求团队规范更强

你面试时更稳的回答不是"哪个好",而是:

要看业务复杂度、团队习惯、SQL 控制诉求和维护成本。

7. 常见误区

  1. 以为会写 SQL 就等于会做 Spring Boot 数据访问。

    不是,中间还有数据源、连接池、事务、映射框架。

  2. 以为 @Transactional 一加就一定回滚。

    不是,异常类型、自调用、代理边界都会影响事务生效。

  3. 以为事务应该加在 Controller。

    通常不推荐,事务边界更适合放在 Service 层。

  4. 以为数据库连不上一定是账号密码错。

    还可能是端口、数据库没启动、驱动不匹配、URL 参数错误、防火墙、权限问题。

  5. 以为 MyBatis 比 JPA "低级"。

    不是,它只是更偏显式 SQL 控制。

  6. 以为查不到数据一定是代码问题。

    也可能是连错库、连错环境、事务未提交、SQL 条件不对。

  7. 以为连接池是可有可无的优化。

    不是,在真实应用里它几乎是必需基础设施。

8. 面试题

  1. Spring Boot 是怎么自动创建数据源的?
  2. 什么是连接池?为什么要用连接池?
  3. MyBatis 和 JPA 有什么区别?你会怎么选?
  4. @Mapper 的作用是什么?
  5. 事务是什么?为什么事务通常放在 Service 层?
  6. @Transactional 为什么能生效?
  7. 哪些情况下事务可能不生效?
  8. 数据库连接失败时,你通常怎么排查?

9. 自测题

  1. 用你自己的话解释数据源、连接池、事务分别是什么。
  2. 为什么说 MyBatis 更适合初学阶段建立数据库直觉?
  3. 如果项目启动报数据库连接失败,你会按什么顺序排查?
  4. 为什么事务更适合加在 Service,而不是 Controller?
  5. @Transactional 和 Spring AOP 有什么关系?
  6. 动手题:完成 GET /users/{id} 的查库版本。
  7. 动手题:完成 POST /users 的入库版本。
  8. 动手题:写一个插两张表的方法,中途故意抛异常,验证事务回滚。
  9. 动手题:故意把数据库密码改错,观察启动报错并提炼关键词。
  10. 动手题:故意让 SQL 表名写错,观察异常类型并记录定位思路。

10. 学完标志

学完这一阶段后,你应该能做到:

  1. 能说清 Spring Boot 连接数据库的大致链路。
  2. 能解释数据源、连接池、Mapper、事务分别在系统里的作用。
  3. 能用 MyBatis 写出基本的查库和入库代码。
  4. 能解释为什么事务通常写在 Service 层。
  5. 能从 AOP 代理角度理解 @Transactional 的基本生效机制。
  6. 遇到数据库连接失败、SQL 执行失败、事务不回滚等问题时,知道第一轮该怎么排查。
  7. 面试时能说出 MyBatis 和 JPA 的取舍,而不是只会背定义。

下一阶段会进入核心机制,重点讲:Spring Boot 启动流程、自动装配条件、配置优先级、Bean 生命周期、常见扩展点。这一阶段会把你前面学到的"会用"真正连到"为什么这样设计"。

第五阶段:核心机制

1. 学什么

这一阶段的目标是把你前面已经会用的 Spring Boot 能力,统一串成一个"运行模型"。

重点学习:

  1. Spring Boot 应用启动时到底发生了什么
  2. 自动装配是怎么被触发的
  3. 自动装配为什么不是"无脑全配"
  4. 配置文件为什么能覆盖默认值,优先级怎么判断
  5. Bean 生命周期大致经过哪些阶段
  6. 常见扩展点有哪些,分别在什么场景下用

这一阶段不是要求你马上抠源码,而是先建立正确框架:

启动 -> 创建容器 -> 加载配置 -> 自动装配 -> 创建 Bean -> 注入依赖 -> 启动 Web 容器

必须吃透

  1. SpringApplication.run() 的作用
  2. 自动装配的条件化思想
  3. 配置优先级
  4. Bean 生命周期主干
  5. 为什么你能"覆盖默认配置"

先知道,后深入

  1. DeferredImportSelector
  2. BeanFactoryPostProcessorBeanPostProcessor 全部细节
  3. EnvironmentPostProcessor
  4. 自动装配导入源码链路的全部实现类

2. 为什么重要

前面几阶段你已经会:

  1. 写接口
  2. 注入 Bean
  3. 连数据库
  4. 开事务

但如果不知道核心机制,你会卡在这些典型问题上:

  1. 为什么某个配置明明写了却不生效
  2. 为什么某个 Bean 明明有依赖却没被创建
  3. 为什么引了依赖后功能突然自动可用了
  4. 为什么自己写的 Bean 可以覆盖 Spring Boot 默认 Bean
  5. 为什么有些扩展点写错时机会完全无效

这阶段会帮你从"使用者"升级到"能解释、能排错、能做设计判断"的层次。

3. 核心概念

3.1 先用直觉理解

你可以把 Spring Boot 应用启动理解成"搭一个会自己装配的工厂":

  1. 先把工厂本体建起来
  2. 读取配置和原材料
  3. 看你引入了哪些模块
  4. 满足条件的组件自动装进去
  5. 没写的用默认值
  6. 你自己手写的优先级通常更高

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 个关键点:

  1. 创建环境对象,读取各种配置来源
  2. 创建 Spring 容器
  3. 加载你的配置类和自动配置类
  4. 完成 Bean 创建后启动应用

4.2 自动装配为什么会发生

Spring Boot 的便利,核心在于:

  1. 你引入了某个 Starter
  2. 这个 Starter 背后带来自动配置类
  3. 启动时 Spring Boot 会尝试加载这些自动配置类
  4. 只有条件满足时,自动配置里的 Bean 才会注册

所以自动装配的逻辑不是:

"我帮你全都配上。"

而是:

"如果你已经有这个场景需要的依赖和条件,我就给你装一个默认实现。"

4.3 为什么默认配置能被你覆盖

这是 Spring Boot 设计得很漂亮的一点。

很多自动配置类会这样写设计意图:

  1. 如果你没定义这个 Bean,我来给你一个默认的
  2. 如果你自己定义了,我就退后

这通常通过 @ConditionalOnMissingBean 表达。

所以:

  1. Spring Boot 提供默认值
  2. 你保留最终控制权
  3. 框架既方便,又不强制

这也是"约定优于配置"不是"禁止配置"的原因。

4.4 配置文件为什么会有优先级

Spring Boot 允许配置来自多个地方:

  1. application.yml
  2. application-dev.yml
  3. 命令行参数
  4. 环境变量
  5. JVM 参数
  6. 外部配置文件

为什么要有优先级?

因为企业里经常需要:

  1. 同一份代码在不同环境使用不同配置
  2. 部署时临时覆盖某个参数
  3. 不把敏感信息写死在项目文件里

直觉上记住:

越外部、越临时、越运行时指定的配置,优先级通常越高。

4.5 Bean 生命周期大致分几步

先不要陷入全部细节,你先记主干:

  1. Bean 定义被注册
  2. Bean 被实例化
  3. 依赖注入
  4. 执行初始化回调
  5. Bean 可供业务使用
  6. 容器关闭时执行销毁回调

简化流程图:
注册 BeanDefinition
实例化
依赖注入
初始化
进入可用状态
容器关闭时销毁

为什么这很重要?

因为很多问题都跟"时机"有关:

  1. 你在 Bean 还没初始化完就用了它
  2. 你想修改 Bean,但修改时机太晚
  3. 你以为配置会影响 Bean,实际上 Bean 早就创建完了

4.6 常见扩展点是干什么的

先建立直觉,不求现在全会。

扩展点 大致用途
CommandLineRunner 应用启动完成后执行一段初始化逻辑
ApplicationRunner 和上面类似,但参数封装更友好
@ConfigurationProperties 把配置批量绑定成对象
BeanPostProcessor 在 Bean 初始化前后做增强
ApplicationListener 监听应用生命周期事件

当前阶段,你最应该先掌握的是:

  1. @ConfigurationProperties
  2. CommandLineRunner
  3. 自动装配条件的阅读能力

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

你可以把它理解成:

  1. 把零散配置映射成一个 Java 对象
  2. 以后业务里直接注入这个对象使用
  3. 比零散 @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("应用启动完成,开始执行初始化逻辑");
    }
}

这个组件会在应用启动完成后执行。

常见用途:

  1. 打印启动信息
  2. 初始化演示数据
  3. 做启动后自检

5.4 覆盖默认 Bean 的直觉示例

假设某个自动配置类会在你没定义时提供一个默认组件。

如果你自己声明了同类型 Bean:

java 复制代码
@Configuration
public class CustomConfig {

    @Bean
    public ObjectMapper objectMapper() {
        return new ObjectMapper();
    }
}

很多情况下,Spring Boot 默认配置会退后,让你自己的 Bean 生效。

这就是"默认配置可覆盖"。

6. 工程实践

6.1 真实工程场景

企业项目最常见的几种机制型需求:

  1. 开发、测试、生产使用不同数据库配置
  2. 应用启动后执行预热或初始化逻辑
  3. 某个组件需要默认实现,但允许项目按需替换
  4. 某些功能只在某个配置开关打开时才启用

这些需求背后,对应的都是你这一阶段学的机制:

  1. Profile
  2. 配置优先级
  3. 自动装配条件
  4. 扩展点

6.2 明确操作步骤

建议你亲手做这 5 个小实验:

  1. 写一个 application.yml,再用命令行参数覆盖其中一个值
  2. 新建 application-dev.ymlapplication-prod.yml,切换 Profile
  3. 写一个 @ConfigurationProperties 类,绑定自定义配置
  4. 写一个 CommandLineRunner,验证启动后逻辑执行
  5. 写一个自定义 @Bean,观察是否覆盖默认 Bean

6.3 工程方法论

初学者最容易学偏的点:

  1. 一上来就钻自动装配源码
  2. 背很多条件注解名字,但说不出设计目的
  3. 配置不生效时只会试错,不会判断优先级和加载来源

正确学习顺序应该是:

  1. 先理解"启动时容器要干什么"
  2. 再理解"自动装配为什么成立"
  3. 再理解"配置为什么能覆盖"
  4. 最后再看生命周期和扩展点

7. 常见误区

  1. 以为自动装配就是框架偷偷帮你做魔法。

    不是,本质是条件判断 + 默认注册。

  2. 以为引了 Starter,就一定所有配置都生效。

    不是,很多功能还依赖类路径、配置项、Web 环境等条件。

  3. 以为 application.yml 一定是最终配置。

    不是,环境变量、命令行、外部配置都可能覆盖它。

  4. 以为 Bean 创建就是 new 一下那么简单。

    不是,中间还有定义注册、依赖注入、初始化回调、后处理器等过程。

  5. 以为配置覆盖失败一定是写错键名。

    有时是 Profile 没激活、优先级更高的配置覆盖了它,或 Bean 创建时机早于你预期。

  6. 以为扩展点越底层越高级。

    不一定,工程上更重要的是选对时机和合适的扩展点。

8. 面试题

  1. SpringApplication.run() 大致做了哪些事情?
  2. Spring Boot 自动装配是怎么实现"开箱即用"的?
  3. 自动装配为什么不会和用户自定义配置冲突?
  4. @ConditionalOnMissingBean 的作用是什么?
  5. Spring Boot 配置优先级一般怎么理解?
  6. Bean 生命周期大致有哪些阶段?
  7. @ConfigurationProperties@Value 有什么区别?
  8. CommandLineRunnerApplicationRunner 有什么用途?

9. 自测题

  1. 用你自己的话把 Spring Boot 启动主流程讲一遍。
  2. 为什么说自动装配本质上是"条件成立时提供默认实现"?
  3. 如果某个配置写在 application.yml 里却不生效,你会从哪些角度排查?
  4. 为什么你自定义的 Bean 往往能覆盖默认 Bean?
  5. @ConfigurationProperties 为什么比到处写 @Value 更适合工程项目?
  6. 动手题:写一个 UserProperties 配置类并成功绑定。
  7. 动手题:切换 devprod Profile,验证不同配置加载。
  8. 动手题:写一个 CommandLineRunner,打印一段启动日志。
  9. 动手题:给一个功能增加开关配置,并用条件化思想解释它为什么能按需启用。

10. 学完标志

学完这一阶段后,你应该能做到:

  1. 能从主干角度解释 Spring Boot 应用启动流程。
  2. 能说清自动装配依赖"条件成立 + 默认配置 + 用户可覆盖"的设计思想。
  3. 能理解配置来源不止一个,并能按优先级思路排查配置不生效问题。
  4. 能说明 Bean 生命周期的大致阶段以及"时机"为什么重要。
  5. 能使用 @ConfigurationProperties、Profile、CommandLineRunner 这类常见机制型能力。
  6. 面试时能把 Spring Boot 的"方便"解释成可理解的工程设计,而不是抽象的"自动魔法"。

下一阶段会进入工程实践,重点讲:日志、Profile、多环境配置、测试、打包部署、监控、Actuator、项目结构和开发规范。这一阶段会把你从"知道原理"推进到"能按企业方式写项目"。

第六阶段:工程实践

1. 学什么

这一阶段解决的问题不是"框架会不会用",而是:

一个 Spring Boot 项目在企业里到底该怎么写、怎么测、怎么配、怎么跑、怎么管。

重点学习:

  1. 项目结构怎么组织更清晰
  2. 日志怎么打才有排查价值
  3. 多环境配置怎么管理
  4. 测试怎么做,单元测试和集成测试怎么区分
  5. 项目怎么打包和部署
  6. 运行中的应用怎么监控
  7. Actuator 是什么,有什么用

必须吃透

  1. 日志
  2. Profile 与多环境配置
  3. 基础测试
  4. 打包部署
  5. Actuator 基础能力

先知道,后深入

  1. 容器编排
  2. 分布式链路追踪
  3. 灰度发布
  4. 高级监控告警体系
  5. 大规模 CI/CD 细节

2. 为什么重要

很多人学 Spring Boot 时只学"能跑",但企业真正看重的是:

  1. 代码是否易维护
  2. 出问题能不能查
  3. 配置是否可控
  4. 发布是否稳定
  5. 上线后有没有可观测性

如果没有工程实践意识,常见后果是:

  1. 日志全是 System.out.println
  2. 开发环境配置直接带进生产
  3. 改个端口、数据库地址都要改代码
  4. 上线后接口挂了,不知道看哪
  5. 没有测试,改一个点怕牵一片

所以这一阶段决定你是否开始像一个"能进项目"的工程师。

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 指定当前启用哪个环境

一句话记住:

  1. 通用项放默认配置
  2. 差异项放环境配置
  3. 敏感信息尽量不要硬编码进仓库

4. 原理解释

4.1 为什么日志不能只靠打印

很多初学者一开始爱写:

java 复制代码
System.out.println("user created");

它的问题是:

  1. 没有统一格式
  2. 不能方便区分级别
  3. 不利于检索和聚合
  4. 线上排查很痛苦

日志框架的设计思想是:

  1. 统一输出格式
  2. 区分级别
  3. 可以控制输出位置和输出量
  4. 便于后续接入日志平台

4.2 为什么要做多环境配置

同一个项目在不同环境里的差异通常很多:

  1. 数据库地址不同
  2. Redis 地址不同
  3. 日志级别不同
  4. 某些开关在生产要关闭

如果你把这些都写死,部署会非常危险。

所以 Spring Boot 用 Profile 帮你做环境切分。

设计思想:

  1. 代码尽量一套
  2. 环境差异配置化
  3. 发布时只切环境,不改代码

4.3 为什么测试要分层

不是所有测试都该直接启动整个项目。

  1. 单元测试更快,适合验证一个类的逻辑
  2. 集成测试更真实,适合验证接口、数据库、容器协作

这背后的设计思想是:

  1. 快速反馈和真实验证都要有
  2. 不同层的问题应该用不同粒度的测试抓

4.4 Spring Boot 为什么适合打包部署

Spring Boot 很适合工程化交付,因为:

  1. 自带内嵌 Tomcat
  2. 可以直接打成可运行 jar
  3. 部署时只要有 JDK,就能启动

这大大降低了传统 Java Web 项目的部署复杂度。

4.5 Actuator 的设计思想

应用上线后,最怕的是:

  1. 你不知道它活着没有
  2. 你不知道连接池满没满
  3. 你不知道配置是不是按预期加载
  4. 你不知道线程、内存、请求情况

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

常见端点:

  1. /actuator/health
  2. /actuator/info

访问 /actuator/health 可以快速查看应用健康状态。

6. 工程实践

6.1 真实工程场景

你接手一个用户系统,企业里常见要求是:

  1. 开发、测试、生产三套环境分开
  2. 用户新增接口失败时,要能从日志中定位
  3. 发版时打包成 jar,服务器直接启动
  4. 运维要有健康检查接口
  5. 基础业务逻辑要有测试兜底

这时候你会用到:

  1. Profile
  2. 规范日志
  3. Maven 打包
  4. Actuator
  5. JUnit + Spring Boot Test

6.2 推荐的工程规范

必须掌握

  1. 不用 System.out.println 代替日志
  2. 不把环境差异写死在代码里
  3. 不把 Controller、Service、Mapper 混写
  4. 每次改核心逻辑至少补最基础测试
  5. 线上服务至少暴露健康检查端点

更像中级工程师的做法

  1. 日志打印关键上下文,不打无意义废话
  2. 错误日志要带异常栈
  3. 配置项集中管理,避免满地 @Value
  4. 对外接口、配置、部署命令都可复现

6.3 明确操作步骤

你可以做这一组工程化练习:

  1. 给现有项目补上 application-dev.ymlapplication-prod.yml
  2. 给核心 Service 加日志
  3. 写一个最基础的单元测试
  4. 写一个 @SpringBootTest 测试验证容器能启动
  5. 引入 Actuator 并开放 health
  6. 用 Maven 打包
  7. 本地通过 java -jar 启动一次
  8. 用不同 Profile 分别运行验证配置差异

6.4 企业里通常怎么用

  1. 开发环境更关注调试便利,日志级别会更详细
  2. 测试环境更接近生产,但常保留测试数据
  3. 生产环境更强调稳定、安全、可观测性
  4. 发布一般不会手改代码,而是切配置和部署参数
  5. 健康检查通常会接入负载均衡、监控平台或容器平台

7. 常见误区

  1. 以为日志越多越好。

    不是,日志要有信息密度和上下文,不是把所有变量都乱打一遍。

  2. 以为开发环境配置能直接搬到生产。

    非常危险,尤其是数据库地址、日志级别、调试开关。

  3. 以为测试就是写个 contextLoads()

    这只是最基础的容器加载测试,不代表业务逻辑就安全。

  4. 以为能 mvn package 就算会部署。

    真正工程里还要考虑环境变量、日志路径、启动参数、健康检查。

  5. 以为 Actuator 只是"顺手加一个依赖"。

    它是应用可观测性的基础入口之一。

  6. 以为日志只在报错时才打。

    关键流程、关键输入、关键结果也需要合理记录。

8. 面试题

  1. Spring Boot 项目常见的工程目录如何划分?
  2. 为什么不推荐使用 System.out.println 打日志?
  3. Spring Boot 如何做多环境配置?
  4. application.ymlapplication-prod.yml 是什么关系?
  5. 单元测试和集成测试有什么区别?
  6. Spring Boot 项目通常怎么打包和运行?
  7. 什么是 Actuator?常见端点有哪些?
  8. 线上问题排查时,日志、健康检查、配置管理分别起什么作用?

9. 自测题

  1. 你会如何设计一个基础的 Spring Boot 项目目录结构?
  2. 如果要排查"用户注册失败",你希望日志里至少出现哪些信息?
  3. 为什么多环境配置不能靠复制三份项目来解决?
  4. @SpringBootTest 适合解决什么问题?
  5. 如果服务打成 jar 后启动失败,你会从哪些方向排查?
  6. 动手题:给项目加上 devprod 两套配置。
  7. 动手题:为某个 Service 补充规范日志。
  8. 动手题:补一个基础单元测试和一个集成测试。
  9. 动手题:引入 Actuator,并验证 /actuator/health 可访问。
  10. 动手题:本地打包并通过 java -jar 启动一次项目。

10. 学完标志

学完这一阶段后,你应该能做到:

  1. 能按常见企业分层方式组织 Spring Boot 项目结构。
  2. 能用规范日志替代随意打印,并知道关键日志应该打什么。
  3. 能用 Profile 管理多环境配置。
  4. 能写最基础的单元测试和 Spring Boot 集成测试。
  5. 能把项目打成 jar 并用命令行运行。
  6. 能引入 Actuator 并理解健康检查的意义。
  7. 对"项目上线后怎么观察、怎么排查、怎么切环境"有基本工程直觉。

下一阶段会进入常见问题与排错,重点讲:启动失败、Bean 注入失败、端口冲突、配置不生效、事务不生效、数据库连不上、接口报错时该怎么系统定位。这一阶段会把你的知识真正变成排障能力。

第七阶段:常见问题与排错

1. 学什么

这一阶段的核心目标是:

把你从"知道知识点"训练成"遇到问题能定位"的人。

重点学习:

  1. Spring Boot 项目启动失败时怎么查
  2. Bean 注入失败怎么查
  3. 端口冲突怎么查
  4. 配置不生效怎么查
  5. 数据库连不上怎么查
  6. 事务不生效怎么查
  7. 接口 404、400、405、415、500 怎么查
  8. 如何建立一套通用排障方法论

必须吃透

  1. 看异常要先看根因
  2. 先缩小问题层级,再定位细节
  3. 配置、扫描路径、依赖、代理、环境,是高频故障源
  4. 日志和报错关键词比盲猜更重要

先知道,后深入

  1. 线程 dump
  2. JVM 级问题定位
  3. 线上性能分析器
  4. 复杂中间件故障协同排查

2. 为什么重要

真实工作里,框架的价值不只体现在"会写",更体现在:

  1. 服务起不来你能不能救
  2. 接口报错你能不能快速缩圈
  3. 某个配置不生效你能不能找准原因
  4. 别人说"事务失效了",你是不是知道先看哪里

面试官也很爱问这类问题,因为它最能区分:

  1. 只做过教程的人
  2. 真正在项目里排过问题的人

这一阶段你要学会的是:系统化排查,而不是凭感觉乱试。

3. 核心概念

3.1 排障的本质

排错不是"记住所有异常",而是做两件事:

  1. 先判断问题出在哪一层
  2. 再在那一层里找关键证据

Spring Boot 常见问题大致可分 6 层:

常见问题
启动层 应用起不来、类加载失败、配置错误
容器层 Bean 注册失败、注入失败、循环依赖
Web 层 404、405、400、415、参数绑定异常
数据层 数据库连不上、SQL 报错、事务失效
配置层 Profile 错、配置没加载、优先级覆盖
运行层 日志不足、环境差异、线上行为和本地不同

3.2 通用排查模型

你以后看到问题,优先按这个顺序思考:

  1. 现象是什么
  2. 发生在启动时还是运行时
  3. 属于哪一层
  4. 日志里最底层的异常是什么
  5. 最近改了什么
  6. 哪个最小实验可以快速验证猜想

一句话总结:

先分类,再看根因,再做最小验证。

3.3 常见错误类型对比

现象 常见根因
启动失败 配置错误、依赖缺失、Bean 创建失败、数据库连接失败
NoSuchBeanDefinitionException 容器里没有这个 Bean
BeanCreationException Bean 在创建过程中出错
404 路径没匹配到
405 请求方法不匹配
400 参数绑定失败、请求格式错误
415 Content-Type 不支持
数据库连接失败 URL、账号密码、端口、驱动、网络问题
事务不回滚 代理未生效、异常类型不对、自调用问题

4. 原理解释

4.1 为什么看异常要看"最底层根因"

很多 Spring Boot 异常会一层包一层:

  1. 表面看到的是 BeanCreationException
  2. 往下翻可能是 UnsatisfiedDependencyException
  3. 再往下可能才是真正根因,比如数据库连不上

所以排查时不要只看第一行异常名。

真正有价值的是:

  1. Caused by
  2. 最底层异常
  3. 关键报错关键词

这就是为什么很多人看了半天 Spring 异常,越看越晕。

因为他们在看"包装层",不是"根因层"。

4.2 为什么要先判断问题属于哪一层

比如一个接口请求失败,可能是:

  1. 路径没映射到
  2. 参数没绑定上
  3. Service 抛异常
  4. 数据库查挂了
  5. 配置指向错环境

如果你不先分层,就会:

  1. 一会改 Controller
  2. 一会改配置
  3. 一会改 SQL
  4. 最后全改乱了

正确做法是先问自己:

  1. 请求有没有到 Controller
  2. 参数是否绑定成功
  3. Service 是否执行到
  4. SQL 是否发出
  5. 事务是否提交

4.3 排障为什么要做"最小验证"

工程上最忌讳一次改很多地方。

因为这样你不知道究竟是哪个修改解决了问题。

最小验证的意思是:

  1. 先只改一个最可能的点
  2. 再验证现象是否变化
  3. 让问题缩小,而不是扩大

这是一种非常重要的工程习惯。

5. 示例

5.1 启动失败排查示例

常见报错:

text 复制代码
APPLICATION FAILED TO START

第一轮排查顺序:

  1. 看最底部 Caused by
  2. 判断是配置问题、Bean 问题还是数据库问题
  3. 看最近改动了依赖、配置还是代码
  4. 如果涉及数据库,先确认数据库本身能否连通

常见根因示例:

  1. 端口被占用
  2. 数据库账号密码错
  3. Bean 注入失败
  4. 某个配置类写错

5.2 Bean 注入失败示例

典型异常:

text 复制代码
NoSuchBeanDefinitionException

第一轮排查顺序:

  1. 这个类有没有加 @Component / @Service / @Repository / @Controller
  2. 它是不是在启动类扫描路径下
  3. 是不是接口注入但没有对应实现类
  4. 是不是有多个同类型 Bean 导致歧义
  5. 是不是这个对象根本不该靠扫描,而应该用 @Bean

高频根因:

  1. 包路径不在扫描范围
  2. 少了注解
  3. 注入的是接口,但没有实现
  4. 有多个实现但没指定

5.3 端口冲突示例

常见报错关键词:

text 复制代码
Port 8080 was already in use

排查思路:

  1. 改配置换端口
  2. 查哪个进程占了端口
  3. 关掉占用进程或改当前服务端口

你要建立一个习惯:

看到这类报错,不要怀疑代码,先怀疑环境。

5.4 配置不生效示例

比如你明明写了:

yaml 复制代码
server:
  port: 8081

结果服务还是跑在 8080

第一轮排查顺序:

  1. 当前激活的是哪个 Profile
  2. 是否有更高优先级配置覆盖
  3. 配置键名是否写对
  4. 配置文件是否真的被加载
  5. 是否写到了错误文件位置

高频原因:

  1. application-prod.yml 覆盖了默认值
  2. 启动参数传了 --server.port=8080
  3. 当前并没有启用你以为的环境

5.5 数据库连接失败示例

常见报错关键词:

text 复制代码
Communications link failure
Access denied for user
Unknown database

第一轮排查顺序:

  1. 数据库服务是否启动
  2. IP、端口、库名是否正确
  3. 用户名密码是否正确
  4. 驱动版本是否匹配
  5. 本机能否手工连上数据库

一个很重要的习惯:

先验证数据库本身能连,再怀疑 Spring Boot 配置。

5.6 事务不生效示例

典型现象:

  1. 方法里抛异常了
  2. 但前面的数据库操作没有回滚

第一轮排查顺序:

  1. 方法是否加了 @Transactional
  2. 方法是不是 public
  3. 是否通过 Spring 代理调用,而不是类内部自调用
  4. 抛出的异常类型是否会触发回滚
  5. 数据库表引擎是否支持事务

高频根因:

  1. 自调用导致代理失效
  2. 抛的是受检异常但没配置回滚规则
  3. 根本没走 Spring 管理的 Bean

5.7 Web 接口错误排查示例

404

排查顺序:

  1. 路径是否写对
  2. Controller 是否被扫描到
  3. 映射注解是否写对
  4. 类级别路径和方法级别路径拼起来是不是正确
405

排查顺序:

  1. 路径通常是对的
  2. 先看 HTTP 方法是否错了
  3. 比如接口定义了 @PostMapping,你却发了 GET
400

排查顺序:

  1. 参数名是否匹配
  2. 参数类型能否转换
  3. JSON 结构是否符合对象字段
  4. 必填参数是否缺失
415

排查顺序:

  1. Content-Type 是否为 application/json
  2. 是否用 @RequestBody 接 JSON
  3. 前端是否真的按 JSON 发送
500

排查顺序:

  1. 先看后端异常栈
  2. 判断是业务异常、空指针、SQL 问题还是外部服务问题
  3. 不要只看状态码,状态码只能告诉你"服务器出错了"

6. 工程实践

6.1 真实工程场景

你在公司里最常遇到的不是"从零写一个框架",而是:

  1. 拉下项目启动失败
  2. 某接口昨天还能用今天 500
  3. 开发环境正常,测试环境不正常
  4. 加了 @Transactional 却没回滚
  5. 改完配置后服务行为完全不符合预期

这时候最重要的不是记忆力,而是排查顺序。

6.2 推荐排查方法论

必须掌握

  1. 先看日志和异常,不凭感觉猜
  2. 先看根因异常,不只看表层异常
  3. 先判断问题层次
  4. 先做最小验证
  5. 先排高频问题:路径、注解、配置、环境、依赖、代理

6.3 一个通用排障模板

以后你可以套这个模板:

  1. 现象:具体报什么错,何时发生
  2. 范围:只在本地、测试、生产,还是所有环境
  3. 分层:启动层、Web 层、数据层、配置层、事务层
  4. 证据:日志关键词、异常栈、HTTP 状态码、SQL 日志
  5. 假设:最可能的 2 到 3 个原因
  6. 验证:做最小改动或最小复现
  7. 结论:根因是什么,如何修复,如何防止复发

6.4 明确操作步骤

建议你做一组"故意制造错误"的练习:

  1. 故意改错数据库密码
  2. 故意让端口冲突
  3. 故意把 Service 放出扫描范围
  4. 故意把 @PostMapping 接口用 GET 调
  5. 故意发错 Content-Type
  6. 故意制造事务自调用失效
  7. 每次都按"现象 -> 根因 -> 修复 -> 复盘"记录

这组训练非常有价值,比只看教程强很多。

7. 常见误区

  1. 看到异常第一反应是上网搜整段报错。

    可以搜,但先自己分层和提炼关键词,不然容易被带偏。

  2. 只看第一行异常。

    Spring 报错经常层层包装,最关键的常常在最后一个 Caused by

  3. 问题一来先改很多地方。

    这样很容易把简单问题改复杂,还失去定位依据。

  4. 默认相信"代码没问题,一定是环境"。

    也不能这样,应该用证据判断,不要先入为主。

  5. 事务不生效就只盯着 @Transactional

    很多时候是代理、自调用、异常类型、调用路径的问题。

  6. 看到 404 就重启项目。

    404 更多时候是路径映射和请求地址问题,不一定和启动有关。

8. 面试题

  1. Spring Boot 项目启动失败时,你通常怎么排查?
  2. NoSuchBeanDefinitionException 常见原因有哪些?
  3. 配置不生效时你会优先看什么?
  4. 数据库连接失败时有哪些高频根因?
  5. 为什么 @Transactional 有时候不生效?
  6. 404、405、400、415、500 分别代表什么,怎么排查?
  7. 你如何区分问题发生在 Web 层、Service 层还是数据层?
  8. 遇到复杂异常栈时,你如何提取真正根因?

9. 自测题

  1. 用你自己的话总结一套 Spring Boot 通用排障流程。
  2. 如果项目启动时报 NoSuchBeanDefinitionException,你会怎么查?
  3. 如果接口调用返回 415,你会从哪些点定位?
  4. 如果事务没有回滚,你会优先排查哪几个方向?
  5. 为什么说"先分类,再看根因,再做最小验证"是排障核心方法?
  6. 动手题:制造一个 Bean 注入失败并修复。
  7. 动手题:制造一个端口冲突并修复。
  8. 动手题:制造一个 400 或 415 并记录分析过程。
  9. 动手题:制造一个数据库连接失败并提炼日志关键词。
  10. 动手题:制造一个事务不生效场景并解释原因。

10. 学完标志

学完这一阶段后,你应该能做到:

  1. 遇到 Spring Boot 常见错误时,不会立刻慌,而是能先分层判断。
  2. 能从异常栈里提取根因,而不是只看表层异常。
  3. 能系统排查 Bean 注入失败、配置不生效、数据库连接失败、事务不生效等高频问题。
  4. 能根据 404、405、400、415、500 快速缩小问题范围。
  5. 能把"修好问题"升级为"总结问题、减少复发"的工程习惯。
  6. 面试时能把排障思路讲成一套方法,而不是零散经验。

下一阶段会进入面试与评估,重点讲:高频面试考点、典型问法、答题层次、项目题怎么答、如何判断自己已经达到初级到中级水平。这一阶段会把你前面的知识整理成可输出、可评估的能力。

第八阶段:面试与评估 + 学习总结与知识闭环

1. 学什么

这一阶段不再新增太多零散知识点,而是做三件最重要的事:

  1. 把前面学过的内容压缩成可输出的面试答案
  2. 建立一套可执行的评估体系,判断自己现在处在哪个水平
  3. 把知识整理成"会解释、会做题、会实战、会复盘"的闭环

这一阶段的目标是:

从"我学过 Spring Boot"变成"我能证明我会 Spring Boot"。

重点内容:

  1. 高频面试考点怎么答
  2. 项目题、原理题、排错题常见问法
  3. 入门、初级、中级的达标标准
  4. 自测题、面试题、实战任务、项目任务
  5. 评分标准
  6. 根据错误结果如何反向补课

必须吃透

  1. 能用自己的话解释核心概念
  2. 能把原理和实战联系起来
  3. 能把排障方法讲成结构化答案
  4. 能把学过的内容组织成项目表达

先知道,后深入

  1. 高级架构设计题
  2. 大规模微服务治理题
  3. 深层源码追踪型面试题

2. 为什么重要

很多人学完一堆知识,最后还是卡在两个地方:

  1. 面试时说不出来
  2. 实际做项目时不知道自己到底会到什么程度

这说明问题不是"没学",而是:

  1. 知识没有形成主线
  2. 没有做输出训练
  3. 没有明确的评估标准

企业面试官通常不会只问"定义是什么",而会继续追问:

  1. 为什么这样设计
  2. 实际项目里怎么用
  3. 出问题怎么排查
  4. 你是怎么权衡的

所以这一阶段的重点不是背题,而是把知识转成"结构化表达能力"。

3. 核心概念

3.1 Spring Boot 面试最常考的几类题

类型 常见问法
基础定义题 Spring Boot 是什么?和 Spring 有什么关系?
核心机制题 自动装配怎么实现?配置为什么能覆盖默认值?
Web 开发题 请求是怎么进 Controller 的?@RequestBody@RequestParam 区别?
数据访问题 MyBatis 和 JPA 怎么选?事务为什么能回滚?
排障题 事务不生效怎么办?配置不生效怎么办?
工程实践题 多环境怎么配?日志怎么打?项目怎么部署?
项目题 你做过什么项目?Spring Boot 在里面承担什么角色?

3.2 一个好答案通常长什么样

一个质量较高的 Spring Boot 面试答案,通常有 4 层:

  1. 先给一句结论
  2. 再讲核心定义
  3. 再讲原理或设计思想
  4. 最后结合项目场景或排障经验

例如回答"什么是 Spring Boot"时,不要只说:

"它是一个 Java 框架。"

更好的结构是:

  1. Spring Boot 是建立在 Spring 之上的快速开发框架
  2. 它通过 Starter 和自动装配降低配置成本
  3. 核心思想是约定优于配置和条件化默认装配
  4. 企业里常用它快速搭建 Web 服务、管理系统和微服务基础骨架

3.3 学习闭环的本质

真正有效的学习闭环,不是"看完了",而是做到这 4 件事:

  1. 能解释
  2. 能写代码
  3. 能排错
  4. 能复盘和补短板

如果缺少其中任何一个,知识都不牢。

4. 原理解释

4.1 为什么面试很爱问 Spring Boot

因为 Spring Boot 非常适合作为"综合能力探针"。

面试官通过它可以顺着一条线判断你:

  1. Java 基础是否扎实
  2. 是否理解 IoC / AOP
  3. 是否会 Web 开发
  4. 是否会数据库和事务
  5. 是否有工程实践经验
  6. 是否真的排过问题

所以 Spring Boot 面试看起来像在问框架,实际上是在问:

你是不是一个能独立做后端业务的人。

4.2 为什么"能答原理"比"背注解"更重要

因为注解只是表象,原理才决定你能不能:

  1. 解释为什么能跑
  2. 解释为什么失效
  3. 解释为什么这么设计
  4. 在新场景里做迁移

比如:

  1. 会背 @Transactional 不够
  2. 你要知道它依赖代理和 AOP
  3. 你要知道自调用可能失效
  4. 你要知道事务边界为什么通常放在 Service

这就是从"会用"到"会解释"的差别。

4.3 为什么要按等级评估自己

因为"我会 Spring Boot"这句话太模糊。

更有用的说法应该是:

  1. 我现在达到入门还是初级
  2. 哪些是稳定会的
  3. 哪些是知道但做不稳
  4. 哪些是面试时还答不完整

这会让你的学习更像工程迭代,而不是模糊堆积。

5. 示例

5.1 高频面试题示例答案模板

题目:Spring Boot 为什么能做到开箱即用?

推荐答法:

  1. 结论:Spring Boot 通过 Starter 和自动装配实现开箱即用。
  2. Starter 负责把某个场景需要的依赖组合好,比如 Web、数据库。
  3. 自动装配会在启动时根据类路径、配置项、是否已有 Bean 等条件,自动注册一批默认组件。
  4. 它不是无脑全配,而是条件成立才配置,同时用户还可以通过自定义 Bean 和配置覆盖默认行为。
  5. 在项目里,这让我们能快速启动一个 Web 服务,而不需要像传统 Spring 那样写大量样板配置。

5.2 项目题回答模板

题目:你在项目里是怎么用 Spring Boot 的?

推荐答法结构:

  1. 项目背景:这是一个什么系统
  2. 技术角色:Spring Boot 在系统里负责什么
  3. 你负责的模块
  4. 关键技术点:比如接口开发、事务、MyBatis、多环境配置、日志
  5. 你解决过的一个问题
  6. 你的思考或优化

示例框架:

text 复制代码
我做的是一个用户管理和订单处理的后台系统,Spring Boot 主要作为整个服务的基础框架。
我负责用户模块和订单模块的接口开发,主要用了 Spring MVC 做 REST API,MyBatis 做数据库访问,Service 层用事务保证多表操作一致性。
工程上我们通过 Profile 管理多环境配置,用日志和 Actuator 做基础排查。
我印象比较深的是处理过一次事务不生效的问题,最后定位是类内部自调用导致代理没生效。

5.3 自我表达训练示例

你可以把每个核心主题都练成"30 秒版"和"3 分钟版"。

例如"自动装配":

30 秒版:

  1. 自动装配是 Spring Boot 根据依赖、配置和上下文条件,自动注册默认 Bean 的机制。
  2. 核心思想是条件成立才配置,并允许用户覆盖默认实现。
  3. 它让项目能快速开箱即用。

3 分钟版:

  1. 先解释 Starter 和自动装配关系
  2. 再解释条件注解
  3. 再讲 @ConditionalOnMissingBean 为什么能让用户覆盖
  4. 最后讲一个 Web 或数据源场景

6. 工程实践

6.1 入门 / 初级 / 中级达标标准

入门达标标准

达到以下标准,说明你已经入门:

  1. 能独立创建 Spring Boot 项目并启动
  2. 能写基础 REST 接口
  3. 能解释 Spring、Spring MVC、Spring Boot 的关系
  4. 知道 Bean、IoC、DI 是什么
  5. 会基础 application.yml 配置
初级达标标准

达到以下标准,说明你已经具备初级开发能力:

  1. 能完成 Controller + Service + Mapper 的基础业务链路
  2. 能接 MySQL,写基本 CRUD
  3. 能使用事务并解释基本原理
  4. 能做多环境配置
  5. 能写基础测试
  6. 能排查常见 404、400、Bean 注入失败、数据库连接失败
中级达标标准

达到以下标准,说明你已经接近中级工程师理解深度:

  1. 能解释 Spring Boot 启动流程和自动装配主干机制
  2. 能解释配置优先级、Bean 生命周期、条件装配思想
  3. 能系统排查事务失效、配置失效、复杂启动失败
  4. 能讲清 MyBatis/JPA 取舍、事务边界设计、工程分层规范
  5. 能把项目经验组织成结构化表达
  6. 能从工程角度讨论日志、部署、监控、可维护性

6.2 一套自测题

  1. Spring Boot 和 Spring Framework 的关系是什么?
  2. 什么是 Bean?什么是 IoC 和 DI?
  3. @Component@Bean 有什么区别?
  4. 一个请求是如何到达 Controller 的?
  5. @RequestParam@PathVariable@RequestBody 分别适合什么场景?
  6. MyBatis 和 JPA 有什么区别?
  7. @Transactional 为什么能生效?
  8. Spring Boot 启动流程大致是什么?
  9. 自动装配为什么能做到开箱即用?
  10. 配置不生效时你会怎么排查?

6.3 一套面试题

  1. Spring Boot 的核心优势是什么?
  2. @SpringBootApplication 做了什么?
  3. Starter 和自动装配分别是什么?
  4. 为什么构造器注入比字段注入更推荐?
  5. 事务为什么一般写在 Service 层?
  6. 为什么事务有时不生效?
  7. Spring MVC 中 DispatcherServlet 的作用是什么?
  8. 为什么 Controller 返回对象能自动变成 JSON?
  9. 多环境配置一般怎么做?
  10. 你在项目里怎么做日志、监控和排错?

6.4 一套实战任务

建议你做一个"用户管理 + 订单管理"练习系统,至少完成:

  1. 用户新增、查询、修改、删除接口
  2. 订单新增、查询接口
  3. MyBatis 接 MySQL
  4. Service 层事务
  5. Profile 区分 dev/prod
  6. 日志
  7. 基础测试
  8. Actuator 健康检查

6.5 一套项目任务

如果你想从"练习项目"再往上走一步,建议做这个项目:

项目名建议:springboot-mall-lite

模块建议:

  1. 用户模块
  2. 商品模块
  3. 订单模块
  4. 登录鉴权基础版
  5. 全局异常处理
  6. 统一返回结构
  7. 配置分环境
  8. 日志和健康检查

项目目标:

  1. 能跑通完整业务链路
  2. 能展示分层设计
  3. 能体现事务、配置、日志、排障能力
  4. 能拿来做简历项目和面试讲解素材

6.6 评分标准

你回答问题或完成任务时,可以按这个标准自评:

维度 分值 评价标准
概念准确性 20 定义是否正确,有无混淆
原理理解 20 能否说明为什么这样设计
实战能力 20 能否写代码、跑通场景
排障能力 20 能否定位并解释常见问题
表达结构 10 回答是否有层次
工程意识 10 是否考虑日志、测试、配置、部署

评分参考:

  1. 90-100:已接近中级理解深度
  2. 75-89:初级较扎实,可继续补机制和项目表达
  3. 60-74:入门到初级过渡期,主线已建立但不稳定
  4. 60 以下:建议回到核心概念、Web、数据访问三条主线重练

7. 常见误区

  1. 以为面试就是背八股。

    真正好的回答一定有"定义 + 原理 + 场景 + 排障"。

  2. 以为能看懂笔记就等于会。

    没有输出训练,面试时通常说不出来。

  3. 以为刷很多题就会了。

    如果没有项目和代码支撑,答案很容易空。

  4. 以为自己"差不多会了",但没有明确标准。

    没有达标标准,就很难知道下一步补什么。

  5. 只练短答案,不练结构化表达。

    面试官一追问,你就容易断层。

  6. 只看正确答案,不记录错误原因。

    错误结果本身就是最好的补课入口。

8. 面试题

  1. Spring Boot 相比传统 Spring 的核心价值是什么?
  2. 你如何理解 IoC、DI、AOP 在 Spring Boot 项目中的作用?
  3. 自动装配的设计思想是什么?
  4. 为什么 Spring Boot 能快速启动一个 Web 项目?
  5. 一个请求从进入系统到返回 JSON,经历了哪些关键组件?
  6. 事务为什么可能失效?你实际会怎么查?
  7. Spring Boot 项目如何管理多环境配置?
  8. 你在项目里如何组织分层、日志和监控?
  9. MyBatis 和 JPA 你会怎么选?
  10. 如果让你从零搭一个 Spring Boot 项目,你会怎么做?

9. 自测题

  1. 用 1 分钟介绍 Spring Boot。
  2. 用 2 分钟解释自动装配。
  3. 用 2 分钟解释事务为什么能回滚。
  4. 用 2 分钟讲一个请求进入 Controller 的流程。
  5. 用 2 分钟讲一次你如何排查"配置不生效"问题。
  6. 用 3 分钟讲一个你可以独立完成的 Spring Boot 小项目。
  7. 现场实操题:从零起一个用户管理接口。
  8. 现场实操题:接数据库并完成新增 + 查询。
  9. 现场实操题:故意制造一个事务问题并解释。
  10. 复盘题:你目前最薄弱的 3 个点是什么,准备怎么补?

10. 学完标志

学完这一阶段后,你应该能做到:

  1. 能把 Spring Boot 的核心知识压缩成结构化面试答案。
  2. 能判断自己目前是入门、初级还是接近中级。
  3. 能用自测题、面试题、实战任务、项目任务来验证真实水平。
  4. 能按评分标准判断自己弱在概念、原理、实战、排障还是表达。
  5. 能根据错误结果反向补课,而不是盲目重复学习。
  6. 已经形成完整知识闭环:会理解、会解释、会上手、会排错、能应对基础到中级面试。

错误结果如何反向补课

这是整套学习里最重要的"复盘机制"。

如果你错在概念定义

回补顺序:

  1. 重新看第二阶段核心概念
  2. 把 IoC、DI、Bean、Starter、自动装配重新用自己的话解释一遍
  3. 要求自己能不看笔记口述

如果你错在 Web 主线

回补顺序:

  1. 回到第三阶段
  2. 重写 @PathVariable@RequestParam@RequestBody 示例
  3. 重新画一遍请求处理流程

如果你错在数据访问和事务

回补顺序:

  1. 回到第四阶段
  2. 重做 MyBatis CRUD 和事务回滚实验
  3. 重点解释事务为什么能生效、为什么会失效

如果你错在机制题

回补顺序:

  1. 回到第五阶段
  2. 重点复习启动流程、条件装配、配置优先级
  3. 把"自动装配为什么不是魔法"重新讲一遍

如果你错在排障题

回补顺序:

  1. 回到第七阶段
  2. 做"故意制造错误"的训练
  3. 每次按"现象 -> 分层 -> 根因 -> 验证 -> 修复"写复盘

如果你错在表达

回补顺序:

  1. 每个主题准备 30 秒版、1 分钟版、3 分钟版
  2. 录音复述
  3. 删掉空话,只保留"定义 + 原理 + 场景 + 排障"

Spring Boot 学习总结

到这里,你应该已经把 Spring Boot 最重要、最常用、最有实战价值的 80% 主干内容串起来了。

你现在的知识地图应该是:

  1. 知道 Spring Boot 是建立在 Spring 之上的快速开发框架
  2. 理解 IoC、DI、Bean、自动装配、配置优先级这些底层主线
  3. 会写 Web 接口
  4. 会接数据库、会做事务
  5. 会做基本工程实践
  6. 会做基础排障
  7. 能应对基础到中级面试中的高频问题

下一步深入方向

如果你要继续进阶,建议按这个顺序:

  1. Spring Security
  2. 全局异常处理与统一响应封装
  3. 参数校验 Validation
  4. Redis
  5. 缓存设计
  6. 消息队列
  7. Spring Cloud / 微服务
  8. Spring 源码与自动装配源码

一句话总结这门技术的学习本质:

Spring Boot 不是背注解,而是理解"容器如何管理对象、框架如何按条件装配能力、工程系统如何稳定运行"。

相关推荐
计算机安禾2 小时前
【数据结构与算法】第20篇:二叉树的链式存储与四种遍历(前序、中序、后序、层序)
c语言·开发语言·数据结构·c++·学习·算法·visual studio
s1mple“”2 小时前
大厂Java面试实录:从Spring Boot到AI技术的医疗健康场景深度解析
spring boot·redis·微服务·kafka·向量数据库·java面试·ai技术
Lyyaoo.2 小时前
Spring Boot自动配置
java·spring boot·后端
呆毛cyan2 小时前
K8s与CICD 部署 - 3. Harbor
后端
呆毛cyan2 小时前
K8s与CICD 部署 - 3. Jenkins - 1.k8s安装jenkins
后端
m0_564876842 小时前
提示词工程Zero-Shot、One-Shot、Few-Shot
人工智能·深度学习·学习
后端不背锅3 小时前
设计模式在业务开发中的实战指南
后端
神奇小汤圆3 小时前
进程 vs 线程:从原理到区别,一次讲清楚
后端
ZhiqianXia3 小时前
Gem5 学习笔记(3) : 源代码鸟瞰
笔记·学习