SpringBoot 教程 IDEA 版

1、这阶段该如何学习(学习规划篇)

1.1 学习核心逻辑

SpringBoot 学习需遵循 **「基础铺垫→快速上手→核心原理→实战进阶」** 的四阶段逻辑,切忌跳过基础直接啃源码。

1.2 分阶段学习路径

阶段 核心目标 必学内容 耗时参考
基础铺垫期 扫清前置障碍 Java8 + 基础、Maven 依赖管理、Spring 核心(IOC/AOP) 1-2 周
快速上手期 跑通项目 + 基础开发 环境搭建、项目创建、Web 接口开发、配置文件使用 1-2 周
核心原理期 理解底层机制 自动装配原理、主启动类运行机制、Starter 自定义 2-3 周
实战进阶期 企业级开发能力 整合 MyBatis/Redis、异常处理、接口文档、微服务整合 3-4 周

1.3 避坑指南

  1. 版本匹配:SpringBoot3.x 必须搭配 JDK17+,SpringBoot2.x 搭配 JDK8+,否则会出现依赖冲突。
  2. 拒绝死磕源码:初期聚焦「怎么用」,理解核心流程后再深挖源码。
  3. 工具优先:使用 IDEA 开发,利用其 Spring Initializr 快速生成项目,减少环境配置成本。

2、什么是 SpringBoot(核心认知篇)

2.1 定义与定位

SpringBoot 是基于 Spring 框架的快速开发脚手架,核心目标是简化 Spring 应用的初始搭建与开发过程,实现「零 XML 配置、一键启动、内置服务器」。

2.2 核心优势(对比传统 Spring)

  1. 约定大于配置:默认配置覆盖 80% 常用场景,无需手动配置 XML。
  2. 内嵌服务器:内置 Tomcat/Jetty/Undertow,无需额外部署服务器。
  3. 依赖自动管理:通过 Starter 启动器统一管理依赖版本,解决版本冲突问题。
  4. 生产级特性:内置监控、指标、健康检查等功能,适配生产环境部署。

2.3 应用场景

适用于中小型 Web 项目、微服务架构基础层、快速原型开发,是目前 Java 后端开发的主流技术栈。

3、什么是微服务架构(架构认知篇)

3.1 核心定义

微服务是一种分布式架构风格,将单体应用按业务领域拆分为多个独立、自治的小型服务,每个服务独立部署、独立运维,通过 HTTP/RPC 等协议通信协作。

3.2 单体架构 vs 微服务架构(核心差异)

维度 单体架构 微服务架构
代码结构 所有模块打包在一个工程,单进程运行 按业务拆分为独立工程,多进程运行
数据存储 共享单一数据库 每个服务独立数据库,数据自治
部署迭代 一处修改全量部署,风险高 单个服务独立迭代,部署灵活
故障影响 单点故障导致整个系统崩溃 故障隔离,仅影响单个服务

3.3 核心特征与适用场景

  1. 核心特征:单一职责、服务自治、轻量级通信、去中心化治理、故障隔离。
  2. 适用场景 :中大型团队、业务复杂度高、需多团队并行开发;不适用:小型项目、初创团队(硬上会增加运维成本)。

3.4 理论基础

  1. CAP 定理:分布式系统中,一致性(C)、可用性(A)、分区容错性(P)三者不可兼得,微服务中优先保证 CP/AP。
  2. BASE 理论:CAP 的落地实践,核心是基本可用、软状态、最终一致性。

4、第一个 SpringBoot 程序(实战入门篇)

4.1 环境准备

  1. 必备工具:JDK1.8+(推荐 1.8)、Maven3.6+、IDEA(社区版 / 旗舰版)。

  2. 环境验证

    复制代码
    # 验证JDK版本
    java -version
    # 验证Maven版本
    mvn -v

4.2 项目创建(Spring Initializr)

  1. 打开 IDEA → 新建项目 → 选择「Spring Initializr」。
  2. 基础配置:
    • Group:com.example(自定义,反向域名格式)
    • Artifact:springboot-demo
    • Type:Maven
    • Java Version:8
  3. 依赖选择(初学者必选):
    • Web → Spring Web(开发 Web 接口)
    • Developer Tools → Spring Boot DevTools(热部署)
    • Core → Lombok(简化实体类代码)
  4. 完成创建,等待 Maven 下载依赖。

4.3 项目结构解析

复制代码
springboot-demo/
├── src/
│   ├── main/
│   │   ├── java/com/example/springbootdemo/
│   │   │   ├── SpringbootDemoApplication.java  # 启动类
│   │   │   ├── controller/  # 接口层
│   │   │   ├── service/     # 业务层
│   │   │   └── mapper/      # 数据访问层
│   │   └── resources/
│   │       ├── application.properties  # 配置文件
│   │       ├── static/                 # 静态资源
│   │       └── templates/              # 模板文件
│   └── test/  # 测试类
└── pom.xml    # 依赖配置文件

4.4 编写第一个接口

  1. 在 controller 包下创建 HelloController:

    复制代码
    package com.example.springbootdemo.controller;
    
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    /**
     * @RestController = @Controller + @ResponseBody,返回JSON数据
     */
    @RestController
    public class HelloController {
    
        /**
         * @GetMapping:处理GET请求,指定访问路径
         */
        @GetMapping("/hello")
        public String helloSpringBoot() {
            return "Hello SpringBoot!这是我的第一个接口~";
        }
    }

4.5 运行与测试

  1. 运行启动类 SpringbootDemoApplication(main 方法)。
  2. 浏览器访问:http://localhost:8080/hello,返回结果即表示成功。

5、IDEA 快速创建及彩蛋(工具优化篇)

5.1 快速创建进阶技巧

  1. 自定义初始依赖:创建项目时可直接勾选所需依赖(如 MyBatis、MySQL),无需后续手动添加。
  2. 离线模式创建:网络不佳时,使用 IDEA 离线模式创建项目,优先加载本地缓存依赖。
  3. 多模块项目创建:通过「New → Module」快速添加子模块,适配多模块架构开发。

5.2 IDEA 核心彩蛋(效率提升技巧)

  1. 代码生成:右键 → Generate → 快速生成 get/set、构造器、toString 等方法(配合 Lombok 可省略此步骤)。
  2. 热部署配置
    • 开启 DevTools 后,修改代码无需重启项目,自动生效。
    • IDEA 设置:File → Settings → Build, Execution, Deployment → Compiler → 勾选「Build project automatically」。
  3. 快捷键优化
    • Ctrl+Alt+O:优化导入包(去除无用导入)。
    • Ctrl+Shift+Alt+N:快速查找类 / 方法。
    • Alt+Insert:快速生成代码模板。
  4. 运行配置:启动类配置中开启「Allow parallel run」,支持多项目并行启动。

6、SpringBoot 自动装配原理(核心原理解析篇)

6.1 核心本质

自动装配是 SpringBoot 实现「零配置」的核心,基于Spring 条件注解 + SPI 机制,根据项目依赖自动配置组件,无需手动编写 XML 配置。

6.2 核心入口:@SpringBootApplication

该注解是组合注解,包含三个核心子注解:

复制代码
@SpringBootConfiguration // 标识配置类,等价于@Configuration
@ComponentScan           // 扫描当前包及子包的Bean
@EnableAutoConfiguration  // 开启自动装配,核心注解
public @interface SpringBootApplication {}

6.3 自动装配执行流程

  1. 加载候选配置:通过 SpringFactoriesLoader 加载 META-INF/spring.factories(2.x)/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports(3.x)中的自动配置类。
  2. 条件过滤:通过 @Conditional 系列注解(@ConditionalOnClass、@ConditionalOnMissingBean 等)筛选符合当前环境的配置类。
  3. 注册 Bean:过滤后的配置类被解析为 Spring 配置类,通过 @Bean 注解将组件注册到 IOC 容器。

6.4 核心规则

  1. 用户配置优先:@ConditionalOnMissingBean 保证用户自定义组件覆盖自动配置组件。
  2. Starter 依赖封装:Starter 启动器封装了依赖版本,引入 starter 即可自动管理依赖。

7、了解下主启动类怎么运行(启动流程篇)

7.1 主启动类核心代码

复制代码
package com.example.springbootdemo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * 主启动类:SpringBoot应用的入口
 */
@SpringBootApplication
public class SpringbootDemoApplication {

    public static void main(String[] args) {
        // 核心方法:启动SpringBoot应用
        SpringApplication.run(SpringbootDemoApplication.class, args);
    }
}

7.2 运行流程拆解

  1. 初始化 SpringApplication:加载应用类型、初始化初始化器、监听器等核心组件。
  2. 执行 run 方法
    • 加载配置环境(Environment),获取配置信息。
    • 创建 IOC 容器(ApplicationContext)。
    • 执行自动装配,加载 Bean 定义。
    • 刷新容器(refresh ()),完成 Bean 的创建、初始化。
    • 启动内嵌服务器(Tomcat),监听端口。

7.3 关键节点

  1. 包扫描规则 :默认扫描主启动类所在包及子包,若 Controller/Service 不在该包下,需手动指定扫描路径:

    复制代码
    @SpringBootApplication(scanBasePackages = "com.xxx")
  2. 异常启动排查

    • 端口占用:通过 server.port=8081 修改端口。
    • 依赖冲突:使用 mvn dependency:tree 排查依赖冲突,排除多余依赖。

8、yaml 语法讲解(配置文件篇)

8.1 定义与优势

YAML(YAML Ain't Markup Language)是人性化的序列化语言,相比 properties 文件,支持层级结构、注释、数据类型,更适合复杂配置。

8.2 核心语法规则

  1. 缩进规范:使用空格缩进(禁止 Tab),同一层级缩进一致(推荐 2/4 个空格)。
  2. 键值对格式:key: value(冒号后必须加空格)。
  3. 数据类型
    • 字符串:无需引号,含特殊字符时用单 / 双引号。
    • 布尔值:true/false。
    • 数字:整数 / 浮点数。
    • 空值:用~表示。

8.3 常用数据结构

  1. 对象 / 嵌套配置

    yaml

    复制代码
    # 基础对象
    user:
      name: 张三
      age: 20
      gender: 男
    # 嵌套对象
    server:
      port: 8080
      servlet:
        context-path: /demo
  2. 数组 / 列表

    yaml

    复制代码
    # 列表
    hobbies:
      - 编程
      - 阅读
      - 运动
    # 行内列表
    hobbies: [编程, 阅读, 运动]
  3. 多行字符串

    yaml

    复制代码
    # 保留换行
    desc: |
      SpringBoot是快速开发脚手架
      支持自动装配、内嵌服务器
    # 折叠换行
    desc: >
      SpringBoot是快速开发脚手架
      支持自动装配、内嵌服务器

8.4 SpringBoot 中 YAML 使用

  1. 文件命名:application.yml(核心配置文件)。
  2. 配置读取
    • 使用 @Value 注解读取单个配置:

      复制代码
      @RestController
      public class ConfigController {
          @Value("${user.name}")
          private String userName;
          
          @GetMapping("/name")
          public String getUserName() {
              return userName;
          }
      }
    • 使用 @ConfigurationProperties 读取批量配置:

      复制代码
      @Component
      @ConfigurationProperties(prefix = "user")
      @Data
      public class UserConfig {
          private String name;
          private int age;
      }

9、给属性赋值的几种方式

9.1 核心场景

SpringBoot 中给 Bean 属性赋值,是配置与业务解耦的核心手段,常见 5 种主流方式,覆盖不同使用场景。

9.2 方式 1:@Value 注解(简单场景)

  • 适用场景:单个 / 少量配置项注入,简单类型(字符串、数字、布尔)

  • 代码示例

    @Component
    public class UserConfig {
    // 直接注入配置文件值
    @Value("${user.name}")
    private String userName;

    复制代码
      @Value("${user.age:18}") // 冒号后为默认值,配置不存在时生效
      private Integer userAge;
    
      // 支持SpEL表达式
      @Value("#{10 * 2}")
      private Integer score;

    }

  • 优缺点:简单易用,不适合批量配置;仅支持基础类型,不支持复杂对象。

9.3 方式 2:@ConfigurationProperties(批量场景)

  • 适用场景:批量配置注入,复杂对象 / 嵌套配置,推荐企业级开发使用

  • 代码示例

    @Component
    @ConfigurationProperties(prefix = "user") // 绑定配置前缀
    @Data // Lombok简化get/set
    public class UserProperties {
    private String name;
    private Integer age;
    private List<String> hobbies; // 支持集合
    private Map<String, String> info; // 支持Map
    }

  • 配置文件(application.yml)

yaml

复制代码
user:
  name: 张三
  age: 20
  hobbies:
    - 编程
    - 阅读
  info:
    phone: 138xxxx1234
    address: 北京
  • 优缺点 :支持批量注入、复杂类型、自动校验;需配合@EnableConfigurationProperties@Component生效。

9.4 方式 3:@PropertySource(自定义配置文件)

  • 适用场景 :加载自定义xxx.properties/xxx.yml文件,拆分配置

  • 代码示例

    @Component
    @PropertySource("classpath:custom.properties") // 指定自定义配置文件
    @ConfigurationProperties(prefix = "custom")
    @Data
    public class CustomProperties {
    private String title;
    private String desc;
    }

  • 注意 :SpringBoot 2.4+ 对@PropertySource加载 yml 支持有限,优先用 properties 文件。

9.5 方式 4:@Environment(动态读取)

  • 适用场景:运行时动态获取配置,无需提前绑定 Bean

  • 代码示例

    @RestController
    public class EnvController {
    @Autowired
    private Environment environment;

    复制代码
      @GetMapping("/env")
      public String getEnv() {
          // 动态读取配置
          String port = environment.getProperty("server.port");
          String name = environment.getProperty("user.name", "默认名");
          return "端口:" + port + ",用户名:" + name;
      }

    }

9.6 方式 5:命令行 / 系统环境变量(启动时赋值)

  • 适用场景:启动时动态传参,无需修改配置文件

  • 示例

    命令行传参(优先级最高)

    java -jar demo.jar --server.port=8081 --user.name=李四

    系统环境变量(Linux/Mac)

    export USER_NAME=王五
    java -jar demo.jar

  • 优先级规则:命令行参数 > 系统环境变量 > 配置文件(application.yml)


10、JSR303 校验

10.1 核心定义

JSR303 是 Java 后端参数校验的标准规范,SpringBoot 通过spring-boot-starter-validation starter 快速集成,实现接口入参的自动化校验,避免手动校验代码冗余。

10.2 环境准备

  • pom.xml 依赖

xml

复制代码
<!-- SpringBoot 2.3+ 需手动引入 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

10.3 常用校验注解

注解 作用 适用类型
@NotNull 不能为 null 任意类型
@NotBlank 不能为 null 且长度 > 0(去除空格) 字符串
@NotEmpty 不能为 null 且长度 > 0 字符串、集合、数组
@Min/@Max 数值最小 / 最大值 数字
@Email 邮箱格式校验 字符串
@Pattern 自定义正则校验 字符串
@Length 字符串长度范围 字符串

10.4 代码实战

  1. 实体类加校验注解

    @Data
    public class UserDTO {
    @NotBlank(message = "用户名不能为空")
    @Length(min = 3, max = 10, message = "用户名长度需在3-10之间")
    private String username;

    复制代码
     @NotBlank(message = "密码不能为空")
     @Length(min = 6, max = 20, message = "密码长度需在6-20之间")
     private String password;
    
     @Email(message = "邮箱格式不正确")
     private String email;
    
     @Min(value = 18, message = "年龄不能小于18岁")
     private Integer age;

    }

  2. Controller 开启校验

    @RestController
    @RequestMapping("/user")
    public class UserController {
    @PostMapping("/add")
    public String addUser(@Valid @RequestBody UserDTO userDTO, BindingResult result) {
    // 校验不通过,返回错误信息
    if (result.hasErrors()) {
    return result.getFieldError().getDefaultMessage();
    }
    return "用户添加成功";
    }
    }

  3. 全局异常处理(优化返回)

    @RestControllerAdvice
    public class GlobalExceptionHandler {
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Map<String, String> handleValidException(MethodArgumentNotValidException e) {
    Map<String, String> errorMap = new HashMap<>();
    e.getBindingResult().getFieldErrors().forEach(error -> {
    errorMap.put(error.getField(), error.getDefaultMessage());
    });
    return errorMap;
    }
    }

10.5 进阶:分组校验

  • 适用场景:同一实体类不同接口(新增 / 编辑)使用不同校验规则

  • 代码示例

    // 定义分组接口
    public interface AddGroup {}
    public interface UpdateGroup {}

    // 实体类加分组
    @Data
    public class UserDTO {
    @NotNull(message = "用户ID不能为空", groups = UpdateGroup.class)
    private Long id;

    复制代码
      @NotBlank(message = "用户名不能为空", groups = {AddGroup.class, UpdateGroup.class})
      private String username;

    }

    // Controller指定分组
    @PostMapping("/add")
    public String addUser(@Validated(AddGroup.class) @RequestBody UserDTO userDTO) {
    return "新增成功";
    }

    @PutMapping("/update")
    public String updateUser(@Validated(UpdateGroup.class) @RequestBody UserDTO userDTO) {
    return "更新成功";
    }


11、多环境配置及配置文件位置

11.1 多环境配置核心需求

开发、测试、生产环境的配置(数据库、端口、日志)不同,需通过多环境配置实现一键切换,避免手动修改配置文件。

11.2 多环境配置实现

  1. 文件命名规则application-{环境名}.yml,如:
    • application-dev.yml(开发环境)
    • application-test.yml(测试环境)
    • application-prod.yml(生产环境)
  2. 主配置文件(application.yml)指定激活环境

yaml

复制代码
# 主配置文件
spring:
  profiles:
    active: dev # 激活开发环境
  1. 各环境配置示例

yaml

复制代码
# application-dev.yml
server:
  port: 8080
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/dev_db
    username: root
    password: 123456

# application-prod.yml
server:
  port: 80
spring:
  datasource:
    url: jdbc:mysql://prod-mysql:3306/prod_db
    username: prod_user
    password: xxxxxx

11.3 配置文件加载位置与优先级

SpringBoot 启动时会按优先级从高到低 加载以下位置的application.yml/application.properties,高优先级配置覆盖低优先级:

  1. 项目根目录下的/config文件夹
  2. 项目根目录
  3. classpath:/config/(resources 下的 config 文件夹)
  4. classpath:/(resources 根目录)

11.4 环境切换的其他方式

  1. 命令行启动指定环境

    java -jar demo.jar --spring.profiles.active=prod

  2. IDEA 启动指定环境

    • 编辑启动配置 → VM options 添加:-Dspring.profiles.active=test
  3. 系统环境变量指定

    export SPRING_PROFILES_ACTIVE=prod
    java -jar demo.jar


12、自动配置原理再理解

12.1 核心回顾

自动配置是 SpringBoot 的灵魂,核心是 **「条件化加载 + SPI 机制」**,根据项目依赖自动注入 Bean,无需手动配置。

12.2 核心入口:@EnableAutoConfiguration

该注解是自动配置的开关,包含两个核心子注解:

复制代码
@AutoConfigurationPackage // 自动扫描主启动类所在包的组件
@Import(AutoConfigurationImportSelector.class) // 核心:加载自动配置类
public @interface EnableAutoConfiguration {}

12.3 自动配置完整流程

  1. 加载候选配置类
    • SpringBoot 2.x:通过SpringFactoriesLoader加载META-INF/spring.factories中的所有自动配置类
    • SpringBoot 3.x:通过AutoConfigurationImportSelector加载META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports中的配置类
  2. 条件过滤(核心)
    • 通过@Conditional系列注解过滤不符合当前环境的配置类,常见注解:
      • @ConditionalOnClass:类路径下存在指定类才生效
      • @ConditionalOnMissingBean:容器中不存在指定 Bean 才生效(用户自定义 Bean 优先)
      • @ConditionalOnProperty:配置文件中存在指定属性才生效
  3. 注册 Bean 到 IOC 容器
    • 过滤后的配置类被解析,通过@Bean注解将组件注册到 Spring 容器
  4. 配置生效
    • 自动配置的 Bean 可直接在项目中使用,如DataSourceRestTemplate

12.4 自定义 Starter(自动配置实战)

  1. 创建自动配置类

    @Configuration
    @ConditionalOnClass(MyService.class) // 类路径存在MyService才生效
    @EnableConfigurationProperties(MyProperties.class) // 绑定配置
    public class MyAutoConfiguration {
    @Bean
    @ConditionalOnMissingBean // 用户未自定义时才创建
    public MyService myService(MyProperties properties) {
    return new MyService(properties);
    }
    }

  2. 注册自动配置类

    • resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports中添加:

    plaintext

    复制代码
    com.example.config.MyAutoConfiguration
  3. 引入 Starter 即可自动生效,无需手动配置。

12.5 调试技巧

  • 启动时添加--debug参数,查看自动配置报告:

    java -jar demo.jar --debug

  • 控制台会输出Positive matches(生效的自动配置)和Negative matches(未生效的自动配置),方便排查问题。


13、web 开发探究

13.1 SpringBoot Web 开发核心特性

SpringBoot 通过spring-boot-starter-web starter,自动集成了 SpringMVC、Tomcat、JSON 处理等 Web 开发核心组件,实现零配置开箱即用。

13.2 自动配置的核心组件

组件 作用
DispatcherServlet SpringMVC 核心分发器,自动注册
内嵌 Tomcat 默认端口 8080,无需额外部署
Jackson JSON 序列化 / 反序列化,自动处理接口返回
静态资源映射 自动映射/static/public等目录
视图解析器 自动配置 Thymeleaf 等模板引擎
全局异常处理 支持@RestControllerAdvice全局捕获

13.3 Web 开发核心流程

  1. 请求入口 :客户端请求进入 Tomcat,由DispatcherServlet拦截
  2. 路由匹配 :通过@RequestMapping/@GetMapping等注解匹配 Controller
  3. 参数解析:自动解析请求参数、JSON、表单数据
  4. 业务处理:Controller 调用 Service 完成业务逻辑
  5. 响应处理:自动将返回值序列化为 JSON,返回给客户端

13.4 自定义 Web 配置(不破坏自动配置)

  • 方式 1:WebMvcConfigurer(推荐)

    @Configuration
    public class WebConfig implements WebMvcConfigurer {
    // 自定义拦截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(new LoginInterceptor())
    .addPathPatterns("/")
    .excludePathPatterns("/login", "/static/
    ");
    }

    复制代码
      // 自定义跨域配置
      @Override
      public void addCorsMappings(CorsRegistry registry) {
          registry.addMapping("/**")
                  .allowedOrigins("*")
                  .allowedMethods("GET", "POST", "PUT", "DELETE");
      }

    }

  • 方式 2:@Bean自定义组件

    @Configuration
    public class WebConfig {
    // 自定义消息转换器,处理日期格式
    @Bean
    public MappingJackson2HttpMessageConverter messageConverter() {
    MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
    ObjectMapper mapper = new ObjectMapper();
    mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
    converter.setObjectMapper(mapper);
    return converter;
    }
    }

13.5 注意事项

  • 禁止使用@EnableWebMvc:该注解会完全禁用 SpringBoot 的 Web 自动配置,仅在完全自定义 SpringMVC 时使用。
  • 拦截器优先级 :通过order()方法指定拦截器执行顺序,数值越小优先级越高。

14、静态资源导入探究

14.1 静态资源默认映射规则

SpringBoot 自动配置了静态资源映射,无需手动配置,默认映射以下目录(classpath即 resources 目录):

  • /static
  • /public
  • /resources
  • /META-INF/resources

14.2 访问规则

  • 静态资源放在以上目录中,可直接通过/资源名访问,无需前缀
  • 示例:resources/static/css/style.css → 访问路径:http://localhost:8080/css/style.css

14.3 自定义静态资源映射

  1. 方式 1:配置文件修改

yaml

复制代码
spring:
  web:
    resources:
      static-locations: classpath:/my-static/, classpath:/custom/ # 自定义静态资源目录
  1. 方式 2:WebMvcConfigurer配置

    @Configuration
    public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
    registry.addResourceHandler("/static/**") // 访问路径
    .addResourceLocations("classpath:/my-static/") // 实际目录
    .setCachePeriod(3600); // 缓存时间(秒)
    }
    }

14.4 静态资源缓存配置

yaml

复制代码
spring:
  web:
    resources:
      cache:
        period: 3600 # 静态资源缓存时间(秒),生产环境建议开启
        cache-control:
          max-age: 3600
          no-cache: false

14.5 常见问题排查

  1. 静态资源 404
    • 检查资源是否放在默认目录,或自定义映射是否正确
    • 检查是否被拦截器拦截,添加排除路径
  2. 静态资源不更新
    • 关闭缓存(开发环境):spring.web.resources.cache.period=0
    • 清除浏览器缓存,或强制刷新(Ctrl+F5)

15、首页和图标定制

15.1 首页定制

SpringBoot 默认会自动识别静态资源目录下的index.html作为首页,无需额外配置:

  • 放置位置:resources/static/index.html
  • 访问路径:http://localhost:8080/ 直接访问首页
自定义首页(非 index.html)
复制代码
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        // 将根路径映射到自定义首页
        registry.addViewController("/").setViewName("forward:/home.html");
        registry.setOrder(Ordered.HIGHEST_PRECEDENCE);
    }
}

15.2 图标(favicon.ico)定制

1. 传统方式(SpringBoot 2.x)
  • 放置位置:resources/static/favicon.ico
  • 自动生效,浏览器标签页显示自定义图标
2. SpringBoot 3.x 配置(推荐)

SpringBoot 3.x 移除了默认 favicon 自动配置,需手动配置:

yaml

复制代码
spring:
  web:
    resources:
      static-locations: classpath:/static/
  • 放置favicon.icoresources/static/目录
  • 或通过 HTML 手动引入:

html

复制代码
<link rel="icon" type="image/x-icon" href="/favicon.ico">

15.3 注意事项

  • 图标格式必须为ico,推荐尺寸 16x16、32x32
  • 清除浏览器缓存后才能看到新图标
  • 生产环境建议使用 CDN 加速静态资源,提升加载速度

16、thymeleaf 模板引擎

16.1 核心定义

Thymeleaf 是 SpringBoot 推荐的服务端模板引擎,用于渲染动态 HTML 页面,替代传统 JSP,支持自然模板(直接打开 HTML 可查看静态效果),完美适配 SpringMVC。

16.2 环境准备

  • pom.xml 依赖

xml

复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
  • 默认配置(无需修改)

yaml

复制代码
spring:
  thymeleaf:
    prefix: classpath:/templates/ # 模板文件目录
    suffix: .html # 模板文件后缀
    mode: HTML # 模板模式
    cache: true # 生产环境开启缓存,开发环境关闭

16.3 核心语法

1. 变量表达式 ${}
  • 用于获取后台传递的变量,渲染到页面

html

复制代码
<p th:text="${user.name}">用户名</p>
<p th:text="${user.age}">年龄</p>
2. 选择表达式 *{}
  • 配合th:object使用,简化变量获取

html

复制代码
<div th:object="${user}">
    <p th:text="*{name}">用户名</p>
    <p th:text="*{age}">年龄</p>
</div>
3. 链接表达式 @{}
  • 用于处理 URL,自动拼接项目上下文路径

html

复制代码
<a th:href="@{/user/list}">用户列表</a>
<img th:src="@{/static/img/logo.png}" alt="logo">
4. 条件判断

html

复制代码
<!-- if判断 -->
<p th:if="${user.age >= 18}">成年</p>
<p th:unless="${user.age >= 18}">未成年</p>

<!-- 三目运算 -->
<p th:text="${user.age >= 18} ? '成年' : '未成年'"></p>
5. 循环遍历

html

复制代码
<table>
    <tr th:each="user : ${userList}">
        <td th:text="${user.id}"></td>
        <td th:text="${user.name}"></td>
        <td th:text="${user.age}"></td>
    </tr>
</table>

16.4 代码实战

  1. Controller 传递数据

    @Controller
    @RequestMapping("/page")
    public class PageController {
    @GetMapping("/index")
    public String index(Model model) {
    // 传递单个对象
    User user = new User("张三", 20);
    model.addAttribute("user", user);

    复制代码
         // 传递集合
         List<User> userList = Arrays.asList(
                 new User("张三", 20),
                 new User("李四", 22)
         );
         model.addAttribute("userList", userList);
    
         // 返回模板名称(对应templates/index.html)
         return "index";
     }

    }

  2. 模板文件(templates/index.html)

html

复制代码
<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>首页</title>
</head>
<body>
    <h1 th:text="'欢迎你,' + ${user.name}"></h1>

    <h3>用户列表</h3>
    <table border="1">
        <tr>
            <th>ID</th>
            <th>姓名</th>
            <th>年龄</th>
        </tr>
        <tr th:each="user : ${userList}">
            <td th:text="${userStat.count}"></td>
            <td th:text="${user.name}"></td>
            <td th:text="${user.age}"></td>
        </tr>
    </table>
</body>
</html>

16.5 开发环境配置(关闭缓存)

yaml

复制代码
spring:
  thymeleaf:
    cache: false # 开发环境关闭缓存,修改页面实时生效
    prefix: classpath:/templates/

16.6 常见问题

  1. 模板 404 :检查模板是否放在templates目录,文件名与 Controller 返回值一致
  2. 页面不更新:关闭 Thymeleaf 缓存,清除浏览器缓存
  3. 静态资源无法加载 :使用@{}表达式处理 URL,确保静态资源放在static目录

17、Thymeleaf 语法(进阶篇)

17.1 核心语法回顾与补充

Thymeleaf 是 SpringBoot 推荐的服务端模板引擎,核心是通过th:前缀的自定义属性实现动态渲染,以下是高频进阶语法:

1. 常用表达式(核心)
表达式类型 语法 作用
变量表达式 ${} 获取后台 Model 传递的变量,支持对象、集合、方法调用
选择表达式 *{} 配合th:object使用,简化对象属性获取
链接表达式 @{} 处理 URL,自动拼接项目上下文路径
消息表达式 #{} 读取国际化资源文件,实现多语言
片段表达式 ~{} 引用模板片段,实现页面复用
2. 常用th:属性(高频)

html

复制代码
<!-- 文本渲染 -->
<p th:text="${user.name}">默认文本</p>
<p th:utext="${user.htmlContent}">渲染HTML内容(不转义)</p>

<!-- 条件判断 -->
<p th:if="${user.age >= 18}">成年</p>
<p th:unless="${user.age < 18}">未成年</p>
<div th:switch="${user.role}">
  <p th:case="'admin'">管理员</p>
  <p th:case="'user'">普通用户</p>
  <p th:case="*">其他角色</p>
</div>

<!-- 循环遍历 -->
<tr th:each="emp : ${empList}" th:class="${empStat.odd}? 'odd'">
  <td th:text="${empStat.count}">序号</td>
  <td th:text="${emp.id}">ID</td>
  <td th:text="${emp.name}">姓名</td>
</tr>
<!-- empStat为循环状态对象,包含count、index、even/odd等属性 -->

<!-- URL绑定 -->
<a th:href="@{/emp/list}">员工列表</a>
<a th:href="@{/emp/detail/{id}(id=${emp.id})}">详情</a>
<img th:src="@{/static/img/logo.png}" alt="logo">

<!-- 表单绑定 -->
<input type="text" th:field="*{user.name}" />
<input type="checkbox" th:field="*{user.hobbies}" th:value="'编程'" />

<!-- 片段引用 -->
<div th:replace="~{common/head :: head}"></div>
3. 内置工具对象(常用)

html

复制代码
<!-- 日期格式化 -->
<p th:text="${#temporals.format(user.createTime, 'yyyy-MM-dd HH:mm:ss')}"></p>
<!-- 字符串处理 -->
<p th:text="${#strings.toUpperCase(user.name)}"></p>
<!-- 集合处理 -->
<p th:text="${#lists.size(empList)}"></p>
<!-- 数字格式化 -->
<p th:text="${#numbers.formatDecimal(user.salary, 0, 2)}"></p>
4. 模板片段复用(页面模块化)
  1. 定义片段(resources/templates/common/head.html):

html

复制代码
<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<head th:fragment="head(title)">
    <meta charset="UTF-8">
    <title th:text="${title}">默认标题</title>
    <link rel="stylesheet" th:href="@{/static/css/bootstrap.min.css}">
</head>
</html>
  1. 引用片段:

html

复制代码
<head th:replace="~{common/head :: head('员工管理系统')}"></head>

18、MVC 配置原理

18.1 SpringBoot MVC 自动配置核心

SpringBoot 通过spring-boot-starter-web自动配置 SpringMVC,核心入口是WebMvcAutoConfiguration自动配置类,遵循 **「约定大于配置」** 原则,默认配置覆盖 80% 常用场景。

18.2 自动配置的核心组件

组件 作用
DispatcherServlet SpringMVC 核心分发器,自动注册,拦截所有请求
HandlerMapping/HandlerAdapter 处理请求映射与适配器,支持 RESTful 接口
ViewResolver 视图解析器,自动配置 Thymeleaf 等模板引擎
MessageConverter 消息转换器,默认集成 Jackson 处理 JSON
StaticResourceHandler 静态资源映射,自动映射/static等目录
LocaleResolver 国际化解析器,支持多语言切换
HandlerExceptionResolver 全局异常处理器,支持@RestControllerAdvice

18.3 自动配置生效条件

  • 类路径下存在spring-webmvc依赖
  • 用户未自定义@EnableWebMvc注解(该注解会完全禁用自动配置)
  • 遵循@ConditionalOnMissingBean规则:用户自定义 Bean 优先于自动配置

18.4 配置优先级

用户自定义配置(WebMvcConfigurer) > SpringBoot 自动配置 > SpringMVC 默认配置


19、扩展 SpringMVC

19.1 扩展原则

SpringBoot 提供WebMvcConfigurer接口,用于扩展 SpringMVC 功能,不破坏自动配置 ,禁止使用@EnableWebMvc(会完全覆盖自动配置)。

19.2 常用扩展场景与代码实现

1. 拦截器配置(登录校验、权限控制)

java

复制代码
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 注册登录拦截器
        registry.addInterceptor(new LoginInterceptor())
                .addPathPatterns("/**") // 拦截所有请求
                .excludePathPatterns(
                        "/login", "/logout", // 登录/登出
                        "/static/**", "/error" // 静态资源、错误页
                );
    }
}
2. 跨域配置(CORS)

java

复制代码
@Override
public void addCorsMappings(CorsRegistry registry) {
    registry.addMapping("/**")
            .allowedOrigins("*") // 允许所有来源
            .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
            .allowedHeaders("*")
            .maxAge(3600); // 预检请求有效期
}
3. 视图控制器(无业务逻辑的页面跳转)

java

复制代码
@Override
public void addViewControllers(ViewControllerRegistry registry) {
    // 根路径跳转到首页
    registry.addViewController("/").setViewName("index");
    registry.addViewController("/index.html").setViewName("index");
    registry.setOrder(Ordered.HIGHEST_PRECEDENCE);
}
4. 静态资源映射(自定义目录)

java

复制代码
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
    registry.addResourceHandler("/static/**")
            .addResourceLocations("classpath:/static/")
            .setCachePeriod(3600);
}
5. 消息转换器(自定义日期格式、全局序列化)

java

复制代码
@Bean
public MappingJackson2HttpMessageConverter messageConverter() {
    MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
    ObjectMapper mapper = new ObjectMapper();
    // 自定义日期格式
    mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
    // 处理null值
    mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
    converter.setObjectMapper(mapper);
    return converter;
}

20、员工管理系统:准备工作

20.1 项目需求

实现一个基础员工管理系统,包含:首页展示、员工 CRUD、登录认证、权限拦截、国际化等功能,基于 SpringBoot+Thymeleaf+Bootstrap 实现。

20.2 环境准备

  1. 依赖引入(pom.xml)

xml

复制代码
<dependencies>
    <!-- Web核心 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- Thymeleaf模板 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    <!-- 校验 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-validation</artifactId>
    </dependency>
    <!-- Lombok -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <!-- 测试 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>
  1. 项目结构

plaintext

复制代码
src/main/java/com/example/emp/
├── controller/    # 控制器
├── entity/        # 实体类
├── interceptor/   # 拦截器
├── service/       # 业务层
└── EmpApplication.java # 启动类
src/main/resources/
├── static/        # 静态资源(css、js、img)
├── templates/     # 模板页面
│   ├── common/    # 公共片段
│   ├── emp/       # 员工相关页面
│   └── index.html # 首页
├── application.yml # 主配置文件
└── i18n/          # 国际化资源文件
  1. 核心配置(application.yml)

yaml

复制代码
server:
  port: 8080
spring:
  # 国际化配置
  messages:
    basename: i18n/login
  # Thymeleaf配置
  thymeleaf:
    cache: false # 开发环境关闭缓存
    prefix: classpath:/templates/
    suffix: .html
  1. 实体类定义

java

复制代码
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Employee {
    private Integer id;
    private String lastName;
    private String email;
    private Integer gender; // 1男 0女
    private String department;
    private Date birth;
}

21、员工管理系统:首页实现

21.1 首页需求

  • 展示系统导航栏、侧边栏
  • 欢迎语展示
  • 跳转员工列表页面

21.2 页面实现(templates/index.html

html

复制代码
<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<head th:replace="~{common/head :: head('员工管理系统')}"></head>
<body>
<!-- 导航栏 -->
<nav class="navbar navbar-dark bg-dark">
    <a class="navbar-brand" href="#">员工管理系统</a>
    <div class="pull-right">
        <a class="btn btn-outline-light" th:href="@{/logout}">退出登录</a>
    </div>
</nav>

<!-- 主体内容 -->
<div class="container-fluid">
    <div class="row">
        <!-- 侧边栏 -->
        <div class="col-md-2">
            <div class="list-group">
                <a href="#" class="list-group-item active">首页</a>
                <a th:href="@{/emp/list}" class="list-group-item">员工管理</a>
                <a href="#" class="list-group-item">部门管理</a>
            </div>
        </div>
        <!-- 主内容区 -->
        <div class="col-md-10">
            <div class="jumbotron">
                <h1 class="display-4">欢迎使用员工管理系统!</h1>
                <p class="lead">本系统基于SpringBoot+Thymeleaf开发,支持员工CRUD、登录认证、国际化等功能。</p>
                <hr class="my-4">
                <a class="btn btn-primary btn-lg" th:href="@{/emp/list}" role="button">查看员工列表</a>
            </div>
        </div>
    </div>
</div>
</body>
</html>

21.3 控制器实现

java

复制代码
@Controller
public class IndexController {
    @GetMapping("/")
    public String index() {
        return "index";
    }
}

22、员工管理系统:国际化

22.1 国际化核心原理

SpringBoot 通过MessageSourceAutoConfiguration自动配置国际化,基于ResourceBundleMessageSource加载资源文件,通过LocaleResolver解析用户语言环境,实现多语言切换。

22.2 实现步骤

  1. 创建国际化资源文件resources/i18n/目录):

    • login.properties(默认中文)
    • login_en_US.properties(英文)
    • login_zh_CN.properties(中文)
  2. 资源文件内容

properties

复制代码
# login.properties(默认中文)
login.username=用户名
login.password=密码
login.btn=登录
login.tip=请登录

# login_en_US.properties(英文)
login.username=Username
login.password=Password
login.btn=Sign in
login.tip=Please sign in
  1. 配置文件指定资源路径

yaml

复制代码
spring:
  messages:
    basename: i18n/login # 资源文件前缀
    encoding: UTF-8
  1. 页面国际化(templates/login.html

html

复制代码
<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<head th:replace="~{common/head :: head(#{login.tip})}"></head>
<body>
<div class="container">
    <form th:action="@{/login}" method="post" class="form-signin">
        <h2 class="form-signin-heading" th:text="#{login.tip}"></h2>
        <input type="text" name="username" class="form-control" th:placeholder="#{login.username}" required>
        <input type="password" name="password" class="form-control" th:placeholder="#{login.password}" required>
        <button class="btn btn-lg btn-primary btn-block" type="submit" th:text="#{login.btn}"></button>
    </form>
    <!-- 语言切换 -->
    <div class="text-center mt-3">
        <a th:href="@{/login(lang='zh_CN')}">中文</a> |
        <a th:href="@{/login(lang='en_US')}">English</a>
    </div>
</div>
</body>
</html>
  1. 自定义 LocaleResolver(切换语言)

    @Component
    public class MyLocaleResolver implements LocaleResolver {
    @Override
    public Locale resolveLocale(HttpServletRequest request) {
    String lang = request.getParameter("lang");
    Locale locale = Locale.getDefault();
    if (StringUtils.hasText(lang)) {
    String[] split = lang.split("_");
    locale = new Locale(split[0], split[1]);
    }
    return locale;
    }

    复制代码
     @Override
     public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {}

    }


23、员工管理系统:登录功能实现

23.1 登录需求

  • 用户名 / 密码校验
  • 登录成功跳转首页
  • 登录失败返回错误提示
  • 记住登录状态(Session 存储)

23.2 登录页面(templates/login.html

见 22.2 节国际化页面实现。

23.3 控制器实现

复制代码
@Controller
public class LoginController {
    @GetMapping("/login")
    public String loginPage() {
        return "login";
    }

    @PostMapping("/login")
    public String login(String username, String password, HttpSession session, Model model) {
        // 模拟校验(实际项目对接数据库)
        if ("admin".equals(username) && "123456".equals(password)) {
            // 登录成功,存储用户信息
            session.setAttribute("loginUser", username);
            return "redirect:/";
        } else {
            // 登录失败,返回错误提示
            model.addAttribute("msg", "用户名或密码错误");
            return "login";
        }
    }

    @GetMapping("/logout")
    public String logout(HttpSession session) {
        // 清除Session
        session.invalidate();
        return "redirect:/login";
    }
}

23.4 登录页面错误提示优化

html

复制代码
<div class="text-center text-danger" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></div>

24、员工管理系统:登录拦截器

24.1 拦截器需求

  • 未登录用户禁止访问系统页面,强制跳转登录页
  • 放行登录、静态资源等请求

24.2 拦截器实现

java

复制代码
@Component
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 获取登录用户
        Object loginUser = request.getSession().getAttribute("loginUser");
        if (loginUser == null) {
            // 未登录,跳转登录页
            request.setAttribute("msg", "请先登录");
            request.getRequestDispatcher("/login").forward(request, response);
            return false;
        }
        // 已登录,放行
        return true;
    }
}

24.3 注册拦截器(WebConfig)

java

复制代码
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Autowired
    private LoginInterceptor loginInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/**")
                .excludePathPatterns(
                        "/login", "/logout",
                        "/static/**", "/error",
                        "/favicon.ico"
                );
    }
}

24.4 注意事项

  • 必须放行静态资源,否则页面样式、JS 无法加载
  • 登录成功后使用redirect跳转,避免表单重复提交
  • 生产环境需配合 Session 超时、CSRF 防护等安全机制

25、员工管理系统:展示员工列表

25.1 需求分析

  • 分页 / 列表展示所有员工信息
  • 支持跳转到新增、修改、删除操作
  • 页面样式优化(Bootstrap 表格)

25.2 业务层实现(模拟数据)

java

复制代码
@Service
public class EmployeeService {
    // 模拟数据库数据
    private static final List<Employee> empList = new ArrayList<>();

    static {
        empList.add(new Employee(1, "张三", "zhangsan@example.com", 1, "技术部", new Date()));
        empList.add(new Employee(2, "李四", "lisi@example.com", 1, "人事部", new Date()));
        empList.add(new Employee(3, "王五", "wangwu@example.com", 0, "财务部", new Date()));
    }

    // 查询所有员工
    public List<Employee> getAllEmployees() {
        return empList;
    }
}

25.3 控制器实现

java

复制代码
@Controller
@RequestMapping("/emp")
public class EmployeeController {
    @Autowired
    private EmployeeService employeeService;

    @GetMapping("/list")
    public String list(Model model) {
        List<Employee> employees = employeeService.getAllEmployees();
        model.addAttribute("emps", employees);
        return "emp/list";
    }
}

25.4 页面实现(templates/emp/list.html

html

复制代码
<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<head th:replace="~{common/head :: head('员工列表')}"></head>
<body>
<!-- 导航栏 -->
<nav class="navbar navbar-dark bg-dark">
    <a class="navbar-brand" th:href="@{/}">员工管理系统</a>
    <a class="btn btn-outline-light" th:href="@{/logout}">退出登录</a>
</nav>

<div class="container-fluid">
    <div class="row">
        <!-- 侧边栏 -->
        <div class="col-md-2">
            <div class="list-group">
                <a th:href="@{/}" class="list-group-item">首页</a>
                <a th:href="@{/emp/list}" class="list-group-item active">员工管理</a>
            </div>
        </div>

        <!-- 主内容区 -->
        <div class="col-md-10">
            <div class="card">
                <div class="card-header d-flex justify-content-between align-items-center">
                    <h5>员工列表</h5>
                    <a th:href="@{/emp/toAdd}" class="btn btn-primary">添加员工</a>
                </div>
                <div class="card-body">
                    <table class="table table-striped table-hover">
                        <thead>
                            <tr>
                                <th>ID</th>
                                <th>姓名</th>
                                <th>邮箱</th>
                                <th>性别</th>
                                <th>部门</th>
                                <th>操作</th>
                            </tr>
                        </thead>
                        <tbody>
                            <tr th:each="emp : ${emps}">
                                <td th:text="${emp.id}"></td>
                                <td th:text="${emp.lastName}"></td>
                                <td th:text="${emp.email}"></td>
                                <td th:text="${emp.gender == 1 ? '男' : '女'}"></td>
                                <td th:text="${emp.department}"></td>
                                <td>
                                    <a th:href="@{/emp/toUpdate(id=${emp.id})}" class="btn btn-sm btn-warning">修改</a>
                                    <a th:href="@{/emp/delete(id=${emp.id})}" class="btn btn-sm btn-danger">删除</a>
                                </td>
                            </tr>
                        </tbody>
                    </table>
                </div>
            </div>
        </div>
    </div>
</div>
</body>
</html>

26、员工管理系统:增加员工实现

26.1 需求分析

  • 新增员工表单页面
  • 表单数据校验
  • 新增成功后跳转列表页

26.2 控制器实现

java

复制代码
// 跳转到新增页面
@GetMapping("/toAdd")
public String toAdd(Model model) {
    // 传递部门列表用于下拉框
    List<String> depts = Arrays.asList("技术部", "人事部", "财务部");
    model.addAttribute("depts", depts);
    return "emp/add";
}

// 处理新增请求
@PostMapping("/add")
public String add(Employee employee) {
    // 模拟新增(实际项目对接数据库)
    employee.setId(empList.size() + 1);
    empList.add(employee);
    return "redirect:/emp/list";
}

26.3 新增页面(templates/emp/add.html

html

复制代码
<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<head th:replace="~{common/head :: head('添加员工')}"></head>
<body>
<div class="container">
    <h3>添加员工</h3>
    <form th:action="@{/emp/add}" method="post">
        <div class="form-group">
            <label>姓名</label>
            <input type="text" class="form-control" name="lastName" required>
        </div>
        <div class="form-group">
            <label>邮箱</label>
            <input type="email" class="form-control" name="email" required>
        </div>
        <div class="form-group">
            <label>性别</label>
            <div class="form-check">
                <input class="form-check-input" type="radio" name="gender" value="1" checked>
                <label class="form-check-label">男</label>
            </div>
            <div class="form-check">
                <input class="form-check-input" type="radio" name="gender" value="0">
                <label class="form-check-label">女</label>
            </div>
        </div>
        <div class="form-group">
            <label>部门</label>
            <select class="form-control" name="department">
                <option th:each="dept : ${depts}" th:text="${dept}" th:value="${dept}"></option>
            </select>
        </div>
        <div class="form-group">
            <label>生日</label>
            <input type="date" class="form-control" name="birth" required>
        </div>
        <button type="submit" class="btn btn-primary">提交</button>
        <a th:href="@{/emp/list}" class="btn btn-secondary">取消</a>
    </form>
</div>
</body>
</html>

27、员工管理系统:修改员工信息

27.1 需求分析

  • 回显员工信息到表单
  • 提交修改后更新数据
  • 跳转回列表页

27.2 控制器实现

java

复制代码
// 跳转到修改页面,回显数据
@GetMapping("/toUpdate")
public String toUpdate(Integer id, Model model) {
    // 根据ID查询员工
    Employee employee = empList.stream()
            .filter(e -> e.getId().equals(id))
            .findFirst()
            .orElse(null);
    model.addAttribute("emp", employee);
    model.addAttribute("depts", Arrays.asList("技术部", "人事部", "财务部"));
    return "emp/update";
}

// 处理修改请求
@PostMapping("/update")
public String update(Employee employee) {
    // 模拟更新(实际项目对接数据库)
    for (int i = 0; i < empList.size(); i++) {
        if (empList.get(i).getId().equals(employee.getId())) {
            empList.set(i, employee);
            break;
        }
    }
    return "redirect:/emp/list";
}

27.3 修改页面(templates/emp/update.html

html

复制代码
<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<head th:replace="~{common/head :: head('修改员工')}"></head>
<body>
<div class="container">
    <h3>修改员工</h3>
    <form th:action="@{/emp/update}" method="post">
        <!-- 隐藏ID用于更新 -->
        <input type="hidden" name="id" th:value="${emp.id}">
        
        <div class="form-group">
            <label>姓名</label>
            <input type="text" class="form-control" name="lastName" th:value="${emp.lastName}" required>
        </div>
        <div class="form-group">
            <label>邮箱</label>
            <input type="email" class="form-control" name="email" th:value="${emp.email}" required>
        </div>
        <div class="form-group">
            <label>性别</label>
            <div class="form-check">
                <input class="form-check-input" type="radio" name="gender" value="1" th:checked="${emp.gender == 1}">
                <label class="form-check-label">男</label>
            </div>
            <div class="form-check">
                <input class="form-check-input" type="radio" name="gender" value="0" th:checked="${emp.gender == 0}">
                <label class="form-check-label">女</label>
            </div>
        </div>
        <div class="form-group">
            <label>部门</label>
            <select class="form-control" name="department">
                <option th:each="dept : ${depts}" th:text="${dept}" th:value="${dept}" th:selected="${dept == emp.department}"></option>
            </select>
        </div>
        <div class="form-group">
            <label>生日</label>
            <input type="date" class="form-control" name="birth" th:value="${#temporals.format(emp.birth, 'yyyy-MM-dd')}" required>
        </div>
        <button type="submit" class="btn btn-primary">提交修改</button>
        <a th:href="@{/emp/list}" class="btn btn-secondary">取消</a>
    </form>
</div>
</body>
</html>

28、员工管理系统:删除及 404 处理

28.1 删除功能实现

控制器

java

复制代码
@GetMapping("/delete")
public String delete(Integer id) {
    // 模拟删除(实际项目对接数据库)
    empList.removeIf(e -> e.getId().equals(id));
    return "redirect:/emp/list";
}

28.2 404 页面定制

SpringBoot 默认会返回白标错误页,可自定义 404 页面提升用户体验:

  1. 创建 404 页面resources/templates/error/404.html

html

复制代码
<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<head th:replace="~{common/head :: head('页面不存在')}"></head>
<body>
<div class="container text-center mt-5">
    <h1 class="display-1">404</h1>
    <h2>页面不存在</h2>
    <p>您访问的页面不存在或已被删除</p>
    <a th:href="@{/}" class="btn btn-primary">返回首页</a>
</div>
</body>
</html>
  1. 配置生效 :SpringBoot 会自动识别error/目录下的状态码页面,无需额外配置。

28.3 全局异常处理(兜底)

java

复制代码
@RestControllerAdvice
public class GlobalExceptionHandler {
    // 处理404异常
    @ExceptionHandler(NoHandlerFoundException.class)
    public ModelAndView handle404(NoHandlerFoundException e) {
        return new ModelAndView("error/404");
    }

    // 处理500异常
    @ExceptionHandler(Exception.class)
    public ModelAndView handle500(Exception e) {
        ModelAndView mv = new ModelAndView("error/500");
        mv.addObject("msg", e.getMessage());
        return mv;
    }
}
  1. 开启 404 异常捕获(application.yml)

yaml

复制代码
spring:
  mvc:
    throw-exception-if-no-handler-found: true
  resources:
    add-mappings: false

29、聊聊该如何写一个网站

29.1 网站开发完整流程

阶段 核心任务 关键产出
需求分析 明确功能、用户群体、业务流程 需求文档、原型图
技术选型 确定前后端技术栈、数据库、中间件 技术方案文档
架构设计 分层架构(Controller/Service/Mapper)、数据库设计 ER 图、接口文档
环境搭建 项目初始化、依赖引入、基础配置 可运行的项目骨架
功能开发 按模块开发(登录、列表、CRUD 等) 可测试的功能模块
测试优化 功能测试、性能优化、安全加固 测试报告、优化方案
部署上线 服务器配置、项目打包、运维监控 上线文档、监控方案

29.2 核心设计原则

  1. 分层架构:遵循 MVC 分层,解耦业务与视图,便于维护
  2. 约定大于配置:遵循 SpringBoot 默认规范,减少冗余配置
  3. 前后端分离 / 服务端渲染:小型项目用 Thymeleaf 服务端渲染,大型项目用前后端分离(Vue/React)
  4. 安全优先:登录拦截、权限控制、SQL 注入防护、XSS 防护
  5. 可扩展性:预留扩展接口,便于后续功能迭代

29.3 避坑指南

  • 禁止在 Controller 中写业务逻辑,业务逻辑统一放在 Service 层
  • 避免硬编码,配置统一放在配置文件中
  • 统一异常处理,避免暴露敏感错误信息
  • 静态资源统一管理,使用 CDN 加速

30、回顾及这周安排

30.1 核心知识点回顾

  1. Thymeleaf:模板引擎核心语法、页面复用、国际化
  2. SpringMVC:自动配置原理、扩展(拦截器、跨域、视图控制器)
  3. 员工管理系统:登录、拦截器、CRUD、异常处理全流程
  4. 网站开发方法论:完整开发流程、设计原则

30.2 周学习安排建议

时间 学习任务 目标
第 1-2 天 回顾 SpringBoot 基础、Thymeleaf 语法 熟练掌握模板渲染、页面开发
第 3-4 天 完成员工管理系统全功能(CRUD、登录、拦截器) 跑通完整项目,理解业务流程
第 5 天 学习数据层集成(JDBC、Druid) 掌握 SpringBoot 数据库操作
第 6-7 天 项目优化、部署上线、拓展学习 优化项目,学习 MyBatis 等进阶技术

31、整合 JDBC 使用

31.1 核心原理

SpringBoot 通过spring-boot-starter-jdbc自动配置JdbcTemplate,简化 JDBC 操作,无需手动创建连接、处理资源释放。

31.2 环境准备

  1. 依赖引入(pom.xml)

xml

复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>
  1. 数据库配置(application.yml)

yaml

复制代码
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/emp_db?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver

31.3 JdbcTemplate 核心用法

java

复制代码
@RestController
@RequestMapping("/jdbc")
public class JdbcController {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    // 查询所有员工
    @GetMapping("/list")
    public List<Map<String, Object>> list() {
        String sql = "select * from employee";
        return jdbcTemplate.queryForList(sql);
    }

    // 新增员工
    @PostMapping("/add")
    public String add(Employee employee) {
        String sql = "insert into employee(last_name, email, gender, department, birth) values(?,?,?,?,?)";
        int rows = jdbcTemplate.update(sql,
                employee.getLastName(),
                employee.getEmail(),
                employee.getGender(),
                employee.getDepartment(),
                employee.getBirth());
        return "新增成功,影响行数:" + rows;
    }

    // 修改员工
    @PutMapping("/update")
    public String update(Employee employee) {
        String sql = "update employee set last_name=?, email=?, gender=?, department=?, birth=? where id=?";
        int rows = jdbcTemplate.update(sql,
                employee.getLastName(),
                employee.getEmail(),
                employee.getGender(),
                employee.getDepartment(),
                employee.getBirth(),
                employee.getId());
        return "修改成功,影响行数:" + rows;
    }

    // 删除员工
    @DeleteMapping("/delete")
    public String delete(Integer id) {
        String sql = "delete from employee where id=?";
        int rows = jdbcTemplate.update(sql, id);
        return "删除成功,影响行数:" + rows;
    }
}

31.4 核心优势

  • 自动管理数据库连接,无需手动关闭
  • 简化 SQL 执行,支持参数绑定
  • 内置事务管理,可通过@Transactional开启事务

32、整合 Druid 数据源

32.1 Druid 核心优势

Druid 是阿里开源的高性能数据库连接池,相比默认 HikariCP,提供监控、防 SQL 注入、连接池管理等企业级特性,适合生产环境使用。

32.2 环境准备

  1. 依赖引入(pom.xml)

xml

复制代码
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.2.16</version>
</dependency>
  1. Druid 配置(application.yml)

yaml

复制代码
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/emp_db?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
    # Druid连接池配置
    druid:
      # 初始化大小、最小、最大
      initial-size: 5
      min-idle: 5
      max-active: 20
      # 连接超时时间
      max-wait: 60000
      # 间隔多久进行一次检测,检测需要关闭的空闲连接
      time-between-eviction-runs-millis: 60000
      # 一个连接在池中最小生存的时间
      min-evictable-idle-time-millis: 300000
      # 监控配置
      stat-view-servlet:
        enabled: true
        login-username: admin
        login-password: 123456
        allow: 127.0.0.1
      filter:
        stat:
          enabled: true
          log-slow-sql: true
          slow-sql-millis: 1000
        wall:
          enabled: true # 防SQL注入

32.3 Druid 监控面板

  • 访问地址:http://localhost:8080/druid
  • 输入配置的用户名 / 密码,即可查看:
    • 数据源连接池状态
    • SQL 执行监控(慢 SQL、执行次数)
    • URI 监控、Spring 监控
    • SQL 防火墙拦截记录

32.4 核心特性

  1. 高性能连接池:比 HikariCP 更适合高并发场景
  2. SQL 监控:实时监控 SQL 执行性能,定位慢 SQL
  3. SQL 防火墙:防 SQL 注入、防暴力破解
  4. 日志记录:完整记录 SQL 执行日志,便于排查问题

33、整合 MyBatis 框架

33.1 核心定位

MyBatis 是一款优秀的持久层框架 ,SpringBoot 通过mybatis-spring-boot-starter实现零配置快速整合,替代原生 JDBC,简化数据库操作,支持自定义 SQL、存储过程和高级映射。

33.2 环境准备

  1. 依赖引入(pom.xml)

xml

复制代码
<!-- MyBatis SpringBoot Starter -->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.3.1</version>
</dependency>
<!-- MySQL驱动 -->
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <scope>runtime</scope>
</dependency>
<!-- Druid数据源(可选,生产环境推荐) -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.2.20</version>
</dependency>
  1. 核心配置(application.yml)

yaml

复制代码
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/emp_db?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
    # Druid连接池配置(可选)
    druid:
      initial-size: 5
      min-idle: 5
      max-active: 20

# MyBatis配置
mybatis:
  mapper-locations: classpath:mapper/*.xml  # Mapper XML文件路径
  type-aliases-package: com.example.emp.entity  # 实体类包别名
  configuration:
    map-underscore-to-camel-case: true  # 开启驼峰命名自动映射
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl  # 打印SQL日志(开发环境)

33.3 项目结构与代码实现

复制代码
src/main/java/com/example/emp/
├── entity/          # 实体类
├── mapper/          # Mapper接口
└── service/         # 业务层
src/main/resources/
├── mapper/          # Mapper XML文件
└── application.yml  # 配置文件
  1. 实体类(Employee.java)

java

复制代码
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Employee {
    private Integer id;
    private String lastName;
    private String email;
    private Integer gender;
    private String department;
    private Date birth;
}
  1. Mapper 接口(EmployeeMapper.java)

java

复制代码
@Mapper // 标识MyBatis Mapper接口,Spring会自动扫描并生成代理对象
public interface EmployeeMapper {
    // 查询所有员工
    List<Employee> selectAll();

    // 根据ID查询员工
    Employee selectById(Integer id);

    // 新增员工
    int insert(Employee employee);

    // 更新员工
    int update(Employee employee);

    // 删除员工
    int deleteById(Integer id);
}
  1. Mapper XML(resources/mapper/EmployeeMapper.xml)

xml

复制代码
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.emp.mapper.EmployeeMapper">
    <!-- 查询所有 -->
    <select id="selectAll" resultType="Employee">
        select * from employee
    </select>

    <!-- 根据ID查询 -->
    <select id="selectById" parameterType="int" resultType="Employee">
        select * from employee where id = #{id}
    </select>

    <!-- 新增 -->
    <insert id="insert" parameterType="Employee">
        insert into employee(last_name, email, gender, department, birth)
        values(#{lastName}, #{email}, #{gender}, #{department}, #{birth})
    </insert>

    <!-- 更新 -->
    <update id="update" parameterType="Employee">
        update employee set last_name=#{lastName}, email=#{email}, gender=#{gender},
        department=#{department}, birth=#{birth} where id=#{id}
    </update>

    <!-- 删除 -->
    <delete id="deleteById" parameterType="int">
        delete from employee where id=#{id}
    </delete>
</mapper>
  1. 业务层(EmployeeService.java)

java

复制代码
@Service
public class EmployeeService {
    @Autowired
    private EmployeeMapper employeeMapper;

    public List<Employee> getAllEmployees() {
        return employeeMapper.selectAll();
    }

    public Employee getEmployeeById(Integer id) {
        return employeeMapper.selectById(id);
    }

    public int addEmployee(Employee employee) {
        return employeeMapper.insert(employee);
    }

    public int updateEmployee(Employee employee) {
        return employeeMapper.update(employee);
    }

    public int deleteEmployee(Integer id) {
        return employeeMapper.deleteById(id);
    }
}

33.4 核心注解说明

注解 作用
@Mapper 标识 Mapper 接口,Spring 自动扫描生成代理 Bean
@MapperScan 批量扫描 Mapper 接口,替代每个接口加@Mapper(启动类添加)
@Param 多参数查询时,给参数命名,对应 XML 中的#{}
@Select/@Insert/@Update/@Delete 注解方式写 SQL,替代 XML 文件

34、SpringSecurity 环境搭建

34.1 核心定位

SpringSecurity 是 Spring 官方提供的安全框架,用于实现认证(登录)、授权(权限控制)、防攻击(CSRF、XSS)等安全功能,是 Spring 生态的标准安全解决方案。

34.2 环境准备

  1. 依赖引入(pom.xml)

xml

复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
  1. 默认效果引入依赖后,SpringSecurity 会自动拦截所有请求,跳转到默认登录页:
  • 默认用户名:user
  • 默认密码:控制台随机生成(如Using generated security password: xxx

34.3 基础配置类

java

复制代码
@Configuration
@EnableWebSecurity // 开启SpringSecurity安全配置
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            // 关闭CSRF(开发环境,生产环境建议开启)
            .csrf(csrf -> csrf.disable())
            // 配置请求授权规则
            .authorizeHttpRequests(auth -> auth
                .anyRequest().authenticated() // 所有请求需要认证
            )
            // 配置登录表单
            .formLogin(form -> form
                .loginPage("/login") // 自定义登录页
                .defaultSuccessUrl("/") // 登录成功跳转首页
                .permitAll() // 登录页所有人可访问
            )
            // 配置登出
            .logout(logout -> logout
                .logoutUrl("/logout")
                .logoutSuccessUrl("/login")
                .permitAll()
            );
        return http.build();
    }

    // 配置用户信息(内存方式,测试用)
    @Bean
    public UserDetailsService userDetailsService() {
        UserDetails user = User.withUsername("admin")
                .password(PasswordEncoderFactories.createDelegatingPasswordEncoder().encode("123456"))
                .roles("ADMIN")
                .build();
        return new InMemoryUserDetailsManager(user);
    }

    // 密码编码器
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

35、用户认证和授权

35.1 核心概念

  • 认证(Authentication):验证用户身份(登录),确认「你是谁」
  • 授权(Authorization):验证用户权限,确认「你能做什么」

35.2 数据库用户认证(实战)

  1. 用户实体类(User.java)

java

复制代码
@Data
public class User implements UserDetails {
    private Integer id;
    private String username;
    private String password;
    private String role; // 权限角色

    // 实现UserDetails接口方法
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return AuthorityUtils.commaSeparatedStringToAuthorityList(role);
    }

    @Override
    public String getPassword() { return password; }

    @Override
    public String getUsername() { return username; }

    @Override
    public boolean isAccountNonExpired() { return true; }

    @Override
    public boolean isAccountNonLocked() { return true; }

    @Override
    public boolean isCredentialsNonExpired() { return true; }

    @Override
    public boolean isEnabled() { return true; }
}
  1. 自定义 UserDetailsService

java

复制代码
@Service
public class MyUserDetailsService implements UserDetailsService {
    @Autowired
    private UserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 从数据库查询用户
        User user = userMapper.selectByUsername(username);
        if (user == null) {
            throw new UsernameNotFoundException("用户名不存在");
        }
        return user;
    }
}
  1. 授权规则配置

java

复制代码
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http
        .csrf(csrf -> csrf.disable())
        .authorizeHttpRequests(auth -> auth
            // 权限控制:不同角色访问不同资源
            .requestMatchers("/admin/**").hasRole("ADMIN")
            .requestMatchers("/user/**").hasAnyRole("USER", "ADMIN")
            .requestMatchers("/static/**").permitAll() // 静态资源放行
            .anyRequest().authenticated()
        )
        .formLogin(form -> form
            .loginPage("/login")
            .defaultSuccessUrl("/")
            .permitAll()
        )
        .logout(logout -> logout.permitAll());
    return http.build();
}

35.3 常用权限注解

java

复制代码
// 方法级权限控制
@RestController
@RequestMapping("/admin")
public class AdminController {
    // 只有ADMIN角色可访问
    @PreAuthorize("hasRole('ADMIN')")
    @GetMapping("/list")
    public String adminList() {
        return "管理员列表";
    }
}

需在配置类添加@EnableMethodSecurity开启方法级权限控制


36、注销及权限控制

36.1 注销功能完善

java

复制代码
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http
        .logout(logout -> logout
            .logoutUrl("/logout") // 注销请求路径
            .logoutSuccessUrl("/login?logout") // 注销成功跳转页
            .invalidateHttpSession(true) // 清除Session
            .clearAuthentication(true) // 清除认证信息
            .addLogoutHandler(new HeaderLogoutHandler()) // 自定义注销处理器
            .permitAll()
        );
    return http.build();
}

36.2 权限控制进阶

1. 权限不足处理(403 页面定制)

java

复制代码
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .exceptionHandling(ex -> ex
                .accessDeniedPage("/403") // 权限不足跳转403页
            );
    }
}
2. 动态权限控制(基于数据库权限表)
  • 设计权限表:sys_permission(权限 ID、权限名称、请求路径、角色)
  • 自定义权限拦截器,从数据库加载权限规则,动态校验用户权限

36.3 权限控制最佳实践

  • 遵循最小权限原则:给用户分配完成工作所需的最小权限
  • 分层控制:请求级(SecurityFilterChain)+ 方法级(@PreAuthorize)+ 数据级(行级权限)
  • 敏感操作二次验证:如删除、修改操作需二次密码验证

37、记住我及首页定制

37.1 记住我(Remember-Me)功能

实现用户登录后,关闭浏览器再次访问无需重新登录,基于持久化 Token 实现。

  1. 配置类开启记住我

java

复制代码
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http, DataSource dataSource) throws Exception {
    http
        .rememberMe(remember -> remember
            .key("uniqueAndSecretKey") // 加密密钥(自定义)
            .tokenRepository(new JdbcTokenRepositoryImpl(dataSource)) // 持久化到数据库
            .tokenValiditySeconds(60 * 60 * 24 * 7) // 有效期7天
            .userDetailsService(userDetailsService)
        );
    return http.build();
}
  1. 登录页添加记住我复选框

html

复制代码
<div class="form-check">
    <input class="form-check-input" type="checkbox" name="remember-me" id="remember">
    <label class="form-check-label" for="remember">记住我</label>
</div>

37.2 首页定制

根据用户角色动态展示首页内容,实现不同角色看到不同页面:

java

复制代码
@Controller
public class IndexController {
    @GetMapping("/")
    public String index(Model model, Authentication authentication) {
        // 获取当前登录用户
        User user = (User) authentication.getPrincipal();
        model.addAttribute("user", user);
        // 根据角色跳转不同首页
        if (user.getRole().contains("ADMIN")) {
            return "admin/index";
        } else {
            return "user/index";
        }
    }
}

38、Shiro 快速开始

38.1 核心定位

Apache Shiro 是一款轻量级安全框架,功能与 SpringSecurity 类似,核心优势是简单易用、不依赖 Spring 容器,可独立使用,适合中小型项目。

38.2 核心组件

组件 作用
Subject 当前用户,所有操作的入口
SecurityManager Shiro 核心,管理所有 Subject
Authenticator 认证器,处理登录
Authorizer 授权器,处理权限
Realm 自定义数据源,连接数据库

38.3 快速入门(独立使用)

java

复制代码
public class ShiroQuickStart {
    public static void main(String[] args) {
        // 1. 创建SecurityManager
        DefaultSecurityManager securityManager = new DefaultSecurityManager();

        // 2. 创建Realm(内存用户)
        IniRealm realm = new IniRealm("classpath:shiro.ini");
        securityManager.setRealm(realm);

        // 3. 绑定SecurityManager
        SecurityUtils.setSecurityManager(securityManager);

        // 4. 获取Subject(当前用户)
        Subject subject = SecurityUtils.getSubject();

        // 5. 登录认证
        UsernamePasswordToken token = new UsernamePasswordToken("admin", "123456");
        try {
            subject.login(token);
            System.out.println("登录成功");

            // 6. 权限校验
            if (subject.hasRole("admin")) {
                System.out.println("拥有admin角色");
            }
            if (subject.isPermitted("user:add")) {
                System.out.println("拥有添加用户权限");
            }
        } catch (AuthenticationException e) {
            System.out.println("登录失败:" + e.getMessage());
        }

        // 7. 注销
        subject.logout();
    }
}
  1. shiro.ini 配置文件

ini

复制代码
[users]
admin = 123456, admin
[roles]
admin = user:add, user:delete

39、Shiro 的 Subject 分析

39.1 Subject 核心本质

Subject 是 Shiro 的核心入口,代表当前用户,封装了用户的认证、授权、会话等操作,所有安全操作都通过 Subject 完成。

39.2 Subject 核心方法

方法 作用
login(Token) 登录认证
logout() 注销
isAuthenticated() 判断是否已认证
hasRole(String role) 判断是否拥有角色
isPermitted(String permission) 判断是否拥有权限
getSession() 获取用户会话
getPrincipal() 获取用户身份信息

39.3 Subject 执行流程

  1. 用户发起请求,Subject 调用login()方法
  2. Subject 将请求委托给 SecurityManager
  3. SecurityManager 调用 Authenticator 进行认证
  4. Authenticator 通过 Realm 从数据库获取用户信息,校验密码
  5. 认证成功后,Subject 保存用户认证状态,后续权限校验直接从 Subject 获取

39.4 Subject 与线程绑定

Shiro 通过ThreadContext将 Subject 绑定到当前线程,可在任意位置通过SecurityUtils.getSubject()获取当前用户,无需传递参数。


40、SpringBoot 整合 Shiro 环境搭建

40.1 环境准备

  1. 依赖引入(pom.xml)

xml

复制代码
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring-boot-starter</artifactId>
    <version>1.13.0</version>
</dependency>

40.2 核心配置类

java

复制代码
@Configuration
public class ShiroConfig {

    // 1. 创建Realm(自定义数据源)
    @Bean
    public UserRealm userRealm() {
        return new UserRealm();
    }

    // 2. 创建SecurityManager
    @Bean
    public SecurityManager securityManager(UserRealm userRealm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(userRealm);
        // 开启RememberMe
        securityManager.setRememberMeManager(new CookieRememberMeManager());
        return securityManager;
    }

    // 3. 创建ShiroFilterFactoryBean(拦截请求)
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
        shiroFilter.setSecurityManager(securityManager);

        // 配置登录页
        shiroFilter.setLoginUrl("/login");
        // 登录成功跳转页
        shiroFilter.setSuccessUrl("/");
        // 权限不足跳转页
        shiroFilter.setUnauthorizedUrl("/403");

        // 配置拦截规则
        Map<String, String> filterMap = new LinkedHashMap<>();
        // 静态资源放行
        filterMap.put("/static/**", "anon");
        // 登录页放行
        filterMap.put("/login", "anon");
        // 注册页放行
        filterMap.put("/register", "anon");
        // 管理员路径需要admin角色
        filterMap.put("/admin/**", "roles[admin]");
        // 所有请求需要认证
        filterMap.put("/**", "authc");

        shiroFilter.setFilterChainDefinitionMap(filterMap);
        return shiroFilter;
    }

    // 4. 开启Shiro注解(@RequiresRoles、@RequiresPermissions)
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }
}

40.3 自定义 Realm

java

复制代码
public class UserRealm extends AuthorizingRealm {
    @Autowired
    private UserMapper userMapper;

    // 授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        // 获取当前用户
        User user = (User) principals.getPrimaryPrincipal();
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        // 添加角色
        info.addRole(user.getRole());
        // 添加权限(从数据库查询)
        info.addStringPermission("user:add");
        return info;
    }

    // 认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        // 获取用户名
        String username = token.getPrincipal().toString();
        // 从数据库查询用户
        User user = userMapper.selectByUsername(username);
        if (user == null) {
            throw new UnknownAccountException("用户名不存在");
        }
        // 校验密码(Shiro自动加密比对)
        return new SimpleAuthenticationInfo(user, user.getPassword(), getName());
    }
}

40.4 Shiro 常用过滤器

过滤器 作用
anon 匿名访问,无需认证
authc 需要认证才能访问
user 记住我或认证通过可访问
roles[admin] 需要 admin 角色
perms[user:add] 需要 user:add 权限
logout 注销过滤器

41、Shiro 实现登录拦截

41.1 核心原理

Shiro 通过过滤器链(FilterChain) 实现请求拦截,基于配置的拦截规则,对未登录用户强制跳转登录页,实现登录拦截功能。

41.2 核心配置(ShiroConfig.java)

java

复制代码
@Configuration
public class ShiroConfig {

    /**
     * Shiro过滤器工厂:核心拦截入口
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
        shiroFilter.setSecurityManager(securityManager);

        // 1. 配置登录页:未登录用户自动跳转到此路径
        shiroFilter.setLoginUrl("/login");
        // 2. 登录成功默认跳转页
        shiroFilter.setSuccessUrl("/");
        // 3. 权限不足跳转页
        shiroFilter.setUnauthorizedUrl("/403");

        // 4. 配置拦截规则(LinkedHashMap保证顺序,从上到下匹配)
        Map<String, String> filterMap = new LinkedHashMap<>();
        // 放行静态资源(anon=匿名访问,无需登录)
        filterMap.put("/static/**", "anon");
        // 放行登录、注册接口
        filterMap.put("/login", "anon");
        filterMap.put("/register", "anon");
        // 放行Swagger接口文档(开发环境)
        filterMap.put("/swagger-ui/**", "anon");
        filterMap.put("/v3/api-docs/**", "anon");
        // 放行错误页
        filterMap.put("/error", "anon");
        filterMap.put("/favicon.ico", "anon");

        // 拦截所有其他请求(authc=需要登录认证)
        filterMap.put("/**", "authc");

        shiroFilter.setFilterChainDefinitionMap(filterMap);
        return shiroFilter;
    }

    // 其他Bean(SecurityManager、Realm等,见第40章)
}

41.3 常用过滤器说明

过滤器 作用
anon 匿名访问,无需登录
authc 需要登录认证才能访问
user 记住我或登录认证均可访问
logout 注销过滤器,访问后自动登出
roles[admin] 需要拥有 admin 角色
perms[user:add] 需要拥有 user:add 权限

41.4 注意事项

  • 拦截规则严格按顺序匹配,放行规则必须放在拦截规则之前
  • 静态资源必须放行,否则页面样式、JS 无法加载
  • 生产环境需关闭 Swagger 等开发接口的放行

42、Shiro 实现用户认证

42.1 核心原理

用户认证是验证用户身份的过程,Shiro 通过Realm连接数据库,校验用户名 / 密码,认证成功后将用户信息存入 Subject,后续请求可直接获取用户状态。

42.2 自定义 Realm(核心)

java

复制代码
public class UserRealm extends AuthorizingRealm {

    @Autowired
    private UserMapper userMapper;

    /**
     * 认证方法:处理登录请求
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        // 1. 从token中获取用户名(前端传入)
        String username = (String) token.getPrincipal();
        // 2. 从数据库查询用户信息
        User user = userMapper.selectByUsername(username);
        if (user == null) {
            // 用户名不存在,抛出异常
            throw new UnknownAccountException("用户名不存在");
        }
        // 3. 封装认证信息(Shiro自动比对密码,无需手动校验)
        // 参数1:用户信息(存入Subject),参数2:数据库密码,参数3:Realm名称
        return new SimpleAuthenticationInfo(
                user,
                user.getPassword(),
                ByteSource.Util.bytes(user.getSalt()), // 加盐(密码加密用)
                getName()
        );
    }

    /**
     * 授权方法:处理权限校验(见第44章)
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        return null;
    }

    /**
     * 配置密码加密器(BCrypt加密)
     */
    @Override
    public void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) {
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
        matcher.setHashAlgorithmName("SHA-256"); // 加密算法
        matcher.setHashIterations(1024); // 加密次数
        matcher.setStoredCredentialsHexEncoded(true);
        super.setCredentialsMatcher(matcher);
    }
}

42.3 登录 Controller 实现

java

复制代码
@Controller
public class LoginController {

    @GetMapping("/login")
    public String loginPage() {
        return "login";
    }

    @PostMapping("/login")
    @ResponseBody
    public String login(String username, String password) {
        // 1. 获取Subject(当前用户)
        Subject subject = SecurityUtils.getSubject();
        // 2. 封装登录令牌
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        // 3. 执行登录(Shiro自动调用Realm的认证方法)
        try {
            subject.login(token);
            return "登录成功";
        } catch (UnknownAccountException e) {
            return "用户名不存在";
        } catch (IncorrectCredentialsException e) {
            return "密码错误";
        } catch (AuthenticationException e) {
            return "登录失败:" + e.getMessage();
        }
    }

    @GetMapping("/logout")
    public String logout() {
        SecurityUtils.getSubject().logout();
        return "redirect:/login";
    }
}

42.4 认证流程总结

  1. 前端提交用户名 / 密码 → Controller 封装UsernamePasswordToken
  2. 调用subject.login(token) → Shiro 委托给 SecurityManager
  3. SecurityManager 调用 Realm 的doGetAuthenticationInfo方法
  4. Realm 从数据库查询用户,校验密码 → 认证成功,用户信息存入 Subject
  5. 后续请求通过SecurityUtils.getSubject()获取用户状态

43、Shiro 整合 MyBatis

43.1 核心目标

将 Shiro 的用户认证、授权与 MyBatis 结合,从数据库动态加载用户、角色、权限数据,替代硬编码的内存用户,实现企业级动态权限控制。

43.2 数据库表设计

sql

复制代码
-- 用户表
CREATE TABLE `sys_user` (
  `id` int NOT NULL AUTO_INCREMENT,
  `username` varchar(50) NOT NULL COMMENT '用户名',
  `password` varchar(100) NOT NULL COMMENT '密码(加密后)',
  `salt` varchar(50) NOT NULL COMMENT '盐值',
  `status` tinyint NOT NULL DEFAULT '1' COMMENT '状态:0禁用 1启用',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 角色表
CREATE TABLE `sys_role` (
  `id` int NOT NULL AUTO_INCREMENT,
  `role_name` varchar(50) NOT NULL COMMENT '角色名称',
  `role_desc` varchar(100) DEFAULT NULL COMMENT '角色描述',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 权限表
CREATE TABLE `sys_permission` (
  `id` int NOT NULL AUTO_INCREMENT,
  `perm_name` varchar(50) NOT NULL COMMENT '权限名称',
  `perm_code` varchar(100) NOT NULL COMMENT '权限标识(如user:add)',
  `url` varchar(200) DEFAULT NULL COMMENT '请求路径',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 用户角色关联表
CREATE TABLE `sys_user_role` (
  `user_id` int NOT NULL,
  `role_id` int NOT NULL,
  PRIMARY KEY (`user_id`,`role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 角色权限关联表
CREATE TABLE `sys_role_perm` (
  `role_id` int NOT NULL,
  `perm_id` int NOT NULL,
  PRIMARY KEY (`role_id`,`perm_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

43.3 MyBatis Mapper 实现

  1. UserMapper.java

java

复制代码
@Mapper
public interface UserMapper {
    // 根据用户名查询用户
    User selectByUsername(String username);
    // 根据用户ID查询角色列表
    List<String> selectRolesByUserId(Integer userId);
    // 根据用户ID查询权限列表
    List<String> selectPermissionsByUserId(Integer userId);
}
  1. UserMapper.xml

xml

复制代码
<mapper namespace="com.example.emp.mapper.UserMapper">
    <!-- 根据用户名查询用户 -->
    <select id="selectByUsername" parameterType="string" resultType="User">
        select * from sys_user where username = #{username}
    </select>

    <!-- 根据用户ID查询角色 -->
    <select id="selectRolesByUserId" parameterType="int" resultType="string">
        select r.role_name from sys_role r
        join sys_user_role ur on r.id = ur.role_id
        where ur.user_id = #{userId}
    </select>

    <!-- 根据用户ID查询权限 -->
    <select id="selectPermissionsByUserId" parameterType="int" resultType="string">
        select p.perm_code from sys_permission p
        join sys_role_perm rp on p.id = rp.perm_id
        join sys_user_role ur on rp.role_id = ur.role_id
        where ur.user_id = #{userId}
    </select>
</mapper>

43.4 改造 Realm(动态加载权限)

java

复制代码
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) {
    String username = (String) token.getPrincipal();
    User user = userMapper.selectByUsername(username);
    if (user == null) throw new UnknownAccountException("用户名不存在");
    if (user.getStatus() == 0) throw new LockedAccountException("账号已被禁用");
    return new SimpleAuthenticationInfo(user, user.getPassword(), ByteSource.Util.bytes(user.getSalt()), getName());
}

@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
    // 获取当前登录用户
    User user = (User) principals.getPrimaryPrincipal();
    // 从数据库查询用户角色和权限
    List<String> roles = userMapper.selectRolesByUserId(user.getId());
    List<String> perms = userMapper.selectPermissionsByUserId(user.getId());

    // 封装授权信息
    SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
    info.setRoles(new HashSet<>(roles));
    info.setStringPermissions(new HashSet<>(perms));
    return info;
}

44、Shiro 请求授权实现

44.1 核心原理

授权是验证用户是否拥有访问资源权限的过程,Shiro 支持URL 拦截授权方法注解授权两种方式,实现细粒度的权限控制。

44.2 方式 1:URL 拦截授权(ShiroConfig)

java

复制代码
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
    ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
    shiroFilter.setSecurityManager(securityManager);

    Map<String, String> filterMap = new LinkedHashMap<>();
    // 放行静态资源、登录页
    filterMap.put("/static/**", "anon");
    filterMap.put("/login", "anon");

    // 角色授权:/admin/** 必须拥有admin角色
    filterMap.put("/admin/**", "roles[admin]");
    // 权限授权:/emp/add 必须拥有user:add权限
    filterMap.put("/emp/add", "perms[user:add]");
    filterMap.put("/emp/delete", "perms[user:delete]");

    // 所有请求需要登录
    filterMap.put("/**", "authc");

    shiroFilter.setFilterChainDefinitionMap(filterMap);
    return shiroFilterFactoryBean;
}

44.3 方式 2:方法注解授权

  1. 开启 Shiro 注解(ShiroConfig)

java

复制代码
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
    AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
    advisor.setSecurityManager(securityManager);
    return advisor;
}

@Bean
@ConditionalOnMissingBean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
    DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
    creator.setProxyTargetClass(true);
    return creator;
}
  1. Controller 方法加注解

java

复制代码
@RestController
@RequestMapping("/emp")
public class EmployeeController {

    // 必须拥有user:add权限才能访问
    @RequiresPermissions("user:add")
    @PostMapping("/add")
    public String addEmployee(@RequestBody Employee employee) {
        return "添加成功";
    }

    // 必须拥有admin角色才能访问
    @RequiresRoles("admin")
    @DeleteMapping("/delete/{id}")
    public String deleteEmployee(@PathVariable Integer id) {
        return "删除成功";
    }

    // 登录即可访问
    @RequiresUser
    @GetMapping("/list")
    public List<Employee> list() {
        return employeeService.getAllEmployees();
    }
}

44.4 方式 3:代码中动态校验

java

复制代码
@GetMapping("/detail/{id}")
public String detail(@PathVariable Integer id) {
    Subject subject = SecurityUtils.getSubject();
    // 动态校验权限
    if (!subject.isPermitted("user:detail")) {
        return "无权限访问";
    }
    return employeeService.getById(id);
}

44.5 权限不足处理

java

复制代码
@ControllerAdvice
public class ShiroExceptionHandler {

    // 处理权限不足异常
    @ExceptionHandler(UnauthorizedException.class)
    public ModelAndView handleUnauthorized(UnauthorizedException e) {
        ModelAndView mv = new ModelAndView("error/403");
        mv.addObject("msg", "您无权限访问此资源");
        return mv;
    }

    // 处理角色不足异常
    @ExceptionHandler(AuthorizationException.class)
    public ModelAndView handleAuthorization(AuthorizationException e) {
        ModelAndView mv = new ModelAndView("error/403");
        mv.addObject("msg", "您的角色无权限访问此资源");
        return mv;
    }
}

45、Shiro 整合 Thymeleaf

45.1 核心目标

在 Thymeleaf 页面中,通过 Shiro 标签实现权限控制的页面元素渲染,如根据用户角色显示 / 隐藏按钮、菜单,实现前端权限控制。

45.2 环境准备

  1. 依赖引入(pom.xml)

xml

复制代码
<dependency>
    <groupId>com.github.theborakompanioni</groupId>
    <artifactId>thymeleaf-extras-shiro</artifactId>
    <version>2.1.0</version>
</dependency>
  1. 配置方言(Thymeleaf 配置)

java

复制代码
@Configuration
public class ThymeleafConfig {
    @Bean
    public ShiroDialect shiroDialect() {
        return new ShiroDialect();
    }
}

45.3 页面标签使用

  1. 页面引入命名空间

html

复制代码
<html xmlns:th="http://www.thymeleaf.org"
      xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
  1. 常用标签示例

html

复制代码
<!-- 已登录用户显示 -->
<div shiro:authenticated>
    <p>欢迎您,<span shiro:principal></span></p>
    <a th:href="@{/logout}">退出登录</a>
</div>

<!-- 未登录用户显示 -->
<div shiro:notAuthenticated>
    <a th:href="@{/login}">请登录</a>
</div>

<!-- 拥有admin角色显示 -->
<a shiro:hasRole="admin" th:href="@{/admin/list}">管理员后台</a>

<!-- 拥有user:add权限显示 -->
<button shiro:hasPermission="user:add" class="btn btn-primary">添加员工</button>

<!-- 没有权限时显示 -->
<button shiro:lacksPermission="user:add" class="btn btn-secondary" disabled>添加员工</button>

<!-- 任意角色满足显示 -->
<div shiro:hasAnyRoles="admin,manager">
    <p>您是管理员/经理</p>
</div>

<!-- 获取当前用户名 -->
<p>当前用户:<span shiro:principal></span></p>

45.4 注意事项

  • 标签仅控制页面元素的显示 / 隐藏,后端必须同时做权限校验(防止绕过前端直接访问接口)
  • 生产环境需确保 Shiro 过滤器和注解双重校验,保障安全

46、鸡汤分析开源项目

46.1 核心目标

通过分析开源项目,学习优秀项目的架构设计、代码规范、技术选型,提升自身开发能力,快速落地企业级项目。

46.2 开源项目分析维度

维度 分析重点 学习价值
项目架构 分层结构(Controller/Service/Mapper)、模块划分、技术栈选型 学习企业级项目标准架构
代码规范 命名规范、注释规范、异常处理、日志规范 提升代码可读性、可维护性
安全设计 认证授权、防攻击(CSRF/XSS)、数据加密 学习企业级安全最佳实践
性能优化 数据库优化、缓存设计、异步处理 提升项目性能、并发能力
文档与测试 接口文档、单元测试、部署文档 学习项目工程化管理

46.3 推荐学习的开源项目

  1. SpringBoot-Learning:SpringBoot 入门实战项目,覆盖基础功能
  2. RuoYi-Vue:基于 SpringBoot+Vue 的后台管理系统,集成权限管理、代码生成
  3. MyBatis-Plus:MyBatis 增强工具,学习持久层优化
  4. Spring-Cloud-Alibaba:微服务开源项目,学习微服务架构

46.4 学习方法

  1. 跑通项目:本地部署运行,熟悉项目功能
  2. 阅读源码:从入口类(启动类)开始,梳理核心流程
  3. 模仿开发:基于开源项目改造,实现自己的业务功能
  4. 总结沉淀:整理项目架构、技术栈、最佳实践,形成自己的知识体系

47、Swagger 介绍及集成

47.1 核心定义

Swagger 是一款RESTful 接口文档工具,通过注解自动生成接口文档,支持在线调试,解决前后端接口文档不一致、维护成本高的问题,是 Java 后端开发的标准工具。

47.2 环境准备(SpringDoc/Swagger3,SpringBoot3.x 推荐)

  1. 依赖引入(pom.xml)

xml

复制代码
<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
    <version>2.2.0</version>
</dependency>

若为 SpringBoot2.x,使用springdoc-openapi-ui 1.6.x 版本

  1. 基础配置(application.yml)

yaml

复制代码
springdoc:
  api-docs:
    enabled: true # 开启接口文档
    path: /v3/api-docs # 接口JSON路径
  swagger-ui:
    enabled: true # 开启SwaggerUI页面
    path: /swagger-ui.html # UI访问路径

47.3 访问地址

  • SwaggerUI 页面:http://localhost:8080/swagger-ui.html
  • 接口 JSON:http://localhost:8080/v3/api-docs

47.4 核心注解说明

注解 作用
@OpenAPIDefinition 全局文档配置(标题、版本、描述)
@Operation 接口方法描述
@Parameter 接口参数描述
@ApiResponse 接口响应描述
@Schema 实体类 / 属性描述
@Tag 接口分组标签

48、配置 Swagger 信息

48.1 全局配置类

java

复制代码
@Configuration
@OpenAPIDefinition(
    info = @Info(
        title = "员工管理系统API文档",
        version = "1.0.0",
        description = "基于SpringBoot+Shiro+MyBatis的员工管理系统接口文档",
        contact = @Contact(
            name = "开发者",
            email = "developer@example.com",
            url = "https://www.example.com"
        ),
        license = @License(
            name = "Apache 2.0",
            url = "https://www.apache.org/licenses/LICENSE-2.0"
        )
    ),
    servers = @Server(url = "http://localhost:8080", description = "本地开发环境")
)
public class SwaggerConfig {

    /**
     * 配置接口扫描规则(仅扫描指定包下的接口)
     */
    @Bean
    public GroupedOpenApi employeeApi() {
        return GroupedOpenApi.builder()
                .group("员工管理模块")
                .pathsToMatch("/emp/**")
                .packagesToScan("com.example.emp.controller")
                .build();
    }

    /**
     * 配置用户模块接口
     */
    @Bean
    public GroupedOpenApi userApi() {
        return GroupedOpenApi.builder()
                .group("用户管理模块")
                .pathsToMatch("/user/**", "/admin/**")
                .build();
    }

    /**
     * 生产环境关闭Swagger(通过@Profile指定环境)
     */
    @Bean
    @Profile("!prod")
    public GroupedOpenApi allApi() {
        return GroupedOpenApi.builder()
                .group("全部接口")
                .pathsToMatch("/**")
                .build();
    }
}

48.2 接口注解使用

java

复制代码
@RestController
@RequestMapping("/emp")
@Tag(name = "员工管理接口", description = "员工CRUD相关接口")
public class EmployeeController {

    @Autowired
    private EmployeeService employeeService;

    @Operation(summary = "查询所有员工", description = "获取员工列表,支持分页")
    @ApiResponse(responseCode = "200", description = "查询成功")
    @GetMapping("/list")
    public List<Employee> list() {
        return employeeService.getAllEmployees();
    }

    @Operation(summary = "根据ID查询员工", description = "通过员工ID查询详细信息")
    @ApiResponse(responseCode = "200", description = "查询成功")
    @ApiResponse(responseCode = "404", description = "员工不存在")
    @GetMapping("/{id}")
    public Employee getById(
            @Parameter(description = "员工ID", required = true) @PathVariable Integer id) {
        return employeeService.getById(id);
    }

    @Operation(summary = "添加员工", description = "新增员工信息")
    @ApiResponse(responseCode = "200", description = "添加成功")
    @PostMapping("/add")
    public String add(
            @Parameter(description = "员工信息", required = true) @RequestBody Employee employee) {
        employeeService.add(employee);
        return "添加成功";
    }
}

48.3 实体类注解使用

java

复制代码
@Data
@Schema(description = "员工实体类")
public class Employee {
    @Schema(description = "员工ID", example = "1")
    private Integer id;

    @Schema(description = "员工姓名", example = "张三")
    private String lastName;

    @Schema(description = "员工邮箱", example = "zhangsan@example.com")
    private String email;

    @Schema(description = "性别:1男 0女", example = "1")
    private Integer gender;

    @Schema(description = "部门", example = "技术部")
    private String department;

    @Schema(description = "生日", example = "2000-01-01")
    private Date birth;
}

48.4 生产环境注意事项

  • 生产环境必须关闭 Swagger,避免接口泄露:通过@Profile("!prod)或配置文件springdoc.api-docs.enabled=false关闭
  • 敏感接口(如登录、支付)需隐藏,避免泄露
  • 可配置认证,仅授权用户可访问 Swagger 文档

49、配置扫描接口及开关

49.1 核心目标

通过配置灵活控制 Swagger 接口扫描范围、文档开关,实现开发环境开启、生产环境关闭,避免接口泄露,同时精准过滤敏感接口。

49.2 接口扫描配置

方式 1:按包扫描(推荐)

java

复制代码
@Configuration
public class SwaggerConfig {
    /**
     * 仅扫描指定包下的接口,过滤无关接口
     */
    @Bean
    public GroupedOpenApi api() {
        return GroupedOpenApi.builder()
                .group("员工管理系统API")
                // 扫描指定包
                .packagesToScan("com.example.emp.controller")
                // 排除指定包(如测试接口)
                .packagesToExclude("com.example.emp.test")
                .build();
    }
}
方式 2:按路径匹配

java

复制代码
@Bean
public GroupedOpenApi api() {
    return GroupedOpenApi.builder()
            .group("员工管理系统API")
            // 匹配指定路径
            .pathsToMatch("/emp/**", "/user/**")
            // 排除敏感路径(如登录、支付)
            .pathsToExclude("/login", "/pay/**")
            .build();
}

49.3 文档开关配置(环境隔离)

方式 1:通过 @Profile 注解(推荐)

java

复制代码
@Configuration
@EnableOpenApi // 仅在指定环境开启Swagger
@Profile({"dev", "test"}) // 仅开发、测试环境开启,生产环境自动关闭
public class SwaggerConfig {
    // 配置类内容
}
方式 2:配置文件动态控制

yaml

复制代码
# application-dev.yml(开发环境)
springdoc:
  api-docs:
    enabled: true
  swagger-ui:
    enabled: true

# application-prod.yml(生产环境)
springdoc:
  api-docs:
    enabled: false
  swagger-ui:
    enabled: false
方式 3:代码动态判断

java

复制代码
@Bean
public GroupedOpenApi api(@Value("${springdoc.api-docs.enabled:true}") boolean enabled) {
    return GroupedOpenApi.builder()
            .group("API文档")
            .displayOperationId(enabled)
            .build();
}

49.4 敏感接口过滤

java

复制代码
@Bean
public GroupedOpenApi api() {
    return GroupedOpenApi.builder()
            .group("API文档")
            // 排除登录、注册、支付等敏感接口
            .pathsToExclude(
                    "/login", "/register", "/pay/**",
                    "/admin/**" // 管理员接口
            )
            .build();
}

50、分组和接口注释及小结

50.1 接口分组配置

按业务模块对接口分组,提升文档可读性,方便前后端协作:

java

复制代码
@Configuration
public class SwaggerConfig {

    /**
     * 员工管理模块分组
     */
    @Bean
    public GroupedOpenApi empApi() {
        return GroupedOpenApi.builder()
                .group("员工管理模块")
                .pathsToMatch("/emp/**")
                .packagesToScan("com.example.emp.controller.emp")
                .build();
    }

    /**
     * 用户管理模块分组
     */
    @Bean
    public GroupedOpenApi userApi() {
        return GroupedOpenApi.builder()
                .group("用户管理模块")
                .pathsToMatch("/user/**", "/admin/**")
                .packagesToScan("com.example.emp.controller.user")
                .build();
    }

    /**
     * 系统模块分组
     */
    @Bean
    public GroupedOpenApi systemApi() {
        return GroupedOpenApi.builder()
                .group("系统管理模块")
                .pathsToMatch("/system/**", "/log/**")
                .build();
    }
}

50.2 接口注释规范

1. 类 / 接口注释

java

复制代码
@RestController
@RequestMapping("/emp")
@Tag(
    name = "员工管理接口",
    description = "员工CRUD、列表查询、信息修改等相关接口"
)
public class EmployeeController {
    // 接口方法
}
2. 方法 / 参数注释

java

复制代码
@Operation(
    summary = "分页查询员工列表",
    description = "根据页码、每页条数分页查询员工,支持按姓名模糊搜索"
)
@ApiResponses({
    @ApiResponse(responseCode = "200", description = "查询成功"),
    @ApiResponse(responseCode = "401", description = "未登录"),
    @ApiResponse(responseCode = "403", description = "无权限")
})
@GetMapping("/list")
public R<PageResult<Employee>> list(
        @Parameter(description = "页码", example = "1", required = true)
        @RequestParam(defaultValue = "1") Integer pageNum,
        @Parameter(description = "每页条数", example = "10", required = true)
        @RequestParam(defaultValue = "10") Integer pageSize,
        @Parameter(description = "搜索关键词", example = "张三")
        @RequestParam(required = false) String keyword
) {
    // 业务逻辑
}
3. 实体类 / 属性注释

java

复制代码
@Data
@Schema(description = "员工实体类")
public class Employee {
    @Schema(description = "员工ID", example = "1", required = true)
    private Integer id;

    @Schema(description = "员工姓名", example = "张三", minLength = 2, maxLength = 10)
    private String lastName;

    @Schema(description = "员工邮箱", example = "zhangsan@example.com", format = "email")
    private String email;
}

50.3 Swagger 核心小结

核心特性 作用
自动生成文档 基于注解自动生成接口文档,无需手动维护
在线调试 直接在文档页发送请求,测试接口
分组管理 按业务模块分组,提升可读性
环境隔离 开发 / 测试环境开启,生产环境关闭,保障安全
多格式导出 支持导出 JSON/HTML 格式文档,方便协作

51、异步任务

51.1 核心原理

SpringBoot 通过@Async注解实现异步任务,将耗时操作(如文件上传、短信发送)放入子线程执行,主线程快速返回,提升接口响应速度。

51.2 快速入门

1. 开启异步支持(启动类添加)

java

复制代码
@SpringBootApplication
@EnableAsync // 开启异步任务支持
public class EmpApplication {
    public static void main(String[] args) {
        SpringApplication.run(EmpApplication.class, args);
    }
}
2. 异步方法实现

java

复制代码
@Service
public class AsyncService {

    /**
     * 异步方法:耗时操作
     */
    @Async // 标记为异步任务,Spring自动创建子线程执行
    public void asyncMethod() throws InterruptedException {
        System.out.println("异步任务开始,线程名:" + Thread.currentThread().getName());
        // 模拟耗时操作(如文件处理、第三方接口调用)
        Thread.sleep(5000);
        System.out.println("异步任务结束,线程名:" + Thread.currentThread().getName());
    }
}
3. Controller 调用

java

复制代码
@RestController
public class AsyncController {
    @Autowired
    private AsyncService asyncService;

    @GetMapping("/async")
    public String asyncTest() throws InterruptedException {
        System.out.println("主线程开始,线程名:" + Thread.currentThread().getName());
        // 调用异步方法,主线程不阻塞
        asyncService.asyncMethod();
        System.out.println("主线程结束,线程名:" + Thread.currentThread().getName());
        return "异步任务已提交";
    }
}

51.3 自定义线程池(推荐)

默认线程池为SimpleAsyncTaskExecutor,无限制创建线程,生产环境需自定义线程池:

java

复制代码
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {

    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 核心线程数
        executor.setCorePoolSize(10);
        // 最大线程数
        executor.setMaxPoolSize(20);
        // 队列容量
        executor.setQueueCapacity(100);
        // 线程名前缀
        executor.setThreadNamePrefix("async-emp-");
        // 线程存活时间
        executor.setKeepAliveSeconds(60);
        // 等待任务完成再关闭
        executor.setWaitForTasksToCompleteOnShutdown(true);
        executor.initialize();
        return executor;
    }

    /**
     * 异步任务异常处理
     */
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return (ex, method, params) -> {
            System.out.println("异步任务异常:" + ex.getMessage());
            // 记录日志、告警
        };
    }
}

51.4 异步任务带返回值

java

复制代码
@Async
public Future<String> asyncTaskWithResult() throws InterruptedException {
    Thread.sleep(3000);
    return new AsyncResult<>("异步任务执行完成");
}

// 调用
Future<String> future = asyncService.asyncTaskWithResult();
String result = future.get(); // 阻塞获取结果

52、邮件任务

52.1 核心目标

SpringBoot 通过spring-boot-starter-mail快速集成邮件功能,支持普通邮件、HTML 邮件、带附件邮件、模板邮件,适用于注册验证码、通知、告警等场景。

52.2 环境准备

1. 依赖引入(pom.xml)

xml

复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-mail</artifactId>
</dependency>
2. 配置文件(application.yml)

yaml

复制代码
spring:
  mail:
    # 邮件服务器SMTP地址(QQ邮箱为smtp.qq.com,163为smtp.163.com)
    host: smtp.qq.com
    # 端口(QQ邮箱465/587,163为25/465)
    port: 465
    # 发件人邮箱
    username: xxx@qq.com
    # 授权码(非邮箱密码,需在邮箱设置中开启SMTP获取)
    password: xxxxxxxxxxxxxxxx
    # 编码
    default-encoding: UTF-8
    # 开启SSL
    properties:
      mail:
        smtp:
          ssl:
            enable: true
          auth: true

52.3 邮件发送实现

1. 普通文本邮件

java

复制代码
@Service
public class MailService {
    @Autowired
    private JavaMailSender javaMailSender;

    @Value("${spring.mail.username}")
    private String from;

    /**
     * 发送普通文本邮件
     */
    public void sendSimpleMail(String to, String subject, String content) {
        SimpleMailMessage message = new SimpleMailMessage();
        message.setFrom(from); // 发件人
        message.setTo(to);     // 收件人
        message.setSubject(subject); // 主题
        message.setText(content);    // 内容
        javaMailSender.send(message);
    }
}
2. HTML 邮件(带样式)

java

复制代码
public void sendHtmlMail(String to, String subject, String htmlContent) throws MessagingException {
    MimeMessage message = javaMailSender.createMimeMessage();
    MimeMessageHelper helper = new MimeMessageHelper(message, true);
    helper.setFrom(from);
    helper.setTo(to);
    helper.setSubject(subject);
    // 第二个参数true表示启用HTML
    helper.setText(htmlContent, true);
    javaMailSender.send(message);
}
3. 带附件邮件

java

复制代码
public void sendAttachmentMail(String to, String subject, String content, String filePath) throws MessagingException {
    MimeMessage message = javaMailSender.createMimeMessage();
    MimeMessageHelper helper = new MimeMessageHelper(message, true);
    helper.setFrom(from);
    helper.setTo(to);
    helper.setSubject(subject);
    helper.setText(content, true);

    // 添加附件
    File file = new File(filePath);
    helper.addAttachment(file.getName(), file);
    javaMailSender.send(message);
}
4. 模板邮件(Thymeleaf)

java

复制代码
@Autowired
private TemplateEngine templateEngine;

public void sendTemplateMail(String to, String subject, Map<String, Object> params) throws MessagingException {
    // 加载Thymeleaf模板
    Context context = new Context();
    context.setVariables(params);
    String htmlContent = templateEngine.process("email/register", context);

    // 发送HTML邮件
    sendHtmlMail(to, subject, htmlContent);
}

53、定时执行任务

53.1 核心原理

SpringBoot 通过@Scheduled注解实现定时任务,支持固定频率、固定延迟、Cron 表达式三种方式,适用于数据统计、日志清理、定时通知等场景。

53.2 快速入门

1. 开启定时任务(启动类添加)

java

复制代码
@SpringBootApplication
@EnableScheduling // 开启定时任务支持
public class EmpApplication {
    public static void main(String[] args) {
        SpringApplication.run(EmpApplication.class, args);
    }
}
2. 定时任务实现

java

复制代码
@Service
public class ScheduledService {

    /**
     * 固定频率执行:每隔5秒执行一次(从上一次开始时间计算)
     */
    @Scheduled(fixedRate = 5000)
    public void fixedRateTask() {
        System.out.println("固定频率任务执行,时间:" + new Date());
    }

    /**
     * 固定延迟执行:上一次执行完成后,延迟3秒执行
     */
    @Scheduled(fixedDelay = 3000)
    public void fixedDelayTask() {
        System.out.println("固定延迟任务执行,时间:" + new Date());
    }

    /**
     * Cron表达式:每天凌晨1点执行
     */
    @Scheduled(cron = "0 0 1 * * ?")
    public void cronTask() {
        System.out.println("Cron定时任务执行,时间:" + new Date());
    }
}

53.3 Cron 表达式详解

Cron 表达式格式:秒 分 时 日 月 周 年(可选)

字段 允许值 允许特殊字符
0-59 , - * /
0-59 , - * /
0-23 , - * /
1-31 , - * ? / L W
1-12 或 JAN-DEC , - * /
1-7 或 SUN-SAT(1 = 周日) , - * ? / L #
常用 Cron 表达式示例
  • 0 0/5 * * * ?:每隔 5 分钟执行
  • 0 0 8,12,18 * * ?:每天 8 点、12 点、18 点执行
  • 0 0 1 ? * MON-FRI:工作日凌晨 1 点执行
  • 0 0 0 1 * ?:每月 1 号凌晨执行

53.4 自定义线程池(多线程定时任务)

默认定时任务单线程执行,任务阻塞会影响其他任务,需自定义线程池:

java

复制代码
@Configuration
@EnableScheduling
public class ScheduledConfig implements SchedulingConfigurer {

    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
        scheduler.setPoolSize(10); // 线程池大小
        scheduler.setThreadNamePrefix("scheduled-emp-");
        scheduler.initialize();
        taskRegistrar.setTaskScheduler(scheduler);
    }
}

53.5 动态定时任务

基于数据库配置定时任务,实现动态修改执行时间,无需重启项目:

java

复制代码
@Service
public class DynamicScheduledService implements SchedulingConfigurer {

    @Autowired
    private TaskMapper taskMapper;

    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        taskRegistrar.addTriggerTask(
                // 任务逻辑
                () -> System.out.println("动态定时任务执行"),
                // 动态触发时间
                triggerContext -> {
                    // 从数据库查询Cron表达式
                    String cron = taskMapper.getCronById(1);
                    CronTrigger trigger = new CronTrigger(cron);
                    return trigger.nextExecutionTime(triggerContext);
                }
        );
    }
}

54、SpringBoot 集成 Redis

54.1 核心定位

Redis 是高性能内存数据库 ,SpringBoot 通过spring-boot-starter-data-redis快速集成,用于缓存、分布式锁、会话共享、计数器等场景,提升系统性能。

54.2 环境准备

1. 依赖引入(pom.xml)

xml

复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 连接池(SpringBoot2.x+默认集成) -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>
2. 配置文件(application.yml)

yaml

复制代码
spring:
  redis:
    # Redis地址
    host: localhost
    # 端口
    port: 6379
    # 密码(无密码则省略)
    password: 123456
    # 数据库(0-15,默认0)
    database: 0
    # 连接超时时间
    timeout: 3000ms
    # 连接池配置
    lettuce:
      pool:
        # 最大连接数
        max-active: 8
        # 最大空闲连接
        max-idle: 8
        # 最小空闲连接
        min-idle: 0
        # 最大等待时间
        max-wait: -1ms

54.3 RedisTemplate 常用操作

SpringBoot 自动配置StringRedisTemplate(操作字符串)和RedisTemplate<Object, Object>(操作对象):

java

复制代码
@RestController
public class RedisController {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private RedisTemplate<Object, Object> redisTemplate;

    /**
     * 字符串操作
     */
    @GetMapping("/string")
    public String stringTest() {
        // 写入字符串
        stringRedisTemplate.opsForValue().set("username", "admin");
        // 读取字符串
        String username = stringRedisTemplate.opsForValue().get("username");
        // 设置过期时间
        stringRedisTemplate.expire("username", 1, TimeUnit.HOURS);
        return username;
    }

    /**
     * 对象操作
     */
    @GetMapping("/object")
    public Employee objectTest() {
        Employee emp = new Employee(1, "张三", "zhangsan@example.com", 1, "技术部", new Date());
        // 写入对象
        redisTemplate.opsForValue().set("emp:1", emp);
        // 读取对象
        Employee empResult = (Employee) redisTemplate.opsForValue().get("emp:1");
        return empResult;
    }

    /**
     * 哈希操作
     */
    @GetMapping("/hash")
    public Map<Object, Object> hashTest() {
        // 写入哈希
        stringRedisTemplate.opsForHash().put("user:1", "name", "张三");
        stringRedisTemplate.opsForHash().put("user:1", "age", "20");
        // 读取哈希
        Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries("user:1");
        return userMap;
    }

    /**
     * 列表操作
     */
    @GetMapping("/list")
    public List<String> listTest() {
        // 左推写入列表
        stringRedisTemplate.opsForList().leftPush("list:1", "a");
        stringRedisTemplate.opsForList().leftPush("list:1", "b");
        // 右弹出读取
        String value = stringRedisTemplate.opsForList().rightPop("list:1");
        // 获取列表全部
        List<String> list = stringRedisTemplate.opsForList().range("list:1", 0, -1);
        return list;
    }
}

55、自定义 RedisTemplate

55.1 核心问题

默认RedisTemplate使用 JDK 序列化,存储对象时会出现乱码,且可读性差,需自定义序列化方式(推荐 JSON 序列化)。

55.2 自定义 RedisTemplate 配置

java

复制代码
@Configuration
@AutoConfigureAfter(RedisAutoConfiguration.class)
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);

        // 配置JSON序列化器(Jackson2JsonRedisSerializer)
        Jackson2JsonRedisSerializer<Object> jsonSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        // 解决日期序列化问题
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
        jsonSerializer.setObjectMapper(objectMapper);

        // 配置String序列化器(用于key)
        StringRedisSerializer stringSerializer = new StringRedisSerializer();

        // 设置key和hashKey的序列化方式为String
        template.setKeySerializer(stringSerializer);
        template.setHashKeySerializer(stringSerializer);

        // 设置value和hashValue的序列化方式为JSON
        template.setValueSerializer(jsonSerializer);
        template.setHashValueSerializer(jsonSerializer);

        // 初始化RedisTemplate
        template.afterPropertiesSet();
        return template;
    }
}

55.3 序列化方式对比

序列化方式 优点 缺点 适用场景
JDK 序列化 无需额外配置 乱码、可读性差、体积大 快速测试
JSON 序列化 可读性好、体积小、跨语言 需配置 生产环境推荐
String 序列化 简单、可读性好 仅支持字符串 字符串类型 key/value
Kryo 序列化 体积小、速度快 需额外依赖、兼容性一般 高性能场景

55.4 常用工具类封装

java

复制代码
@Component
public class RedisUtils {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 写入缓存
     */
    public boolean set(String key, Object value) {
        try {
            redisTemplate.opsForValue().set(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 写入缓存并设置过期时间
     */
    public boolean set(String key, Object value, long time, TimeUnit unit) {
        try {
            redisTemplate.opsForValue().set(key, value, time, unit);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 读取缓存
     */
    public Object get(String key) {
        return key == null ? null : redisTemplate.opsForValue().get(key);
    }

    /**
     * 删除缓存
     */
    public boolean delete(String key) {
        return redisTemplate.delete(key);
    }

    /**
     * 分布式锁(加锁)
     */
    public boolean tryLock(String key, String value, long expireTime) {
        return Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(key, value, expireTime, TimeUnit.SECONDS));
    }

    /**
     * 分布式锁(解锁)
     */
    public boolean unlock(String key, String value) {
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
        Long result = redisTemplate.execute(redisScript, Collections.singletonList(key), value);
        return result != null && result == 1;
    }
}

56、分布式系统理论

56.1 核心定义

分布式系统是由多个独立节点组成的系统,节点通过网络通信协作,共同完成一个目标,核心目标是提升系统性能、可用性、可扩展性。

56.2 核心特性

特性 定义
可扩展性 系统可通过增加节点线性提升性能,支持水平扩展
高可用性 系统部分节点故障时,仍能正常提供服务
一致性 所有节点数据保持一致(CAP 定理核心)
分区容错性 网络分区时,系统仍能正常工作
容错性 系统可自动处理节点故障、网络异常

56.3 核心理论

1. CAP 定理

分布式系统中,一致性(Consistency)、可用性(Availability)、分区容错性(Partition Tolerance) 三者不可兼得,最多同时满足两个:

  • CA:放弃分区容错,仅适用于单体系统
  • CP:放弃可用性,保证一致性(如 Zookeeper、分布式锁)
  • AP:放弃强一致性,保证可用性(如 Redis 集群、最终一致性)
2. BASE 理论

CAP 的落地实践,核心是基本可用(Basically Available)、软状态(Soft State)、最终一致性(Eventually Consistent),适用于分布式系统:

  • 基本可用:系统出现故障时,允许部分功能不可用
  • 软状态:系统中间状态不影响最终结果
  • 最终一致性:数据最终会达到一致,无需实时一致
3. 分布式锁

解决分布式系统中并发资源竞争问题,核心要求:

  • 互斥性:同一时间只有一个节点持有锁
  • 安全性:锁只能被持有者释放
  • 可用性:锁超时自动释放,避免死锁
  • 高性能:加锁 / 解锁操作高效
4. 分布式事务

解决分布式系统中数据一致性问题,常见方案:

  • 2PC(两阶段提交):强一致性,性能差,适合低并发场景
  • TCC(Try-Confirm-Cancel):柔性事务,需业务改造
  • 最终一致性:基于消息队列,保证数据最终一致(如 RocketMQ 事务消息)

56.4 分布式系统核心问题与解决方案

问题 解决方案
服务间通信 RESTful API、RPC(Dubbo、Feign)、消息队列(RabbitMQ、RocketMQ)
服务注册发现 Nacos、Eureka、Consul
负载均衡 Nginx、Ribbon、Spring Cloud LoadBalancer
服务熔断降级 Hystrix、Sentinel
分布式缓存 Redis 集群、Codis
分布式日志 ELK、SkyWalking
分布式追踪 Zipkin、Jaeger

56.5 SpringBoot 与分布式

SpringBoot 是分布式系统的基础开发框架,配合 Spring Cloud 可快速构建微服务架构,核心组件:

  • Spring Cloud Alibaba:Nacos(注册发现 / 配置中心)、Sentinel(熔断降级)、Seata(分布式事务)
  • Spring Cloud Netflix:Eureka、Hystrix、Zuul
  • Spring Cloud Gateway:API 网关

57、什么是 RPC

57.1 核心定义

RPC(Remote Procedure Call,远程过程调用)是一种分布式系统通信协议,允许程序像调用本地方法一样调用远程服务器的方法,屏蔽网络通信细节,让开发者无需关注底层网络实现,专注于业务逻辑。

57.2 RPC 核心原理

RPC 的核心是 **「透明化远程调用」**,完整流程如下:

  1. 客户端调用:客户端像调用本地方法一样调用远程服务,传入参数
  2. 客户端 Stub(存根):将方法名、参数等信息序列化,封装成网络请求报文
  3. 网络传输:通过 TCP/IP 等协议将请求发送到服务端
  4. 服务端 Stub(存根):接收请求,反序列化报文,解析出方法名和参数
  5. 服务端执行:调用本地服务方法,执行业务逻辑,返回结果
  6. 服务端 Stub 序列化:将返回结果序列化,封装成响应报文
  7. 网络回传:将响应发送回客户端
  8. 客户端 Stub 反序列化:解析响应,将结果返回给调用者

57.3 RPC 核心组件

组件 作用
Stub(存根) 封装序列化 / 反序列化,屏蔽网络细节,让调用像本地方法
序列化框架 将对象转换为二进制流,常用:Hessian、Protobuf、JSON
网络传输 底层通信协议,常用:TCP(长连接)、HTTP/2
服务注册发现 动态管理服务地址,实现服务上下线感知
负载均衡 客户端 / 服务端负载均衡,提升系统可用性
熔断降级 服务故障时自动降级,避免雪崩

57.4 RPC vs HTTP

对比维度 RPC HTTP(RESTful)
性能 基于 TCP 长连接,序列化高效,性能高 基于 HTTP 协议,开销大,性能较低
易用性 调用像本地方法,开发效率高 需手动处理请求 / 响应,开发成本高
通用性 跨语言支持弱(部分框架) 跨语言、跨平台,通用性强
适用场景 微服务内部、高并发、低延迟场景 对外 API、前后端分离、跨平台场景

57.5 主流 RPC 框架

  • Dubbo:阿里开源,Java 生态主流,SpringBoot 生态完美适配
  • gRPC:Google 开源,基于 HTTP/2,跨语言支持优秀
  • Thrift:Apache 开源,跨语言,适合多语言异构系统
  • Feign:SpringCloud 组件,基于 HTTP,封装 RESTful 调用

58、Dubbo 及 Zookeeper 安装

58.1 核心定位

  • Dubbo:阿里开源的高性能 Java RPC 框架,是微服务架构的核心通信组件,提供服务注册发现、负载均衡、熔断降级等能力
  • Zookeeper :Apache 开源的分布式协调服务,作为 Dubbo 的注册中心,存储服务地址信息,实现服务上下线感知

58.2 Zookeeper 安装(Linux 环境)

  1. 环境准备:JDK1.8+(Zookeeper 依赖 Java)

  2. 下载安装

    bash

    复制代码
    # 下载Zookeeper(推荐3.7.x版本)
    wget https://archive.apache.org/dist/zookeeper/zookeeper-3.7.1/apache-zookeeper-3.7.1-bin.tar.gz
    # 解压
    tar -zxvf apache-zookeeper-3.7.1-bin.tar.gz
    # 重命名
    mv apache-zookeeper-3.7.1-bin zookeeper
  3. 配置文件

    bash

    复制代码
    cd zookeeper/conf
    cp zoo_sample.cfg zoo.cfg
    # 编辑配置文件
    vim zoo.cfg

    核心配置:

    properties

    复制代码
    # 数据存储目录
    dataDir=/usr/local/zookeeper/data
    # 客户端端口
    clientPort=2181
    # 心跳时间
    tickTime=2000
  4. 启动与验证

    bash

    复制代码
    # 启动Zookeeper
    bin/zkServer.sh start
    # 查看状态
    bin/zkServer.sh status
    # 客户端连接
    bin/zkCli.sh -server localhost:2181

58.3 Dubbo 环境准备(SpringBoot 集成)

  1. 依赖引入(pom.xml)

    xml

    复制代码
    <!-- Dubbo SpringBoot Starter -->
    <dependency>
        <groupId>org.apache.dubbo</groupId>
        <artifactId>dubbo-spring-boot-starter</artifactId>
        <version>3.2.0</version>
    </dependency>
    <!-- Zookeeper客户端 -->
    <dependency>
        <groupId>org.apache.dubbo</groupId>
        <artifactId>dubbo-registry-zookeeper</artifactId>
        <version>3.2.0</version>
    </dependency>
    <dependency>
        <groupId>org.apache.zookeeper</groupId>
        <artifactId>zookeeper</artifactId>
        <version>3.7.1</version>
        <exclusions>
            <exclusion>
                <groupId>org.slf4j</groupId>
                <artifactId>slf4j-log4j12</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
  2. 核心配置(application.yml)

    yaml

    复制代码
    # 服务提供者配置
    dubbo:
      application:
        name: dubbo-provider # 应用名
      registry:
        address: zookeeper://localhost:2181 # Zookeeper地址
        timeout: 3000
      protocol:
        name: dubbo # 通信协议
        port: 20880 # Dubbo端口
      scan:
        base-packages: com.example.dubbo.provider # 扫描服务接口

59、Dubbo-admin 安装测试

59.1 核心定位

Dubbo-admin 是 Dubbo 提供的可视化管理控制台,用于监控服务状态、管理服务上下线、查看调用统计、配置负载均衡等,是 Dubbo 微服务的运维核心工具。

59.2 安装部署(Docker 方式,推荐)

  1. 拉取镜像

    bash

    复制代码
    docker pull apache/dubbo-admin:latest
  2. 启动容器

    bash

    复制代码
    docker run -d \
      -p 38080:38080 \
      -e admin.registry.address=zookeeper://zookeeper地址:2181 \
      -e admin.config-center.address=zookeeper://zookeeper地址:2181 \
      --name dubbo-admin \
      apache/dubbo-admin:latest
  3. 访问验证

    • 访问地址:http://localhost:38080
    • 默认账号 / 密码:root / rootguest / guest

59.3 核心功能

  • 服务治理:查看服务列表、服务提供者 / 消费者、服务上下线
  • 监控统计:服务调用次数、响应时间、成功率
  • 配置管理:动态配置负载均衡、熔断降级、路由规则
  • 权限管理:用户、角色、权限管理

59.4 测试流程

  1. 启动 Zookeeper、Dubbo-admin
  2. 启动 Dubbo 服务提供者,服务自动注册到 Zookeeper
  3. 登录 Dubbo-admin,查看服务是否注册成功
  4. 启动 Dubbo 服务消费者,调用服务,查看调用统计

60、服务注册发现实战

60.1 核心原理

服务注册发现是微服务架构的核心机制:

  • 服务注册:服务启动时,将自己的地址、端口、服务名等信息注册到注册中心(Zookeeper)
  • 服务发现:服务消费者从注册中心获取服务提供者地址列表
  • 服务感知:注册中心实时监控服务状态,服务上下线时自动通知消费者

60.2 实战步骤

1. 定义服务接口(公共模块)

java

复制代码
public interface EmployeeService {
    // 根据ID查询员工
    Employee getEmployeeById(Integer id);
    // 查询所有员工
    List<Employee> listEmployees();
}
2. 服务提供者实现

java

复制代码
@DubboService // Dubbo服务注解,标记为服务提供者
@Service
public class EmployeeServiceImpl implements EmployeeService {

    @Override
    public Employee getEmployeeById(Integer id) {
        return new Employee(id, "张三", "zhangsan@example.com", 1, "技术部", new Date());
    }

    @Override
    public List<Employee> listEmployees() {
        return Arrays.asList(
                new Employee(1, "张三", "zhangsan@example.com", 1, "技术部", new Date()),
                new Employee(2, "李四", "lisi@example.com", 1, "人事部", new Date())
        );
    }
}
3. 服务消费者实现

java

复制代码
@RestController
@RequestMapping("/consumer")
public class EmployeeConsumerController {

    @DubboReference // Dubbo引用注解,标记为服务消费者
    private EmployeeService employeeService;

    @GetMapping("/emp/{id}")
    public Employee getEmployee(@PathVariable Integer id) {
        return employeeService.getEmployeeById(id);
    }

    @GetMapping("/emp/list")
    public List<Employee> listEmployees() {
        return employeeService.listEmployees();
    }
}
4. 消费者配置(application.yml)

yaml

复制代码
dubbo:
  application:
    name: dubbo-consumer
  registry:
    address: zookeeper://localhost:2181
  protocol:
    name: dubbo
  consumer:
    timeout: 3000 # 调用超时时间
    loadbalance: round-robin # 负载均衡策略(轮询)
5. 启动验证
  1. 启动 Zookeeper
  2. 启动服务提供者,服务自动注册到 Zookeeper
  3. 启动服务消费者,调用接口,验证服务调用成功
  4. 登录 Dubbo-admin,查看服务提供者 / 消费者状态

60.3 核心特性

  • 负载均衡:Dubbo 提供多种负载均衡策略:轮询、随机、加权轮询、一致性哈希
  • 熔断降级:服务故障时自动降级,返回默认值,避免系统雪崩
  • 服务治理:动态配置服务权重、路由规则,实现灰度发布
  • 高可用:注册中心集群、服务多实例部署,避免单点故障

61、聊聊现在和未来

61.1 技术现状总结

本教程完整覆盖了 SpringBoot 从入门到微服务的全栈技术,核心技术栈如下:

技术模块 核心内容
SpringBoot 基础 自动配置、启动原理、配置文件、Web 开发
数据层集成 JDBC、Druid、MyBatis、事务管理
安全框架 SpringSecurity、Shiro、认证授权、登录拦截
工具集成 Swagger 接口文档、异步 / 邮件 / 定时任务
中间件集成 Redis 缓存、分布式锁、分布式系统理论
微服务通信 RPC、Dubbo、Zookeeper、服务注册发现

61.2 技术发展趋势

1. 微服务架构演进
  • 云原生:SpringBoot+SpringCloud+K8s 成为微服务标准架构
  • 服务网格(Service Mesh):Istio、Linkerd,将服务治理下沉到基础设施
  • Serverless:无服务器架构,进一步降低运维成本
2. RPC 框架发展
  • Dubbo 3.x:支持 HTTP/3、云原生、多语言,性能大幅提升
  • gRPC:云原生时代主流 RPC 框架,跨语言、高性能
  • RSocket:响应式 RPC,支持双向通信、流式调用
3. 数据层技术
  • MyBatis-Plus:MyBatis 增强工具,简化 CRUD 开发
  • SpringData JPA:ORM 框架,适合简单 CRUD 场景
  • 分布式数据库:Sharding-JDBC、TiDB,解决分库分表问题
4. 安全与监控
  • SpringSecurity 6.x:全新 API,更简洁的安全配置
  • SkyWalking:分布式链路追踪、APM 监控,微服务运维核心工具
  • Sentinel:流量控制、熔断降级,保障微服务稳定性

61.3 学习路线建议

  1. 夯实基础:深入理解 SpringBoot 自动配置原理、Spring 核心 IOC/AOP
  2. 数据层精通:MyBatis、Redis、分布式锁、分布式事务
  3. 微服务进阶:SpringCloud Alibaba、Dubbo、Nacos、Sentinel
  4. 云原生学习:Docker、K8s、Jenkins、CI/CD
  5. 性能优化:JVM 调优、数据库优化、缓存优化、并发编程

61.4 总结

SpringBoot 是 Java 后端开发的基石框架,本教程从入门到实战,完整覆盖了企业级项目开发的核心技术栈。通过本教程的学习,你已经掌握了从单体应用到微服务架构的全流程开发能力,后续可基于此深入学习微服务、云原生等进阶技术,成为 Java 后端全栈工程师。

相关推荐
禹中一只鱼2 小时前
【IDEA 出现 `IDE error occurred`】
java·ide·spring boot·intellij-idea
weixin_408099672 小时前
【保姆级教程】按键精灵调用 OCR 文字识别 API(从0到1完整实战 + 可运行脚本)
java·前端·人工智能·后端·ocr·api·按键精灵
卓怡学长2 小时前
基于 SpringBoot 的生活信息分享平台,从 0 到 1 完整实现(附源码 + 数据库)
java·数据库·spring boot·tomcat·maven
hero.fei2 小时前
RoaringBitmap在SpringBoot中的使用以及与BitSet对比
java·spring boot·spring
Traving Yu2 小时前
Spring源码与框架原理
java·后端·spring
王家视频教程图书馆2 小时前
rust 写gui 程序 最流行的是哪个
开发语言·后端·rust
AI服务老曹2 小时前
异构计算与边缘协同:基于 Spring Boot 的 AI 视频管理平台架构深度解析
人工智能·spring boot·音视频
好大哥呀2 小时前
如何在Spring Boot中配置数据库连接?
数据库·spring boot·后端
老神在在0012 小时前
企业级 SpringBoot 后端通用开发规范|统一响应 + 敏感字段加密
spring boot·后端·状态模式