第1章 First Spring Boot

认识Spring Boot

我们来看看官方是如何介绍的:

https://docs.spring.io/spring-boot/index.html

![image-20250413160742206](./images/Spring Boot 3/image-20250413160742206.png)

![image-20250413160744834](./images/Spring Boot 3/image-20250413160744834.png)

Spring Boot倡导约定优于配置,将简化开发发挥到极致。使用Spring Boot框架可以快速构建Spring应用,再也不需要大量的繁琐的的各种配置。Spring Boot框架设计的目标是:程序员关注业务逻辑就行了,环境方面的事儿交给Spring Boot就行。

Spring Boot特性:

  1. 快速创建独立的Spring应用程序。(Spring支持的SpringBoot都支持,也就是说SpringBoot全方位支持IoC,AOP等)
  2. 嵌入式的Tomcat、Jetty、Undertow容器。(web服务器本身就是几个jar包,Spring Boot框架自动嵌入了。)
  3. 需要什么功能时只需要引入对应的starter启动器即可。(启动器可以自动管理这个功能相关的依赖,自动管理依赖版本的控制)
  4. 尽最大努力,最大可能的自动配置Spring应用和第三方库。(例如:如果要进行事务的控制,不用做任何事务相关的配置,只需要在service类上添加@Transactional注解即可。)
  5. 没有代码生成,没有XML配置。(Spring Boot的应用程序在启动后不会动态地创建新的Java类,所有逻辑都是在编译期就已经确定好的)
  6. 提供了生产监控的支持,例如健康检查,度量信息,跟踪信息,审计信息等。也支持集成外部监控系统。

Spring Boot的开箱即用和约定优于配置:

  • 开箱即用:Spring Boot框架设计得非常便捷,开发者能够在几乎不需要任何复杂的配置的情况下,快速搭建并运行一个功能完备的Spring应用。
  • 约定优于配置:“约定优于配置”(Convention Over Configuration, CoC)是一种软件设计哲学,核心思想是通过提供一组合理的默认行为来减少配置的数量,从而简化开发流程。例如:Spring Boot默认约定了使用某个事务管理器,在事务方面不需要做任何配置,只需要在对应代码的位置上使用@Transactional注解即可。

知识储备

学习本套课程的前提是你已经学过了:

  • MyBatis
  • Spring
  • SpringMVC

主要版本说明

Spring Boot版本:3.3.5

IDEA版本:2024.2.4

JDK版本:21(Spring Boot3要求JDK的最低版本是JDK17)

First Spring Boot

需求:在浏览器上输入请求路径 http://localhost:8080/hello,在浏览器上显示 HelloWorld!

使用**Spring Boot3 开发web应用**,实现步骤如下:

第一步:创建一个空的工程,并设置JDK版本21(Spring Boot 3要求JDK最低版本是17)

![image-20250413163139334](./images/Spring Boot 3/image-20250413163139334.png)

设置jdk21

![image-20250413163147002](./images/Spring Boot 3/image-20250413163147002.png)

第二步:设置maven

![image-20250414180440476](./images/Spring Boot 3/image-20250414180440476.png)

第三步:创建一个Maven模块 sb3-01-first-web

![image-20250414180717296](./images/Spring Boot 3/image-20250414180717296.png)

第四步:打开Spring Boot 3官方文档,按照文档一步一步进行

![image-20250414180926480](./images/Spring Boot 3/image-20250414180926480.png)

![image-20250414180931149](./images/Spring Boot 3/image-20250414180931149.png)

第五步:要使用Spring Boot 3,需要继承这个开源项目。从官方指导文档中复制以下内容:

![image-20250414180949569](./images/Spring Boot 3/image-20250414180949569.png)

1
2
3
4
5
6
7
8
<!--继承Spring Boot 3.3.3开源项目-->
<!--使用SpringBoot框架,首先要继承SpringBoot这个父工程。-->
<!--这里为什么不是直接引入springboot依赖,而是继承SpringBoot父工程的方式??????-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.4</version>
</parent>

我们开发的每一个项目其实可以看做是 Spring Boot 项目下的子项目。

第六步:添加Spring Boot的web starter

![image-20250414181710087](./images/Spring Boot 3/image-20250414181710087.png)

这个也就是web启动器,在parent下立即添加如下配置,让Spring Boot项目具备开发web应用的依赖:

1
2
3
4
5
6
7
8
 <!--依赖-->
<dependencies>
<!--引入web启动器,这样的话自动会将web开发相关的所有依赖全部引入,例如:json、tomcat、springmvc等,包括这些依赖的版本也不需要我们管理,自动管理。这里的版本号也在父工程管理好了-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>

关联的依赖也被引入进来,如下:

![image-20250414182237867](./images/Spring Boot 3/image-20250414182237867.png)

可以看到spring mvc被引入了,tomcat服务器也被引入了。

第七步:编写Spring Boot主入口程序

新建下面这个

![image-20250414182626673](./images/Spring Boot 3/image-20250414182626673.png)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.powernode.springboot;

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

// 所有的springboot应用的主入口程序必须使用 @SpringBootApplication 注解进行标注。
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
// 主入口,运行main方法就是启动服务器
//需要两个参数,一个是主入口类,一个是主入口里面的这个参数比如args
SpringApplication.run(MyApplication.class,args);
}
}

第八步:编写controller

必须要放在主入口程序同级目录下或者子目录里

1
2
3
4
5
6
7
8
9
10
11
12
13
// 提醒:这个HelloController要想被spring容器管理,这个类所在的位置必须满足:和SpringBoot主入口程序在同级目录下,或者子目录下。
//@Controller
@RestController
public class HelloController {

//发送一个get请求,后面是请求路径,然后返回字符串到浏览器上,就要加一个@ResponseBody
// 也可以不加,就需要把Controller换成RestController,既起到了Controller作用又给这个Controller所有的方法都加上ResponseBody注解
@GetMapping("/hello")
//@ResponseBody
public String hello(){
return "Hello, SpringBoot3!";
}
}

第九步:运行main方法就是启动web容器

![image-20250414184053516](./images/Spring Boot 3/image-20250414184053516.png)

第十步:打开浏览器访问

![image-20250414184101339](./images/Spring Boot 3/image-20250414184101339.png)

便捷的部署方式

打jar包运行

Spring Boot提供了打包插件,可以将Spring Boot项目打包为**可执行 jar 包**。Web服务器(Tomcat)也会连同一块打入jar包中。只要电脑上安装了Java的运行环境(JDK),就可以启动Spring Boot项目。但注意不要去占用8080端口

![image-20250414184720123](./images/Spring Boot 3/image-20250414184720123.png)

根据官方文档指导,使用打包功能需要引入以下的插件:

1
2
3
4
5
6
7
8
9
<!--这是一个能够创建可执行jar包的springboot插件。-->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

执行打包命令,生成可执行jar包:

![image-20250414185231753](./images/Spring Boot 3/image-20250414185231753.png)

![image-20250414185235074](./images/Spring Boot 3/image-20250414185235074.png)

![image-20250414185239632](./images/Spring Boot 3/image-20250414185239632.png)

单独的将这个 jar 包可以拷贝到任何位置运行,通过java -jar sb3-01-first-web-1.0-SNAPSHOT.jar命令来启动 Spring Boot 项目:

![image-20250414185249149](./images/Spring Boot 3/image-20250414185249149.png)

另外,Spring Boot框架为我们提供了非常灵活的配置,在可执行jar包的同级目录下新建配置文件:application.properties,并配置以下信息:

1
server.port=8888

重新启动服务器,然后使用新的端口号访问:

![image-20250414185311030](./images/Spring Boot 3/image-20250414185311030.png)

SpringBoot的jar包和普通jar包的区别(面试题)

Spring Boot 打包成的 JAR 文件与传统的 Java 应用程序中的 JAR 文件相比确实有一些显著的区别,主要体现在依赖管理可执行性上。

依赖管理

  • Spring Boot 的 JAR 包通常包含了应用程序运行所需的所有依赖项,也就是说它是一个“fat jar”(胖 JAR 包),这种打包方式使得应用可以独立运行,而不需要外部的类路径或应用服务器上的其他依赖。
  • 普通的 JAR 文件一般只包含一个类库的功能,并且需要依赖于特定的类路径来找到其他的类库或者框架,这些依赖项通常在部署环境中已经存在,比如在一个应用服务器中。

可执行性

  • Spring Boot 的 JAR 文件可以通过直接执行这个 JAR 文件来启动应用程序,也就是说它是一个可执行的 JAR 文件。通过 java -jar your-application.jar 命令就可以直接运行应用程序。
  • 而普通的 JAR 文件通常是不可直接执行的,需要通过指定主类(main class)的方式或者其他方式来启动一个应用程序,例如使用 -cp-classpath 加上类路径以及主类名来执行。

Spring Boot 的这些特性使得部署和运行变得更加简单和方便,特别是在微服务架构中,每个服务都可以被打包成独立的 JAR 文件并部署到任何支持 Java 的地方。

SpringBoot的可执行jar包目录结构:

![image-20250414194631521](./images/Spring Boot 3/image-20250414194631521.png)

普通jar包的目录结构:![image-20250414194638757](./images/Spring Boot 3/image-20250414194638757.png)

Spring Boot脚手架

什么是脚手架

建筑工程中的脚手架

在建筑工程领域,“脚手架”指的是临时性的结构,用于支撑建筑物以及建筑材料,同时为建筑工人提供工作平台。这种脚手架通常是由钢管、扣件、木板和其他配件组成的,可以根据施工需要搭建不同高度和形状的结构

![image-20250414194943558](./images/Spring Boot 3/image-20250414194943558.png)

软件开发中的脚手架

在软件开发领域,“脚手架”指的是用于快速创建项目基本结构的工具或模板。它帮助开发者初始化项目,设置必要的目录结构、文件模板以及依赖项。

Spring Boot脚手架

Spring Boot 脚手架(Scaffold)可以帮助开发者快速搭建一个Spring Boot项目结构,让开发者只专注于业务逻辑的开发,而不是在项目的初始阶段花费大量时间来配置环境或者解决依赖关系。

Spring Boot 脚手架工具存在多种形式,以下是一些常见的 Spring Boot 脚手架工具和方法:

  • Spring Initializr:

这是 Spring 官方提供的工具,可以在 https://start.spring.io 上找到。它允许开发者选择所需的依赖、Java 版本、构建工具(Maven 或 Gradle)以及其他配置选项来生成一个新的 Spring Boot 项目。

  • IntelliJ IDEA 内置支持:

IntelliJ IDEA 集成了 Spring Initializr 的功能,可以在 IDE 内直接创建 Spring Boot 项目。

  • Start Alibaba Cloud:

阿里云提供的 Start Alibaba Cloud 增强版工具,除了基本的 Spring Boot 模块外,还集成了阿里云服务和中间件的支持。

  • JHipster:

JHipster 是一个流行的脚手架工具,用于生成完整的 Spring Boot 应用程序,包括前端(Angular, React 或 Vue.js)和后端。它还包括用户管理和认证等功能。

  • Yeoman Generators:

Yeoman 是一个通用的脚手架工具,它有一个庞大的插件生态系统,其中包括用于生成 Spring Boot 项目的插件。

  • Bootify:

Bootify 是另一个用于生成 Spring Boot 应用程序的脚手架工具,提供了一些预定义的应用模板。

  • Spring Boot CLI:

Spring Boot CLI 是一个命令行工具,允许用户通过命令行来编写和运行 Spring Boot 应用。

  • Visual Studio Code 插件:

Visual Studio Code 社区提供了多个插件,如 Spring Boot Extension Pack,可以帮助开发者生成 Spring Boot 项目的基本结构。

  • GitHub Gist 和 Bitbucket Templates:

在 GitHub 和 Bitbucket 上,有很多开发者分享了用于生成 Spring Boot 项目的脚本或模板。

  • 自定义脚手架:

很多开发者也会根据自己的需求定制自己的脚手架工具,比如使用 Bash 脚本、Gradle 或 Maven 插件等。

使用官方提供的

使用官方脚手架生成Spring Boot项目

Spring Initializr:https://start.spring.io

![image-20250414195328832](./images/Spring Boot 3/image-20250414195328832.png)

点击“GENERATE”后,生成zip压缩包

将其解压后的目录结构是一个标准的maven 工程:![image-20250414195343809](./images/Spring Boot 3/image-20250414195343809.png)

将项目放到IDEA当中

直接将解压后的sb3-02-use-spring-initializr拷贝到我们新建的空工程SpringBoot3下,然后重启idea就行了

![image-20250414213321678](./images/Spring Boot 3/image-20250414213321678.png)

注意:如果pom.xml文件的图标颜色不是蓝色,而是橘色,需要在pom.xml文件上右键,选择:add as maven project。这样pom.xml文件的图标就会变为蓝色了。

脚手架生成的pom.xml文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!--继承Spring Boot父工程-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<!--自己项目的坐标-->
<groupId>com.powernode</groupId>
<artifactId>sb3-02-use-spring-initializr</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>sb3-02-use-spring-initializr</name>
<description>使用spring官方提供的脚手架构建springboot项目</description>
<url/>
<licenses>
<license/>
</licenses>
<developers>
<developer/>
</developers>
<scm>
<connection/>
<developerConnection/>
<tag/>
<url/>
</scm>
<properties>
<java.version>21</java.version>
</properties>
<dependencies>
<!--web起步依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--单元测试起步依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<!--打包插件-->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>

脚手架生成的Spring Boot项目的结构

![image-20250415010746176](./images/Spring Boot 3/image-20250415010746176.png)

src下面就是程序的入口,java的这个位置是类的根路径,resources同样也是类的根路径,放在这里面相当于java的com同级,只不过java放的是源文件,resources放的是配置文件,static主要放静态的,templates放动态网页的模板文件

application.properties是我们主要配置的文件,是springboot默认的配置文件,

编写controller并测试

新建controller包,并新建HelloController类,如下图:

![image-20250415011419320](./images/Spring Boot 3/image-20250415011419320.png)

重点:默认情况下,SpringBoot项目只扫描主入口程序所在目录以及子目录,因此创建的Controller类要求放在主入口程序的同级目录下或子目录下。其他位置默认情况下扫描不到。

启动应用并访问:

![image-20250415011446207](./images/Spring Boot 3/image-20250415011446207.png)

使用阿里提供的

阿里巴巴提供的 Spring Boot 项目脚手架服务称为 DragonBoot(也被称为 Alibaba Cloud Spring Boot Initializr)。DragonBoot 基于 Spring Initializr,并在此基础上增加了更多的定制选项,特别是针对阿里巴巴云服务和中间件的支持。脚手架地址:https://start.aliyun.com/

当下(2024年)阿里云提供的脚手架使用的版本较低,国内有一些公司在用。如果要求版本较高的,则阿里云脚手架不适用。

![image-20250415013052683](./images/Spring Boot 3/image-20250415013052683.png)

使用IDEA工具的脚手架插件

IDEA工具自带了Spring Boot脚手架的插件,使用它会更加的方便,让我们来操作一下:

![image-20250415013647170](./images/Spring Boot 3/image-20250415013647170.png)

![image-20250415013812077](./images/Spring Boot 3/image-20250415013812077.png)

第2章 Spring Boot核心机制

为何以继承方式引入SpringBoot

提出疑问

以前我们在开发项目时,需要什么,引入对应的依赖就行,比如我们需要连接mysql数据,则引入mysql驱动的依赖,如下:

1
2
3
4
5
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.3.0</version>
</dependency>

现在我们要使用SpringBoot框架,按说也应该采用依赖的方式将SpringBoot框架引入,如下:

1
2
3
4
5
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.3</version>
</dependency>

但是SpringBoot官方推荐的不是直接引入依赖,而是采用继承的方式实现,如下:

1
2
3
4
5
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.3</version>
</parent>

为什么?

作为父项目和作为依赖的区别

继承父工程的优势

  • 依赖管理:可以在父工程中定义依赖的版本,子模块可以直接引用而不必指定版本号,让依赖管理更统一。
  • 插件管理:可以在父工程中配置常用的插件及其版本,子模块可以直接使用这些配置。
  • 属性设置:可以在父工程中定义一些通用的属性,如项目编码、Java 版本等。
  • 统一配置:可以统一多个子模块的构建配置,确保一致性。

直接引入依赖的局限性(如果你不使用继承父工程的方式,而是通过直接引入依赖的方式来管理项目,那么你将失去上述的一些优势)

  • 依赖版本管理:每个子模块都需要单独指定依赖的版本,这会导致大量的重复配置,并且难以维护。
  • 插件配置:每个子模块都需要单独配置插件及其版本,无法共享父工程中的插件配置。
  • 属性设置:每个子模块都需要单独设置通用的属性,如项目编码、Java 版本等。
  • 构建配置:每个子模块的构建配置需要单独维护,难以保证一致性。

总结:选择哪种方式取决于你的具体需求。

  • 如果你希望多个项目之间共享构建配置,那么使用父项目是一个好的选择;
  • 如果你只是想在项目之间共享代码,那么应该使用依赖关系。

原理揭晓

通过源码来分析一下:

![image-20250415015255630](./images/Spring Boot 3/image-20250415015255630.png)

![image-20250415015258610](./images/Spring Boot 3/image-20250415015258610.png)

![image-20250415015301233](./images/Spring Boot 3/image-20250415015301233.png)

通过上图源码可以看到Spring Boot预先对开发中需要用到的依赖进行了版本的统一管理。我们需要和SpringBoot框架共享这个构建配置。因此官方推荐使用继承的方式引入SpringBoot框架。

依赖统一管理的好处

Spring Boot 框架的一个重要特性就是简化了项目依赖管理。它通过提供一个叫做“依赖管理”的功能来帮助开发者更容易地管理和使用第三方库和其他 Spring 组件。具体来说,Spring Boot 提供了一个包含多个 Spring 和其他常用库的依赖版本配置文件(通常是在 spring-boot-dependencies 文件中),这使得开发者不需要在自己的项目中显式指定这些依赖的版本号。

这样做有以下几个好处:

  1. 简化依赖声明
    开发者只需要在 pom.xml 文件中声明需要的依赖而不需要指定其版本号,因为 Spring Boot 已经为这些依赖指定了版本。例如,如果你需要使用mysql驱动,你只需要添加相应的依赖声明而不需要关心版本。
1
2
3
4
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
  1. 避免版本冲突
    当多个库之间存在依赖关系的时候,如果手动管理版本可能会导致版本之间的冲突(即“依赖地狱”)。Spring Boot 提供的统一版本管理可以减少这种冲突的可能性。
  2. 易于升级
    当 Spring Boot 发布新版本时,通常会更新其依赖库到最新稳定版。因此,当你升级 Spring Boot 版本时,它所管理的所有依赖也会随之更新到兼容的版本。
  3. 减少配置错误
    由于 Spring Boot 自动处理了依赖的版本,减少了手动输入版本号可能引入的拼写或格式错误。
  4. 提高开发效率
    开发者可以专注于业务逻辑的编写,而不是花费时间在解决依赖问题上。

总的来说,Spring Boot 的依赖管理功能使得开发者可以更加专注于业务逻辑的实现,同时减少了因依赖版本不一致而引发的问题,提高了项目的可维护性和开发效率。

当然,如果你在项目中需要更改某个依赖的版本号,不想使用SpringBoot框架指定的版本号,只需要在引入依赖时强行执行版本号即可,maven是支持就近原则的:

这样做就是采用SpringBoot指定版本的依赖:

1
2
3
4
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>

![image-20250415153723118](./images/Spring Boot 3/image-20250415153723118.png)

这样做就是不采用SpringBoot指定版本的依赖:

1
2
3
4
5
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.2.0</version>
</dependency>

![image-20250415153728868](./images/Spring Boot 3/image-20250415153728868.png)

Starter-启动器

在 Spring Boot 中,启动器(Starter)本质上是一个简化依赖管理的概念。

Spring Boot 的启动器本质上就是一组预定义的依赖集合,它们被组织成一个个 Maven的依赖,以方便开发者快速集成特定的功能模块。

如果你想做web开发,只需要引入web启动器。web启动器会自动引入web开发所需要的子依赖。

启动器实现原理

  1. 依赖聚合
    每个启动器通常对应一个特定的功能集或者一个完整的应用模块,如 spring-boot-starter-web 就包含了构建 Web 应用所需的所有基本依赖项,如 Spring MVC, Tomcat 嵌入式容器等。
  2. 依赖传递
    当你在项目中引入一个启动器时,它不仅会把自身作为依赖加入到你的项目中,还会把它的所有直接依赖项(transitive dependencies)也加入进来。这意味着你不需要单独声明这些依赖项,它们会自动成为项目的一部分。
  3. 版本管理
    启动器内部已经指定了所有依赖项的具体版本,这些版本信息存储在一个公共的 BOM(Bill of Materials,物料清单)文件中,通常是 spring-boot-dependencies。当引入启动器时,实际上也间接引用了这个 BOM,从而确保了所有依赖项版本的一致性。
  4. 自动配置
    许多启动器还提供了自动配置(Auto-configuration),这是一种机制,允许 Spring Boot 根据类路径上的可用组件自动设置你的应用程序。例如,如果类路径上有 Spring MVC 和嵌入式 Tomcat,则 Spring Boot 会自动配置它们,并准备好一个 web 应用程序。

使用启动器的示例

假设你想创建一个基于 Spring MVC 的 RESTful Web 应用,你可以简单地将 spring-boot-starter-web 添加到你的项目中:

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

当你添加这个依赖时,Spring Boot 会处理所有必要的细节,包括添加 Spring MVC 和 Tomcat 作为嵌入式 Servlet 容器,并且根据类路径上的内容进行适当的自动配置。如下图所示:

![image-20250415162234484](./images/Spring Boot 3/image-20250415162234484.png)

都有哪些启动器

启动器通常包括:

  • SpringBoot官方提供的启动器
  • 非官方提供的启动器

官方提供的启动器

启动器命名特点:spring-boot-starter-*

![image-20250415162322902](./images/Spring Boot 3/image-20250415162322902.png)

非官方的启动器

启动器命名特点:*-spring-boot-starter

![image-20250415162331627](./images/Spring Boot 3/image-20250415162331627.png)

Spring Boot核心注解

如果加入了mybatis启动器,就必须配置数据源,否则报错

@SpringBootApplication注解

Spring Boot的主入口程序被@SpringBootApplication注解标注,可见这个注解的重要性,查看它的源码:

![image-20250416005803306](./images/Spring Boot 3/image-20250416005803306.png)

  1. 当前类被 @SpringBootApplication 标注,被该注解标注的类是springboot项目的入口类。
  2. 该类的main方法就是整个springboot项目的入口。
  3. 关于 @SpringBootApplication 注解:
    从源码角度来看,该注解被以下三个注解标注了:
    @SpringBootConfiguration
    @EnableAutoConfiguration
    @ComponentScan
    也就是说 @SpringBootApplication 注解是一个复合注解,同时拥有以上三个注解的功能。

/@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@ComponentScan.Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @ComponentScan.Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)把这些注解直接复制到这里也可以代替@SpringBootApplication
/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@SpringBootApplication
public class Springboot304CoreAnnotationApplication {

// 从这里开始。
// SpringApplication 【spring应用程序】
// run 【运行】
// run方法的第一个参数其实就是配置类,对应的就是以前的配置文件
// springboot应用程序就是从这个配置类开始,加载所有的bean的。
// Springboot304CoreAnnotationApplication.class 又被称为源【起源】
// args 可以传递命令行参数到这个数组上
public static void main(String[] args) {
SpringApplication.run(Springboot304CoreAnnotationApplication.class, args);
}
}

关于 @SpringBootConfiguration 注解:

从源码角度来看,该注解被 @Configuration 注解标注,被该注解标注的类统一是配置类,代替以前的配置。
因此得出一个结论:springboot项目的主入口类同时又是一个配置类。
因此在springboot主入口配置类当中使用 @Bean 注解标注方法的话,该方法的返回值对象应该会被纳入IoC容器的管理。

1
2
3
4
5
6
7
8
9
10
11
@SpringBootApplication
public class Springboot304CoreAnnotationApplication {
@Bean//被bean这个注解标注的返回值将来会自动纳入IoC容器的管理,前提是这个类必须是配置类
public Date getDate(){
return new Date();
}
public static void main(String[] args) {
SpringApplication.run(Springboot304CoreAnnotationApplication.class, args);
}
}

再写一个Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
@RestController
public class UserController {

// 从spring的IoC容器中查找Date对象,如果有的话会自动注入到这里。
@Autowired//自动注入
private Date date;

@GetMapping("/hello")
public String hello(){
return date.toString();//通过toString打印到浏览器上
}
}

就可以通过浏览器接收到date了

关于 @EnableAutoConfiguration 注解:

该注解表示启用自动配置。
也就是说默认情况下,springboot应用都会默认启用自动配置。
自动配置有什么用?
所谓的自动配置只要启动,springboot应用会去类路径当中查找class,根据类路径当中有某个类,或某些类,来自动管理bean,不需要我们程序员手动配置。
比如:springboot检测到类路径当中有 SqlSessionFactory,或者在application.properties文件中配置了数据源,那么springboot会认为项目中
有mybatis框架,因此会将mybatis中相关的bean自动初始化,然后放到IoC容器当中,自动将这些bean管理起来。

  • SqlSessionFactory: MyBatis的核心工厂SqlSessionFactory会被自动配置。这个工厂负责创建SqlSession实例,后者用来执行映射文件中的SQL语句。

  • TransactionManager: DataSourceTransactionManager会被自动配置来管理与数据源相关的事务。

    要先在pom里配上mybatis依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <!--mybatis启动器-->
    <dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>3.0.4</version>
    </dependency>

    <!--添加mysql驱动依赖-->
    <dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    </dependency>

    然后在application里配置数据源

    1
    2
    3
    4
    5
    spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver//如果爆红就需要添加mysql依赖
    spring.datasource.url=jdbc:mysql://localhost:3306/springboot
    spring.datasource.username=root
    spring.datasource.password=liuqi3711022
    spring.datasource.type=com.zaxxer.hikari.HikariDataSource
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    @SpringBootApplication
    public class Springboot304CoreAnnotationApplication {
    public static void main(String[] args) {
    // 通过 run方法的返回值是可以获取到 Spring 上下文对象的(也就是.var的那个)。
    // ConfigurableApplicationContext 继承了 ApplicationContext
    // 因此 run方法的返回值就是spring容器。
    ConfigurableApplicationContext applicationContext = SpringApplication.run(Springboot304CoreAnnotationApplication.class, args);

    //通过bean的name获取bean,就都能检测到
    Object sqlSessionFactory = applicationContext.getBean("sqlSessionFactory");
    System.out.println(sqlSessionFactory);

    Object transactionManager = applicationContext.getBean("transactionManager");
    System.out.println(transactionManager);

    //关闭容器
    applicationContext.close();
    }
    }

关于 @ComponentScan 注解:

负责组件扫描的。代替的xml配置是:

1
<context:component-scan base-packages="com.powernode.springboot" />

这个注解出现在springboot主入口类上,因此组件扫描默认扫描的包是主入口程序所在的包以及该包下的所有子包。

要让bean纳入IoC容器的管理,必须将类放到主入口程序同级目录下,或者子目录下。

Spring Boot的单元测试(没看懂)

controller属于表示层或者叫web层

不使用单元测试怎么调用service

创建模块

使用脚手架创建springboot3-05-junit模块,不添加任何启动器

编写service

![image-20250417024457620](./images/Spring Boot 3/image-20250417024457620.png)

1
2
3
4
5
6
7
@Service("userService")//给起个名字叫userService
public class UserServiceImpl implements UserService {
@Override
public User findUser() {
return new User("jackson", "123456");
}
}
1
2
3
4
public interface UserService {
//提供一个能返回user的方法
User findUser();
}

直接在入口程序中调用service

1
2
3
4
5
6
7
8
9
10
11
@SpringBootApplication
public class Springboot305JunitApplication {
public static void main(String[] args) {
//在不使用junit的前提下,怎么调用Service方法
ConfigurableApplicationContext applicationContext = SpringApplication.run(Springboot305JunitApplication.class, args);
UserService userService = applicationContext.getBean("userService", UserService.class);//userService就是@Service的名字,这个返回userService
User user = userService.findUser();
System.out.println(user);
applicationContext.close();
}
}

执行结果:

![image-20250417024804850](./images/Spring Boot 3/image-20250417024804850.png)

这种方式就是手动获取Spring上下文对象ConfigurableApplicationContext,然后调用getBean方法从Spring容器中获取service对象,然后调用方法。

使用单元测试怎么调用service

test-starter引入以及测试类编写

使用单元测试应该如何调用service对象上的方法呢?

在使用脚手架创建Spring Boot项目时,为我们生成了单元测试类,如下:

![image-20250417025327260](./images/Spring Boot 3/image-20250417025327260.png)

![image-20250417025726542](./images/Spring Boot 3/image-20250417025726542.png)

当然,如果要使用单元测试,需要引入单元测试启动器,如果使用脚手架创建SpringBoot项目,这个test启动器会自动引入:

1
2
3
4
5
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

@SpringBootTest注解

@SpringBootTest 会创建一个完整的 Spring 应用程序上下文(Application Context),这个上下文包含了应用程序的所有组件和服务。以下是 @SpringBootTest 做的一些主要工作:

  1. 创建 ApplicationContext
    • @SpringBootTest 使用 SpringApplicationrun() 方法来启动一个 Spring Boot 应用程序上下文。这意味着它会加载应用程序的主配置类和其他相关的配置类。
  2. 加载配置文件
    • 它会查找并加载默认的配置文件,如 application.properties
  3. 自动配置
    • 如果应用程序依赖于 Spring Boot 的自动配置特性,@SpringBootTest 会确保这些自动配置生效。这意味着它会根据可用的类和bean来自动配置一些组件,如数据库连接、消息队列等。
  4. 注入依赖
    • 使用 @SpringBootTest 创建的应用程序上下文允许你在测试类中使用 @Autowired 注入需要的 bean,就像在一个真实的 Spring Boot 应用程序中一样。

总的来说,@SpringBootTest 为你的测试提供了尽可能接近实际运行时环境的条件,这对于验证应用程序的行为非常有用。

注入service并调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// springboot项目中使用单元测试junit,那么单元测试类必须使用这个注解进行标注。
@SpringBootTest
class Springboot305JunitApplicationTests {

@Autowired
private UserService userService;

// 单元测试方法,单元测试方法使用 @Test 注解标注。
@Test
void test01() {
User user = userService.findUser();
System.out.println(user);
}
}

外部化配置

什么是外部化配置

外部化配置是指:将配置信息存储在应用程序代码之外的地方。这样配置信息可以独立于代码进行管理。这样方便了配置的修改,并且修改后不需要重新编译代码,也不需要重新部署项目。

在springboot应用程序默认是先找外部化配置

外部化配置的方式

SpringBoot支持多种外部化配置方式,包括但不限于:

  • properties文件
  • YAML文件
  • 系统环境变量
  • 命令行参数
  • ……

外部化配置的优势

  1. 灵活性:配置文件可以独立于应用程序部署,这使得可以根据运行环境的不同来调整配置,而无需修改代码。
  2. 易于维护:配置变更不需要重新构建和部署应用程序,降低了维护成本。
  3. 安全性:敏感信息如数据库密码、API密钥等可以存储在外部,并且可以限制谁有权限访问这些配置信息。
  4. 共享性:多实例或多服务可以共享相同的配置信息,减少重复配置的工作量。
  5. 版本控制:配置文件可以存放在版本控制系统中,便于跟踪历史版本和回滚配置。

总之,外部化配置使得配置更加灵活、安全、易于管理和共享,是现代云原生应用中非常推荐的做法

外部化配置对比传统配置

在传统的SSM三大框架中,如果修改XML的配置后,需要对应用重新打包,重新部署。

使用SpringBoot框架的外部化配置后,修改配置后,不需要对应用重新打包,也不需要重新部署,最多重启一下服务即可。

application.properties

application.properties配置文件是SpringBoot框架默认的配置文件。

application.properties不是必须的,SpringBoot对于应用程序来说,都提供了一套默认配置(就是我们所说的自动配置)。

如果你要改变这些默认的行为,可以在application.properties文件中进行配置。

application.properties可以放在类路径当中,也可以放在项目之外。因此称为外部化配置。

![image-20250417031216205](./images/Spring Boot 3/image-20250417031216205.png) 类的根路径calsspath

Spring Boot 框架在启动时会尝试从以下位置加载 application.properties 配置文件(优先级从高到低):

  1. file:./config/(在jar包外的):首先在Spring Boot 当前工作目录下的 config 文件夹中查找。
    1. 注意:如果没有找到application.properties会继续找application.yml,如果这两个都没有找到,才会进入以下位置查找,以此类推。
  2. file:./:如果在当前工作目录下config目录中找不到时,再从当前工作目录中查找,找不到properties就再找yml。
  3. classpath:/config/:如果从工作目录中找不到,会从类路径中找,先从类路径的 /config/ 目录下寻找配置文件。
  4. classpath:/:如果在 /config/ 下没有找到,它会在类路径的根目录下查找。

Spring Boot 会按照这个顺序来加载配置文件,如果在多个位置有相同的属性定义,那么最先检查的位置中的属性值将优先使用。

如果你想要指定其他的配置文件位置或者改变默认的行为,可以通过 --spring.config.location= 后跟路径的方式来指定配置文件的具体位置。例如 :

1
java -jar sb3-01-first-web-1.0-SNAPSHOT.jar --spring.config.location=file:///E:\a\b\application.properties

这样,Spring Boot 将会首先从 E:\a\b\ 这个路径加载配置文件。注意,这种方式可以用来覆盖默认的配置文件位置,并且可以结合以上提到的位置一起使用。

注意:以上的--spring.config.location=file:///E:\a\b\application.properties就属于命令行参数,它将来会被传递到main方法的(String[] args)参数上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@SpringBootApplication
public class Springboot306PropertiesLocationApplication {

// args 就是接收命令行参数的。
public static void main(String[] args) {
// 打印命令行参数。
for (String arg : args) {
System.out.println(arg);
}

//这里我们就知道为什么命令行参数要作为参数传到run方法里,因为run方法需要通过命令行参数来决定配置文件的路径
ConfigurableApplicationContext applicationContext = SpringApplication.run(Springboot306PropertiesLocationApplication.class, args);
AppConfig appConfig = applicationContext.getBean(AppConfig.class);
appConfig.printInfo();//这是自定义的一个printInfo
applicationContext.close();
}
}

使用@Value注解

@Value注解可以将application.properties/application.yml文件中的配置信息注入/绑定到java对象的属性上。

语法格式:@Value(“${key}”)

使用脚手架创建SpringBoot项目,不添加任何启动器,在resources/application.properties文件中进行如下配置:

1
2
3
myapp.username=jackson
myapp.email=jackson@123.com
myapp.age=2

编写service类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Service
public class SystemService {
@Value("${myapp.username}")
private String username;
@Value("${myapp.email}")
private String email;

// 注意:当使用 @Value注解的时候,如果这个key不存在,并且没有指定默认值,则报错。50就是默认值
//@Value("${myapp.age}")
@Value("${myapp.age:50}")
private Integer age;
public void printInfo(){
System.out.println(username);
System.out.println(email);
System.out.println(age);
}
}

编写单元测试:

1
2
3
4
5
6
7
8
9
@SpringBootTest
class Springboot307ValueAnnotationApplicationTests {
@Autowired
private SystemService systemService;
@Test
void test01() {
systemService.printInfo();
}
}

运行结果:![image-20250418164855135](./images/Spring Boot 3/image-20250418164855135.png)

YAML

YAML概述

SpringBoot采用集中式配置管理,所有的配置都编写到一个配置文件中:application.properties

如果配置非常多,层级不够分明,因此SpringBoot为了提高配置文件可读性,也支持YAML格式的配置文件:application.yml

YAML(YAML Ain’t Markup Language)是一种人类可读的数据序列化格式,它通常用于配置文件,在各种编程语言中作为一种存储或传输数据的方式。YAML的设计目标是易于阅读和编写,同时保持足够的表达能力来表示复杂的数据结构。

YAML文件的扩展名可以是**<font style="color:#DF2A3F;">.yaml</font>****<font style="color:#DF2A3F;">.yml</font>**

常见的数据存储和交换格式

propertiesXMLJSONYAML这几种格式确实是用来存储和交换数据的常见方式,但它们各有特点和适用场景:

Properties

  • 这种格式主要用于Java应用程序中的配置文件。它是键值对的形式,每一行是一个键值对,使用等号或冒号分隔键和值。
  • 特点是简单易懂,但在处理复杂结构的数据时显得力不从心。

XML (eXtensible Markup Language)

  • XML是一种标记语言,用来描述数据的格式。它支持复杂的数据结构,包括嵌套和属性。
  • XML文档具有良好的结构化特性,适合传输和存储结构化的数据。但是,XML文档通常体积较大,解析起来也比较耗资源。

JSON (JavaScript Object Notation)

  • JSON是一种轻量级的数据交换格式,易于人阅读和编写,同时也易于机器解析和生成。它基于JavaScript的一个子集,支持多种数据类型,如数字、字符串、布尔值、数组和对象。
  • JSON因为简洁和高效而广泛应用于Web应用程序之间进行数据交换。

YAML (YAML Ain’t Markup Language)

  • YAML设计的目标之一就是让人类更容易阅读。它支持类似JSON的数据序列化,但提供了更多的灵活性,例如缩进来表示数据结构。
  • YAML非常适合用来编写配置文件,因为它允许以一种自然的方式组织数据,并且可以包含注释和其他人类可读的元素。

总结来说,这四种格式都可以用来存储和交换数据,但它们的设计初衷和最佳使用场景有所不同。选择哪种格式取决于具体的应用需求、数据复杂度、性能要求等因素。

![](./images/Spring Boot 3/1730804471502-31c64115-c49a-4d76-92e0-90e9ae543b32.png)

YAML的语法规则

YAML的语法规则如下:

  1. 数据结构:YAML支持多种数据类型,包括:
    1. 字符串、数字、布尔值
    2. 数组、list集合
    3. map键值对 等。
  2. YAML使用一个空格来分隔属性名属性值,例如:
    1. properties文件中这样的配置:name=jack
    2. yaml文件中需要这样配置:name: jack
  3. YAML用换行+空格来表示层级关系。注意不能使用tab,必须是空格,空格数量无要求,大部分建议2个或4个空格。例如:
    1. properties文件中这样的配置:myapp.name=mall
    2. yaml文件中就需要这样配置:
1
2
myapp:
name: mall
  1. 同级元素左对齐。例如:
    1. properties文件中有这样的配置:
1
2
myapp.name=mall
myapp.count=10
2. `yaml`文件中就应该这样配置:
1
2
3
myapp:
name: mall
count: 10
  1. 键必须是唯一的:在一个映射中,键必须是唯一的。
  2. 注释:使用#进行注释。
  3. 大小写敏感

YAML的使用小细节

第一:普通文本也可以使用单引号或双引号括起来:(当然普通文本也可以不使用单引号和双引号括起来。)

  • 单引号括起来:单引号内所有的内容都被当做普通文本,不转义(例如字符串中有\n,则\n被当做普通的字符串)
  • 双引号括起来:双引号中有 \n 则会被转义为换行符

第二:保留文本格式

  • | 将文本写到这个符号的下层,会自动保留格式。

    1
    2
    3
    4
    5
    myapp:
    username: |
    aaaa
    bbbb
    cccc

第三:文档切割

  • — 这个符号下面的配置可以认为是一个独立的yaml文件。便于庞大文件的阅读。

配置文件合并

一个项目中所有的配置全部编写到application.properties文件中,会导致配置臃肿,不易维护,有时我们会将配置编写到不同的文件中,例如:application-mysql.properties专门配置mysql的信息,application-redis.properties专门配置redis的信息,最终将两个配置文件合并到一个配置文件中。

properties文件

配置文件里有中文的话编码格式会变成![image-20250419015101834](./images/Spring Boot 3/image-20250419015101834.png) ,需要在settings里有个File encoding,

![image-20250419015258428](./images/Spring Boot 3/image-20250419015258428.png)

application-mysql.properties

1
2
spring.datasource.username=root
spring.datasource.password=123456

application-redis.properties

1
2
spring.data.redis.host=localhost
spring.data.redis.port=6379

application.properties

1
spring.config.import=classpath:application-mysql.properties,classpath:application-redis.properties

编写service测试,看看能否拿到配置信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Configuration
public class AppConfig {
@Value("${spr}")
private String mysqlUsername;
@Value("${spring.datasource.password}")
private String mysqlPassword;
@Value("${spring.data.redis.host}")
private String redisHost;
@Value("${spring.data.redis.port}")
private String redisPort;

public void printInfo(){
System.out.println(mysqlUsername + "," + mysqlPassword + "," + redisHost + "," + redisPort);
}
}

yaml文件

application-mysql.yml

1
2
3
4
spring:
datasource:
username: rootyml
password: 123456yml

application-redis.yml

1
2
3
4
5
spring:
data:
redis:
host: localhostyml
port: 6379

application.yml

1
2
3
4
5
6
7
spring:
config:
import:
#数组或者list集合可以这么写
- classpath:/config/application-mysql.yml
- classpath:/config/application-redis.yml
# import: [classpath:/config/application-mysql.yml, classpath:/config/application-redis.yml]

多环境切换

在Spring Boot中,多环境切换是指在一个应用程序中支持多种运行环境配置的能力。这通常用于区分开发(development)、测试(testing)、预生产(staging)和生产(production)等不同阶段的环境。

这种功能使得开发者能够在不同的环境中使用不同的配置,比如数据库连接信息、服务器端口、环境变量等,而不需要更改代码。这对于维护一个可移植且易于管理的应用程序非常重要。

  1. 开发环境的配置文件名一般叫做:application-dev.properties
1
2
3
spring.datasource.username=dev
spring.datasource.password=dev123
spring.datasource.url=jdbc:mysql://localhost:3306/dev
  1. 测试环境的配置文件名一般叫做:application-test.properties
1
2
3
spring.datasource.username=test
spring.datasource.password=test123
spring.datasource.url=jdbc:mysql://localhost:3306/test
  1. 预生产环境的配置文件名一般叫做:application-preprod.properties
1
2
3
spring.datasource.username=preprod
spring.datasource.password=preprod123
spring.datasource.url=jdbc:mysql://localhost:3306/preprod
  1. 生产环境的配置文件名一般叫做:application-prod.properties
1
2
3
spring.datasource.username=prod
spring.datasource.password=prod123
spring.datasource.url=jdbc:mysql://localhost:3306/prod

然后添加下面的东西

如果你希望该项目使用生产环境的配置,你可以这样做:

  • 第一种方式:在application.properties文件中添加这个配置:spring.profiles.active=prod,这个prod就是用来区分环境的,测试环境就换成test
  • 第二种方式:在命令行参数上添加:–spring.profiles.active=prod

将配置绑定到bean

绑定简单bean

SpringBoot配置文件中的信息除了可以使用@Value注解读取之外,也可以将配置信息一次性赋值给Bean对象的属性。

例如有这样的配置:

1
2
3
4
5
6
myapp:
a:
username: jacksonyml
password: 123456yml
age: 30
gender: false
1
2
3
4
5
# 将这里的配置信息一次性的绑定到bean对象的属性上。比 @Value 注解好用,方便。
myapp.a.username=jackson
myapp.a.password=123456
myapp.a.age=20
myapp.a.gender=true

Bean需要这样定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 纳入IoC容器的管理。
//@Component
// 这个注解标注的类一定会被纳入IoC容器的管理,同时这个类也表示是一个配置类。
@Configuration
// 将配置文件中的属性值一次性的绑定到bean对象的属性上。
@ConfigurationProperties(prefix = "myapp.a")//这里给了前缀名,要实现这种一次性的绑定功能,配置文件中的属性名和bean对象的属性名要一致。
public class AppConfig {
private String username;
private String password;
private Integer age;
private Boolean gender;

@Override
public String toString() {
return "AppConfig{" +
"username='" + username + '\'' +
", password='" + password + '\'' +
", age=" + age +
", gender=" + gender +
'}';
}

public void setUsername(String username) {
this.username = username;
}

public void setPassword(String password) {
this.password = password;
}

public void setAge(Integer age) {
this.age = age;
}

public void setGender(Boolean gender) {
this.gender = gender;
}
}
  1. 被绑定的bean,需要使用@ConfigurationProperties(prefix = "app")注解进行标注,prefix用来指定前缀,哪个是前缀
  2. 配置文件中的nameageemail要和bean对象的属性名nameageemail对应上。(属性名相同)
  3. 并且bean中的所有属性都提供了setter方法。因为底层是通过setter方法给bean属性赋值的。
  4. 这样的bean需要使用@Component注解进行标注,纳入IoC容器的管理。@Component注解负责创建Bean对象,@ConfigurationProperties(prefix = "app")注解负责给bean对象的属性赋值。
  5. bean的属性需要是非static的属性。

我们把这个Bean对象的类名打印一下看看:

![](./images/Spring Boot 3/1729671124931-0ce60aa3-54bb-4a35-9b99-c846f86ad6b8.png)

可以发现底层实际上创建了AppBean的代理对象AppBean$$SpringCGLIB

生成代理对象会影响效率,这里我们不需要使用代理功能,可以通过以下配置来取消代理机制:

1
2
3
4
5
6
7
8
@Configuration(proxyBeanMethods = false)
@ConfigurationProperties(prefix = "app")
public class AppBean {
private String name;
private Integer age;
private String email;
//setter and getter
}

绑定嵌套bean

当一个Bean中嵌套了一个Bean,这种情况下可以将配置信息绑定到该Bean上吗?当然可以。

有这样的一个配置:

1
2
3
4
5
6
app:
xyz:
name: lucy_yml
addr:
city: TJ
street: NanKai

需要编写这样的两个Bean:

1
2
3
4
5
public class Address {
private String city;
private String street;
省略
}
1
2
3
4
5
6
7
@Component
@ConfigurationProperties(prefix = "app.xyz")
public class User {
private String name;
private Address addr;//嵌套bean,被一起加入了ioc容器
~~~省略
}

然后执行测试程序

@EnableConfigurationProperties与@ConfigurationPropertiesScan

AppBean纳入IoC容器的管理,之前我们说了两种方式:第一种是使用@Component,第二种是使用@Configuration。SpringBoot其实还提供了另外两种方式:

  • 第一种:@EnableConfigurationProperties
  • 第二种:@ConfigurationPropertiesScan

这两个注解都是标注在SpringBoot主入口程序上的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 将配置信息绑定到bean的第三种方式:在主入口程序上添加以下注解。
// 以下注解的作用是:启用将配置信息绑定到bean,以下代码的意思就是将配置信息绑定到User这个bean上。
@EnableConfigurationProperties({User.class})
//@EnableConfigurationProperties({User.class})//可以添加多个,底层的value是class数组

// 将配置信息绑定到bean的第四种方式:在主入口程序上添加以下注解。
//@ConfigurationPropertiesScan(basePackages = "com.powernode.springboot.bean")
@SpringBootApplication
public class Springboot310ConfigBindToBeanApplication {

public static void main(String[] args) {
SpringApplication.run(Springboot310ConfigBindToBeanApplication.class, args);
}
}

将配置赋值到Bean的Map/List/Array属性上

配置信息如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
app2.abc.names[0]=jack
app2.abc.names[1]=lucy

app2.abc.addrArray[0].city=BeiJing
app2.abc.addrArray[0].street=ChaoYang
app2.abc.addrArray[1].city=TianJin
app2.abc.addrArray[1].street=NanKai

app2.abc.addrList[0].city=BeiJing_List
app2.abc.addrList[0].street=ChaoYang_List
app2.abc.addrList[1].city=TianJin_List
app2.abc.addrList[1].street=NanKai_List

app2.abc.addrs.addr1.city=BeiJing_Map
app2.abc.addrs.addr1.street=ChaoYang_Map
app2.abc.addrs.addr2.city=TianJin_Map
app2.abc.addrs.addr2.street=NanKai_Map

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//@Component如果不加这个,就在主入口那边绑定bean
@ConfigurationProperties(prefix = "app2.abc")
public class AppBean {
// 数组:数组中元素是简单类型
private String[] names;
// 数组:数组中元素是bean
private Address[] addrArray;
// List集合:集合中的元素是bean
private List<Address> addrList;
// Map集合:String,Bean
private Map<String, Address> addrs;
后面就是set方法和toString方法了省略
}

提醒:记得入口程序使用@EnableConfigurationProperties({User.class, AppBean.class})进行标注。

yml配置信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
app2:
abc:
# names: [jack, lucy]可以这样写
names:
- tom2
- smith2

# addrArray: yml里面是区分大小写的,但是addrArray可以写成addr-array,在mybatis里有一样的自动解析驼峰
addr-array:
- city: BeiJing2
street: ChaoYang2
- city: TianJin2
street: NanKai2
# addrList:
addr-list:
- city: BeiJing_List2
street: ChaoYang_List2
- city: TianJin_List2
street: NanKai_List2
addrs:
addr1:
city: BeiJing_Map2
street: ChaoYang_Map2
addr2:
city: TianJin_Map2
street: NanKai_Map2

将配置绑定到第三方对象

将配置文件中的信息绑定到某个Bean对象上,如果这个Bean对象没有源码,是第三方库提供的,怎么办?

此时可以单独编写一个方法,在方法上使用以下两个注解进行标注:

  • @Bean
  • @ConfigurationProperties

比如借用上面的类Address.class,可以写一个方法返回这个类的对象然后纳入ioc容器

1
2
3
4
5
6
7
8
9
10
11
// 指定该类是一个配置类。
@Configuration
public class AppConfig2 {

// 假设Address是第三方库提供的类。我们使用以下方式可以完成配置到bean的属性的绑定。
@Bean // 纳入IoC容器的管理
@ConfigurationProperties(prefix = "other.abc") // 将配置文件中凡是以 other.abc 开头的配置数据绑定到Address对象的属性上。
public Address address(){
return new Address();
}
}

指定数据来源

之前所讲的内容是将Spring Boot框架默认的配置文件application.propertiesapplication.yml作为数据的来源绑定到Bean上。如果配置信息没有在默认的配置文件中呢?可以使用@PropertySource注解指定配置文件的位置,这个配置文件可以是.properties,也可以是.xml。这里重点掌握.properties即可。

resources目录下新建a目录,在a目录下新建b目录,b目录中新建group-info.properties文件,进行如下的配置:

1
2
3
group.name=IT
group.leader=LaoDu
group.count=20

定义Java类Group,然后进行注解标注:

1
2
3
4
5
6
7
8
9
10
@Configuration
@ConfigurationProperties(prefix = "group")
// 用这个注解来指定数据来源。
@PropertySource("classpath:/a/b/group-info.properties")
public class Group {
private String name;
private String leader;
private Integer count;
下面就是set和toSTring方法,省略
}

以下三个注解分别起到什么作用:

  • @Configuration:指定该类为配置类,纳入Spring容器的管理
  • @ConfigurationProperties(prefix = “group”):将配置文件中的值赋值给Bean对象的属性
  • @PropertySource(“classpath:a/b/group-info.properties”):指定额外的配置文件

@ImportResource注解

创建Bean的三种方式总结:

  • 第一种方式:编写applicationContext.xml文件,在该文件中注册Bean,Spring容器启动时实例化配置文件中的Bean对象。
  • 第二种方式:@Configuration注解结合@Bean注解。
  • 第三种方式:@Component、@Service、@Controller、@Repository等注解。

第二种和第三种我们都已经知道了。针对第一种方式,如果在SpringBoot框架中应该怎么实现呢?使用@ImportResource注解实现

定义一个普通的Java类:Person

1
2
3
4
5
public class Person {
private String name;
private int age;
下面省略set get tostring
}

resources目录下新建applicationContext.xml配置文件,添加:

![image-20250420152210414](./images/Spring Boot 3/image-20250420152210414.png)

1
2
3
4
<bean id="person" class="com.powernode.springboot.bean.Person">
<property name="name" value="jackson"/>
<property name="age" value="20"/>
</bean>

在SpringBoot主入口类上添加@ImportResource进行资源导入,这样applicationContext.xml文件中的Bean将会纳入IoC容器的管理:

1
2
@ImportResource("classpath:/applicationContext.xml")
public class Springboot310ConfigBindToBeanApplication {}

Environment

SpringBoot框架在启动的时候会将系统配置,环境信息全部封装到Environment对象中,如果要获取这些环境信息,可以调用>Environment接口的方法

在Spring Boot中,Environment接口提供了访问应用程序环境信息的方法,比如活动配置文件、系统环境变量、命令行参数等。Environment接口由Spring框架提供,Spring Boot应用程序通常会使用Spring提供的实现类AbstractEnvironment及其子类来实现具体的环境功能。

Environment对象封装的主要数据包括:

  1. Active Profiles: 当前激活的配置文件列表。Spring Boot允许应用程序定义不同的环境配置文件(如开发环境、测试环境和生产环境),通过激活不同的配置文件来改变应用程序的行为。
  2. System Properties: 系统属性,通常是操作系统级别的属性,比如操作系统名称、Java版本等。
  3. System Environment Variables: 系统环境变量,这些变量通常是由操作系统提供的,可以在启动应用程序时设置特定的值。
  4. Command Line Arguments: 应用程序启动时传递给主方法的命令行参数。
  5. Property Sources: Environment还包含了一个PropertySource列表,这个列表包含了从不同来源加载的所有属性。PropertySource可以来自多种地方,比如配置文件、系统属性、环境变量等。

在Spring Boot中,可以通过注入Environment来获取上述信息。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Component
public class SomeBean {

@Autowired
private Environment environment;

public void doSome(){
// 直接使用这个环境对象,来获取环境信息,配置信息等。
String[] activeProfiles = environment.getActiveProfiles();
for (String activeProfile : activeProfiles) {
System.out.println(activeProfile);
}

// 也可以获取配置信息
String street = environment.getProperty("app.xyz.addr.street");
System.out.println(street);
}
}

通过这种方式,你可以根据环境的不同灵活地配置你的应用程序。Environment是一个非常有用的工具,它可以帮助你管理各种类型的配置信息,并根据不同的运行时条件做出相应的调整。

Spring Boot中如何进行AOP的开发(需要补习)

Spring Boot AOP概述

面向切面编程AOP在Spring教程中已经进行了详细讲解,这里不再赘述,如果忘记的同学,可以重新听一下Spring教程中AOP相关的内容。这里仅带着大家在Spring Boot中实现AOP编程。

Spring Boot的AOP编程和Spring框架中AOP编程的唯一区别是:引入依赖的方式不同。其他内容完全一样。Spring Boot中AOP编程需要引入aop启动器:

1
2
3
4
5
<!--aop启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

![](./images/Spring Boot 3/1729734178510-707a3d64-caf6-407d-ba2b-9633f218c0ed.png)

可以看到,当引入aop启动器之后,会引入aop依赖aspectj依赖

  • aop依赖:如果只有这一个依赖,也可以实现AOP编程,这种方式表示使用了纯Spring AOP实现aop编程。
  • aspectj依赖:一个独立的可以完成AOP编程的AOP框架,属于第三方的,不属于Spring框架。(我们通常用它,因为它的功能更加强大)

Spring Boot AOP实现

实现功能:项目中很多service,要求执行任何service中的任何方法之前记录日志。

创建Spring Boot项目引入aop启动器
1
2
3
4
5
<!--aop启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
编写service并提供方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public interface UserService {
/**
* 保存用户信息
* @param id 用户id
* @param name 用户名
*/
void save(Long id, String name);

/**
* 根据id删除用户
* @param id 用户id
*/
void deleteById(Long id);
}
1
2
3
4
5
6
7
8
9
10
11
12
@Service
public class UserServiceImpl implements UserService {
@Override
public void save(Long id, String name) {
System.out.println("正在保存用户信息:" + name);
}

@Override
public void deleteById(Long id) {
System.out.println("正在删除用户" + id + "信息");
}
}
编写切面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Aspect//指定它是一个切面
@Component//纳入ioc
public class LoggingAspect {

// 日志记录器,记录日志的工具
private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);

// 定义切入点,匹配所有以 "service" 结尾的包下的所有方法
// 切入点表达式
@Pointcut("execution(* com.powernode.springboot.service..*(..))")
public void serviceMethods() {
}

// 在切入点的方法执行前执行此方法
// 前置通知
@Before("serviceMethods()")
public void logBefore(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
logger.info("方法 [{}] 携带的 [{}] 被调用了.", methodName, args);
}
}
测试
1
2
3
4
5
6
7
8
9
public static void main(String[] args) {
ConfigurableApplicationContext applicationContext = SpringApplication.run(Springboot311AopApplication.class, args);

UserService userService = applicationContext.getBean(UserService.class);
userService.save(111L, "jackson");
userService.deleteById(111L);

applicationContext.close();
}

第3章 SSM整合

整合持久层框架MyBatis

准备数据库表及数据

创建数据库:springboot

使用IDEA工具自带的mysql插件来完成表的创建和数据的准备:

![](./images/Spring Boot 3/1729050185616-731cbd39-267f-45e1-81f3-1f07d621c514.png)

![image-20250420210717500](./images/Spring Boot 3/image-20250420210717500.png)

![image-20250420210729068](./images/Spring Boot 3/image-20250420210729068.png)

![image-20250420210733298](./images/Spring Boot 3/image-20250420210733298.png)

表创建成功后,为表准备数据,如下:

![image-20250420210742206](./images/Spring Boot 3/image-20250420210742206.png)

创建SpringBoot项目

引入mysql驱动以及mybatis的启动器

![image-20250420210817285](./images/Spring Boot 3/image-20250420210817285.png)

依赖如下:

1
2
3
4
5
6
7
8
9
10
11
12
<!--mybatis的启动器-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
<!--mysql的驱动依赖-->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>

注意,之前也提到过:

  • Spring Boot官方提供的启动器的名字规则:spring-boot-starter-xxx
  • 第三方(非Spring Boot官方)提供的启动器的名字规则:xxx-spring-boot-starter

编写数据源配置

前面提到过,Spring Boot配置统一可以编写到application.properties中,配置如下:

1
2
3
4
5
6
7
8
9
# Spring Boot脚手架自动生成的
spring.application.name=sb3-05-springboot-mybatis

# mybatis连接数据库的数据源
spring.datasource.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/springboot
spring.datasource.username=root
spring.datasource.password=liuqi3711022

以上的配置属于连接池的配置,连接池使用的是Spring Boot默认的连接池:HikariCP

1
2
3
4
5
6
7
8
# 数据源配置
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/springboot
username: root
password: 123456
type: com.zaxxer.hikari.HikariDataSource

编写实体类Vip

t_vip中的字段分别是:

  • id
  • name
  • card_number
  • birth

对应实体类Vip中的属性名分别是:

  • Long id;
  • String name;
  • String cardNumber;
  • String birth;

创建包bean,在该包下新建Vip类,代码如下:

1
2
3
4
5
6
7
public class Vip {
private Long id;
private String name;
private String cardNumber; // 数据库字段 card_number
private String birth;
有参无参构造方法,没有id的有参,toSTring,setget
{}

编写Mapper接口

创建repository包,在该包下新建VipMapper接口,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public interface VipMapper {
/**
* 保存会员信息
* @param vip 会员信息
* @return 1表示保存成功。
*/
int insert(Vip vip);

/**
* 获取所有会员信息
* @return 会员列表
*/
List<Vip> selectAll();
}

编写Mapper接口的XML配置文件

resources目录下新建mapper目录,将来的mapper.xml配置文件放在这个目录下。

安装MyBatisX插件,该插件可以根据我们编写的VipMapper接口自动生成mapper的XML配置文件。

![image-20250421002452276](./images/Spring Boot 3/image-20250421002452276.png)

然后在VipMapper接口上:alt+enter,选择[mybatisx]generate mapper of xml,生成mapper of xml:需要选择一个生成的位置

![image-20250421002424305](./images/Spring Boot 3/image-20250421002424305.png)

![image-20250421003045200](./images/Spring Boot 3/image-20250421003045200.png)

接下来,你会看到Mapper接口中方法报错了,可以在错误的位置上使用alt+enter,选择Generate statement

![image-20250421013141212](./images/Spring Boot 3/image-20250421013141212.png)

这个时候在mapper的xml配置文件中便生成了对应的配置,如下:

![image-20250421013237683](./images/Spring Boot 3/image-20250421013237683.png)

接下来就是编写SQL语句了,最终VipMapper.xml文件的配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?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.powernode.springboot.repository.VipMapper">
<!--就是说下面的这个太长了,可以在配置文件里起别名起,起完别名就可以直接写那个类了,也可以不写,他会自动识别-->
<!--<insert id="insert"parameterType="com.powernode.springboot.bean.Vip"></insert>-->
<insert id="insert"parameterType="Vip">
insert into t_vip(id,name,card_number,birth) values(null,#{name},#{cardNumber},#{birth})
</insert>
<!--<select id="selectAll" resultType="com.powernode.springboot.bean.Vip"></select>-->
<select id="selectAll" resultType="Vip">
select * from t_vip
</select>
</mapper>

配置文件起别名:

1
2
3
# mybatis相关配置
# 1. 起别名
mybatis.type-aliases-package=com.powernode.springboot.bean
1
2
3
4
# mybatis相关配置
# 1. 起别名
mybatis:
type-aliases-package: com.powernode.springboot.bean

写个VipService接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public interface VipService {
/**
* 保存会员信息
* @param vip 会员信息
* @return true表示成功,false表示失败
*/
boolean save(Vip vip);

/**
* 查看会员列表
* @return 会员列表
*/
List<Vip> findAll();
}

写实现类VipServiceImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Service
public class VipServiceImpl implements VipService {
@Autowired
//这里报错是因为vipmapper的接口和xml必须要告诉springboot在哪里,就需要在配置文件加上路径后,再在入口程序加个扫描注解
private VipMapper vipMapper;

@Override
public boolean save(Vip vip) {
return vipMapper.insert(vip) == 1;//==1表示成功
}

@Override
public List<Vip> findAll() {
return vipMapper.selectAll();
}
}

添加Mapper的扫描

上面的VipMapper会有红线,是因为springboot不知道在哪里

在Spring Boot的入口程序上添加如下的注解,来完成VipMapper接口的扫描:

1
2
// 通过这个指定去哪里找Mapper接口(配置Mapper的扫描。)
@MapperScan(basePackages = {"com.powernode.springboot.repository"})

告诉MyBatis框架MapperXML文件的位置

application.properties配置文件中进行如下配置:

1
2
# 2. 告诉springboot mapper.xml文件在哪里
mybatis.mapper-locations=classpath:/mapper/*.xml
1
2
3
# 2. 告诉springboot,mapper配置文件的位置
mybatis:
mapper-locations: classpath:/mapper/*.xml

测试整合MyBatis是否成功

在Spring Boot主入口程序中获取Spring上下文对象ApplicationContext,从Spring容器中获取VipMapper对象,然后调用相关方法进行测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 通过这个指定去哪里找Mapper接口(配置Mapper的扫描。)
@MapperScan(basePackages = {"com.powernode.springboot.repository"})
@SpringBootApplication
public class Springboot312MybatisApplication {

public static void main(String[] args) {
ConfigurableApplicationContext applicationContext = SpringApplication.run(Springboot312MybatisApplication.class, args);
// 获取service对象
VipService vipService = applicationContext.getBean(VipService.class);

// 保存会员
Vip vip1 = new Vip("jack1", "1234567892", "1999-10-11");
vipService.save(vip1);
// 保存会员
Vip vip2 = new Vip("lucy1", "1234567893", "1999-10-12");
vipService.save(vip2);
// 查看会员列表
vipService.findAll().forEach(System.out::println);//用了Lambda表达式
//数据cardnumber为空是因为数据库叫card_number,vip里叫cardNumber,没对应上,可以在xml起别名,也可以启用自动映射数据库表的列名和bean的属性名

applicationContext.close();
}
}

测试结果:

![image-20250421183815008](./images/Spring Boot 3/image-20250421183815008.png)

测试结果中可以看到cardNumber属性没有赋值成功,原因是:表中的字段名叫做card_number,和实体类Vip的属性名cardNumber对应不上。解决办法两个:

  • 第一种方式:查询语句使用as关键字起别名,让查询结果列名和实体类的属性名对应上。
1
2
3
<select id="selectAll" resultType="Vip">
select id,name,card_number as cardNumber,bith from t_vip起别名
</select>
  • 第二种方式:通过配置自动映射
1
2
# 3. 启用自动映射数据库表的列名和bean的属性名
mybatis.configuration.map-underscore-to-camel-case=true

mapper的xml文件中的sql语句仍然使用*的方式:

map-underscore-to-camel-case 是一个配置项,主要用于处理数据库字段名与Java对象属性名之间的命名差异。在许多数据库中,字段名通常使用下划线(_)分隔单词,例如 first_name 或 last_name。而在Java代码中,变量名通常使用驼峰式命名法(camel case),如 firstName 和 lastName。

当使用MyBatis作为ORM框架时,默认情况下它会将SQL查询结果映射到Java对象的属性上。如果数据库中的字段名与Java对象的属性名不一致,那么就需要手动为每个字段指定相应的属性名,或者使用某种方式来自动转换这些名称。

map-underscore-to-camel-case 这个配置项的作用就是在查询结果映射到Java对象时,自动将下划线分隔的字段名转换成驼峰式命名法。这样可以减少手动映射的工作量,并提高代码的可读性和可维护性。

Lombok库

Lombok 是一个 Java 库,它可以通过注解的方式减少 Java 代码中的样板代码。Lombok 自动为你生成构造函数、getter、setter、equals、hashCode、toString 方法等,从而避免了手动编写这些重复性的代码。这不仅减少了出错的机会,还让代码看起来更加简洁。

Lombok只是一个编译阶段的库,能够帮我们自动补充代码,在Java程序运行阶段并不起作用。(因此Lombok库并不会影响Java程序的执行效率)

添加依赖

在 Maven 的 pom.xml 文件中添加 Lombok 依赖:

1
2
3
4
5
6
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.34</version>
<scope>provided</scope>
</dependency>

IDEA中安装Lombok插件

高版本的IntelliJ IDEA工具默认都是绑定Lombok插件的,不需要再额外安装:

Lombok 的主要注解

@Data

  • 等价于 @ToString, @EqualsAndHashCode, @Getter@Setter, @RequiredArgsConstructor.
  • 用于生成:必要参数的构造方法、getter、setter、toString、equals 和 hashcode 方法。

@Getter / @Setter

  • 分别用于生成所有的 getter 和 setter 方法。
  • 可以作用于整个类,也可以作用于特定的字段。

@NoArgsConstructor

  • 生成一个无参构造方法。

@AllArgsConstructor

  • 生成一个包含所有实例变量的构造器。

@RequiredArgsConstructor

  • 生成包含所有被 final 修饰符修饰的实例变量的构造方法。被final修饰的变量必须在构造方法执行前手动附上值0
  • 如果没有final的0实例变量,则自动生成无参数构造方法。

@ToString / @EqualsAndHashCode

  • 用于生成 toString 和 equals/hashCode 方法。
  • 这两个注解都有exclude属性,通过这个属性可以定制toString、hashCode、equals方法,也就是不包含的意思

注:Lombok只能帮助我们生成无参数构造方法和全参数构造方法,其他定制参数的构造方法无法生成。

如何使用 Lombok?

创建一个普通的Maven模块来快速测试一下Lombok库的使用:

![image-20250421185352259](./images/Spring Boot 3/image-20250421185352259.png)

添加最新依赖

在 Java 类中使用 Lombok 提供的注解。

1
2
3
4
5
6
7
8
9
10
@Data
// 无参数构造方法
@NoArgsConstructor
// 全参数构造方法
@AllArgsConstructor
public class User {
private Long id;
private String name;
private Integer age;
}

编写测试程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Test {
public static void main(String[] args) {
// 创建User对象
User user = new User();
user.setId(111L);
user.setName("jackson");
user.setAge(20);
System.out.println(user);

// 创建User对象
user = new User(120L, "lucy", 30);
System.out.println(user);
}
}

Lombok的其他常用注解

@Value

该注解会给所有属性添加final,给所有属性提供getter方法,自动生成toStringhashCodeequals

通过这个注解可以创建不可变对象。

1
2
3
4
5
@Value
public class Customer {
private String name;
private int age;
}

测试程序:

1
2
3
4
5
6
public class CustomerTest {
public static void main(String[] args) {
Customer customer = new Customer("jackson", 20);
System.out.println(customer);
}
}

可以查看一下字节码,你会发现,@Value注解的作用只会生成:全参数构造方法、getter方法、hashCode、equals、toString方法。(没有setter方法。)

@Builder

GoF23种设计模式之一:建造模式

建造模式(Builder Pattern)属于创建型设计模式。GoF23种设计模式之一。

用于解决对象创建时参数过多的问题。它通过将对象的构造过程与其表示分离,使得构造过程可以逐步完成,而不是一次性提供所有参数。建造模式的主要目的是让对象的创建过程更加清晰、灵活和可控。

简而言之,建造模式用于:

  1. 简化构造过程:通过逐步构造对象,避免构造函数参数过多。
  2. 提高可读性和可维护性:让构造过程更加清晰和有序。
  3. 增强灵活性:允许按需配置对象的不同部分。

这样可以更方便地创建复杂对象,并且使得代码更加易于理解和维护。

建造模式的代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// 建造模式。23种设计模式之一。GoF之一。
public class Person {
// 一般建造模式的bean属性使用final进行修饰
private final String name;
private final int age;
// 提供一个私有的全参数的构造方法
private Person(String name, int age) {
this.name = name;
this.age = age;
}
// getter//不用setter
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
// 通过这个公开的静态方法获取建造器对象
public static PersonBuilder builder() {
return new PersonBuilder();
}
// 静态的内部类:建造者,属性需要和上面的相同
public static class PersonBuilder {
private String name;
private int age;
public PersonBuilder name(String name) {
this.name = name;//先把参数的String name赋给上面的private String name
return this;//这里的this就是当前的PersonBuilder对象
}
public PersonBuilder age(int age) {
this.age = age;
return this;
}
public Person build() {
return new Person(name, age);//通过这个方法给person的nameage附上值
}
}
}
1
2
3
4
5
6
7
8
9
10
public class Test {
public static void main(String[] args) {
Person person = Person.builder()
.name("jack")
.age(30)
.build();
System.out.println(person);
//流程是通过person的builder方法获取personbuilder对象,然后去调age和name方法赋值,再调用build方法把传进来的参数新建给person对象
}
}
使用@Builder注解自动生成建造模式的代码

该注解可以直接帮助我们生成以上的代码。使用@Builder注解改造以上代码。

1
2
3
4
5
6
@Data
@Builder // 帮助我们这个类生成符合建造模式的代码。
public class Person {
private String name;
private int age;
}
1
2
3
4
5
6
7
8
9
public class Test {
public static void main(String[] args) {
Person person = Person.builder()
.name("jack")
.age(30)
.build();
System.out.println(person);
}
}

@Singular

@Singular注解是辅助@Builder注解的。

当被建造的对象的属性是一个集合,这个集合属性使用@Singular注解进行标注的话,可以连续调用集合属性对应的方法完成多个元素的添加。如果没有这个注解,则无法连续调用方法完成多个元素的添加。代码如下:

1
2
3
4
5
6
7
8
9
10
@Data
@Builder // 帮助我们这个类生成符合建造模式的代码。
public class Person {
private String name;
private int age;

// Singular翻译为:单数。表示一条一条添加
@Singular("addPhone")
private List<String> phones;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class Test {
public static void main(String[] args) {
//不加注解的话是先创建一个list集合然后一个一个加进去再赋值
/*List<String> phones = new ArrayList<>();
phones.add("12345678912");
phones.add("12345678913");
phones.add("12345678914");

Person person = Person.builder()
.name("jack")
.age(30)
.phones(phones)
.build();
System.out.println(person);*/

//直接就加到list集合里
Person person = Person.builder()
.name("jack")
.age(30)
.addPhone("12345678912")
.addPhone("12345678913")
.addPhone("12345678914")
.build();
System.out.println(person);
}
}

@Slf4j

Lombok 支持多种日志框架的注解,可以根据你使用的日志框架选择合适的注解。以下是 Lombok 提供的**部分日志注解**及其对应的日志框架:

  1. @Log4j
    • 自动生成一个 org.apache.log4j.Logger 对象。
    • 适用于 Apache Log4j 1.x 版本。
  2. @Slf4j
    • 自动生成一个 org.slf4j.Logger 对象。
    • 适用于 SLF4J(Simple Logging Facade for Java),这是一种日志门面,可以与多种实际的日志框架(如 Logback、Log4j 等)集成。
  3. @Log4j2
    • 自动生成一个 org.apache.logging.log4j.Logger 对象。
    • 适用于 Apache Log4j 2.x 版本。
使用示例

假设我们有一个类 UserService,并且我们想要使用 SLF4J 作为日志框架,我们可以这样使用 @Slf4j 注解:

1
2
3
4
5
6
@Slf4j
public class UserService {
public void saveUser(){
log.info("保存用户信息....");
}
}
1
2
3
4
5
6
public class UserServiceTest {
public static void main(String[] args) {
UserService userService = new UserService();
userService.saveUser();
}
}

在这个例子中,log 是一个静态成员变量,表示一个 org.slf4j.Logger 对象。Lombok 自动生成了这个日志对象,并且你可以直接使用它来进行日志记录。

选择合适的注解

选择哪个注解取决于你使用的日志框架。例如:

  • 如果你使用的是 SLF4J,可以选择 @Slf4j
  • 如果你使用的是 Log4j 1.x,可以选择 @Log4j
  • 如果你使用的是 Log4j 2.x,可以选择 @Log4j2
注意事项

确保在使用这些注解之前,已经在项目中引入了相应的日志框架依赖。例如,如果你使用 SLF4J,你需要在项目中添加 SLF4J 的依赖,以及一个具体的日志实现(如 Logback)。对于其他日志框架,也需要相应地添加依赖。

示例依赖

如果你使用 Maven 项目,并且选择了 SLF4J + Logback 的组合,可以添加以下依赖:

1
2
3
4
5
6
7
8
9
10
11
12
<!--Slf4j日志规范-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.16</version>
</dependency>
<!--Slf4j日志实现:logback-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.5.11</version>
</dependency>

MyBatis逆向生成

MyBatis逆向工程:使用IDEA插件可以根据数据库表的设计逆向生成MyBatis的Mapper接口 与 MapperXML文件。

安装插件free mybatis tools

![image-20250423023425918](./images/Spring Boot 3/image-20250423023425918.png)

在IDEA中配置数据源数据库

然后在里面创建好数据

![image-20250423030158773](./images/Spring Boot 3/image-20250423030158773.png)

![image-20250423030250812](./images/Spring Boot 3/image-20250423030250812.png)

使用脚手架创建SpringBoot项目

添加依赖:mybatis依赖、mysql驱动、Lombok库

![image-20250423030332658](./images/Spring Boot 3/image-20250423030332658.png)

生成MyBatis代码放到SpringBoot项目中

在表上右键:Mybatis-Generator

![image-20250423030500952](./images/Spring Boot 3/image-20250423030500952.png)

![QQ20250423-033015](./images/Spring Boot 3/QQ20250423-033015.png)

代码生成后,如果在IDEA中看不到,这样做(重新从硬盘加载):

![image-20250423033134517](./images/Spring Boot 3/image-20250423033134517.png)

注意:生成的VipMapper接口上自动添加了@Repository注解,这个注解没用,删除即可。


编写mybatis相关配置

application.properties属性文件的配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
spring.application.name=springboot3-13-mybatis-gennerator

#还要告诉配置文件的位置
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/springboot
spring.datasource.username=root
spring.datasource.password=liuqi3711022
#这个不配也可以,默认就是这个spring.datasource.type=com.zaxxer.hikari.HikariDataSource

# mybatis相关配置
mybatis.configuration.map-underscore-to-camel-case=true
mybatis.mapper-locations=classpath:/mapper/*.xml
mybatis.type-aliases-package=com.powernode.springboot.bean

编写测试程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//做包扫描
@MapperScan(basePackages = "com.powernode.springboot.repository")
@SpringBootApplication
public class Springboot313MybatisGenneratorApplication {

public static void main(String[] args) {
ConfigurableApplicationContext applicationContext = SpringApplication.run(Springboot313MybatisGenneratorApplication.class, args);
VipMapper vipMapper = applicationContext.getBean(VipMapper.class);

Vip vip = new Vip("jack", "1234567899", "2000-11-10");
vipMapper.insert(vip);

Vip vip2 = vipMapper.selectByPrimaryKey(1L);
System.out.println(vip2);
applicationContext.close();
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@NoArgsConstructor
@AllArgsConstructor
@Data
public class Vip implements Serializable {
//因为id是主键自增,不需要生成构造方法,就给其他的手动生成
private Long id;

private String name;

private String cardNumber;

private String birth;

public Vip(String name, String cardNumber, String birth) {
this.name = name;
this.cardNumber = cardNumber;
this.birth = birth;
}

private static final long serialVersionUID = 1L;
}

整合SpringMVC(SSM整合)

Spring Boot项目本身就是基于Spring框架实现的。因此SSM整合时,只需要在整合MyBatis框架之后,引入web启动器即可完成SSM整合。

使用脚手架创建SpringBoot项目

![image-20250423193335275](./images/Spring Boot 3/image-20250423193335275.png)

添加依赖:web启动器、mybatis启动器、mysql驱动依赖、lombok依赖

使用free mybatis tool插件逆向生成MyBatis代码

![image-20250423193624771](./images/Spring Boot 3/image-20250423193624771.png)

整合MyBatis

编写数据源的配置

1
2
3
4
5
6
# 数据源
spring.datasource.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/springboot
spring.datasource.username=root
spring.datasource.password=liuqi3711022

编写mapper xml配置文件的位置

1
2
3
4
# mybatis配置
mybatis.type-aliases-package=com.powernode.ssm.bean
mybatis.mapper-locations=classpath:/mapper/*.xml
mybatis.configuration.map-underscore-to-camel-case=true

在主入口类上添加@MapperScan注解

1
2
3
4
5
6
7
8
9
@MapperScan(basePackages = "com.powernode.ssm.repository")
@SpringBootApplication
public class Springboot314SsmApplication {

public static void main(String[] args) {
SpringApplication.run(Springboot314SsmApplication.class, args);
}

}

编写service

编写VipService接口:

1
2
3
4
5
6
7
8
9
10
public interface VipService {

/**
* 根据会员的卡号查询会员的信息。
* @param cardNumber 会员的卡号
* @return 会员信息
*/
Vip findByCardNumber(String cardNumber);

}

编写VipServiceImpl实现类:

1
2
3
4
5
6
7
8
9
10
11
@Service
public class VipServiceImpl implements VipService {
@Autowired
private VipMapper vipMapper;

@Override
public Vip findByCardNumber(String cardNumber) {
//调用vipmapper里的查询方法,把卡号传进去
return vipMapper.selectByCardNumber(cardNumber);
}
}

编写controller

编写VipController,代码如下:

1
2
3
4
5
6
7
8
9
10
11
@RestController
public class VipController {

@Autowired
private VipService vipService;

@GetMapping("/detail")
public Vip detail(@RequestParam("cn") String cardNumber){//地址写上http://localhost:8080/detail?cn=卡号
return vipService.findByCardNumber(cardNumber);//会返回一个vip然后转成json响应在浏览器
}
}

提示:这里使用了RESTFul编程风格,这个内容在SpringMVC课程中已经讲过。忘了的同学可以回头观看一下。

启动服务器测试

执行SpringBoot项目主入口的main方法,启动Tomcat服务器:

打开浏览器访问:http://localhost:8080/detail?cn=1234567893

到此为止,SSM框架就集成完毕了,通过这个集成也可以感觉到SpringBoot简化了SSM三大框架的集成。

第4章 Spring Boot自动配置

自动配置概述

SpringBoot的两大核心

Spring Boot 框架的两大核心特性可以概括为“启动器”(Starter)和“自动配置”(Auto-configuration)。

  1. 启动器(Starter)
    Spring Boot 提供了一系列的 Starter POMs,它们是一组预定义的依赖关系。

当你在项目中引入一个 Starter POM 时,它会自动包含所有必要的 Spring 组件以及合理的默认设置。开发者不需要手动管理复杂的依赖关系,也不需要担心版本冲突的问题,减少了配置上的出错可能。

  1. 自动配置(Auto-Configuration)
    当添加了特定的 Starter POM 后,Spring Boot 会**根据类路径上存在的 jar 包来自动配置 Bean(自动配置相关组件)(比如:SpringBoot发现类路径上存在mybatis相关的类,例如SqlSessionFactory.class,那么SpringBoot将自动配置mybatis相关的所有Bean。)**。

如果开发者没有显式地提供任何与特定功能相关的配置,Spring Boot 将使用其默认配置来自动设置这些功能。当然,如果需要的话,用户也可以覆盖这些默认设置。

这两个特性结合在一起,使得使用 Spring Boot 开发应用程序变得更加简单快速,减少了大量的样板代码和重复配置的工作。让程序员专注业务逻辑的开发,在环境方面耗费最少的时间

体会自动配置带来的便捷

拿SpringBoot集成MyBatis为例。

以前,在没有SpringBoot框架的时候,我们用Spring集成MyBatis框架,需要进行如下的配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">

<!-- 数据源配置 -->
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
<property name="username" value="root"/>
<property name="password" value="password"/>
</bean>

<!-- SqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="mapperLocations" value="classpath:mapper/*.xml"/>
<property name="typeAliasesPackage" value="com.example.model"/>
</bean>

<!-- Mapper 扫描器 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.example.mapper"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>

<!-- 事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>

<!-- 开启事务注解 -->
<tx:annotation-driven transaction-manager="transactionManager"/>

<!-- 扫描 service 层的包 -->
<context:component-scan base-package="com.example.service"/>

</beans>

通过以上的配置可以看到Spring集成MyBatis的时候,需要手动提供BasicDataSourceSqlSessionFactoryBeanMapperScannerConfigurerDataSourceTransactionManager等Bean的配置。

使用了Spring Boot框架之后,这些配置都不需要提供了,SpringBoot框架的自动配置机制可以全部按照默认的方式自动化完成。减少了大量的配置,在环境方面耗费很少的时间,让程序员更加专注业务逻辑的处理。我们只需要在application.yml中提供以下的配置即可:

1
2
3
4
5
6
7
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/springboot
username: root
password: 123456
type: com.zaxxer.hikari.HikariDataSource

spring-boot-starter是springboot框架最核心的启动器,做springboot项目时,这个启动器是必须的,如果创建这个项目的时候没有选择任何启动器,这个时候springboot项目最小配置就必须将spring-boot-starter配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!--springboot框架建议以继承父工程的方式引入springboot。-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

<!--这个启动器是springboot最核心的启动器。这个启动器是必须引入的。-->
<!--引入任何启动的时候,spring-boot-starter都会关联引入。-->
<!--<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>-->
<!--这个就是web启动器,web启动器会关联引入springboot核心启动器,上面的starter就可以不要了-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

引入web启动器都有哪些组件会准备好

通过以下代码获取spring ioc容器中的所有注册的bean,一个Bean就是一个组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
@SpringBootApplication
public class Springboot315AutoConfigurationApplication {

public static void main(String[] args) {
ConfigurableApplicationContext applicationContext = SpringApplication.run(Springboot315AutoConfigurationApplication.class, args);
//从ioc容器中查看有多少组件,将组件的id打印出来,这个方法能获取所有组件的id
String[] allBeanNames = applicationContext.getBeanDefinitionNames();
for(String allBeanName : allBeanNames){
System.out.println(allBeanName);
}
applicationContext.close();
}
}

在springboot没有引入任何启动器的情况下,默认提供了59bean

引入web启动器可以发现,ioc容器中注册的bean总数量为160

也就是说,引入了web启动器后,ioc容器中增加了101个bean对象(加入了101个组件)。这101个bean对象都是为web开发而准备的,例如我们常见的:

  • dispatcherServlet:DispatcherServlet 是 Spring MVC 的前端控制器,负责接收所有的 HTTP 请求,并将请求分发给适当的处理器(Controller)
  • viewResolver:ViewResolver 是 Spring MVC 中用于将逻辑视图名称解析为实际视图对象的组件。它的主要作用是根据控制器返回的视图名称,找到对应的视图实现(如 JSP、Thymeleaf、Freemarker 等),并返回给 DispatcherServlet 用于渲染视图。
  • characterEncodingFilter:字符集过滤器组件,专门处理字符集,解决请求和响应的乱码问题。
  • mappingJackson2HttpMessageConverter:负责处理消息转换的组件。它可以将json字符串转换成java对象,也可以将java对象转换为json字符串。
  • ……

每一个组件都有它特定的功能。

没有使用SpringBoot之前,以上的很多组件都是需要手动配置的。

默认的包扫描规则

之前我们已经说过并且测试过:springboot默认情况下只扫描主入口类所在包及子包下的类。

这是因为@SpringBootApplication注解被@ComponentScan标注,代替spring以前的这个配置:<context:component-scan base-packages="主入口类所在包"/>

当然,我们也可以打破这个规则,通过以下两种方式:

  • 第一种:@SpringBootApplication(scanBasePackages = “com”)
  • 第二种:@ComponentScan(“com”)
1
2
3
4
5
6
7
//@SpringBootApplication里面集成了这三个注解,componentscan是负责包扫描的,指定这个包下所有的bean可以被加载
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com")
//@SpringBootApplication(scanBasePackages = "com")//将扫描包的范围设定为从com包下开始
public class Springboot315AutoConfigurationApplication {

默认配置

springboot为功能的实现提供了非常多的默认配置.

例如:tomcat服务器端口号在没有配置的情况下,默认是8080

当然,也可以在application.properties文件中进行重新配置:

1
server.port=8081

再如,配置thymeleaf的模板引擎时,默认的模板引擎前缀是classpath:/templates/,默认的后缀是.html

当然,也可以重新配置:

1
2
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html

通过创建脚手架时引入wen启动器会自动创建static(放置静态资源js图片)和templates(放置thymeleaf模版引擎下的xxx.html)

这些配置最终都会通过@ConfigurationProperties(prefix="")注解绑定到对应的bean的属性上。这个Bean我们一般称为属性类。例如:

ServerProperties:服务器属性类,专门负责配置服务器相关信息。

1
2
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties {}

ThymeleafProperties:Thymeleaf属性类,专门负责配置Thymeleaf模板引擎的。

1
2
@ConfigurationProperties(prefix = "spring.thymeleaf")
public class ThymeleafProperties {}

SpringBoot官方文档当中也有指导,告诉你都有哪些属性类,告诉你在application.properties中都可以配置哪些东西。默认值都是什么:

![image-20250424141751278](./images/Spring Boot 3/image-20250424141751278.png)

自动配置是按需加载的

SpringBoot提供了非常多的自动配置类,有的是web相关的自动配置,有的是mail相关的自动配置。但是这些自动配置并不是全部生效,它是按需加载的。导入了哪个启动器,则该启动器对应的自动配置类才会被加载

这些自动配置类在哪里?

任何启动器都会关联引入这样一个启动器:spring-boot-starter,它是springboot框架最核心的启动器。

spring-boot-starter又关联引入了spring-boot-autoconfigure。所有的自动配置类都在这里。

![image-20250424165037627](./images/Spring Boot 3/image-20250424165037627.png)

![image-20250424164959922](./images/Spring Boot 3/image-20250424164959922.png)

SpringBoot框架提供的条件注解

如何做到按需加载的,依靠SpringBoot框架中的条件注解来实现的。

Spring Boot框架中的@ConditionalOnXxx系列注解属于条件注解(Conditional Annotations),它们用于基于某些条件来决定是否应该创建一个或一组Bean。这些注解通常用在自动配置类上,以确保只有在特定条件满足时才会应用相应的配置。

这里是一些常见的@ConditionalOnXxx注解及其作用:

  • @ConditionalOnClass:当指定的类存在时,才创建Bean。
  • @ConditionalOnMissingClass:当指定的类不存在时,才创建Bean。
  • @ConditionalOnBean:当容器中存在指定的Bean时,才创建Bean。
  • @ConditionalOnMissingBean:当容器中不存在指定的Bean时,才创建Bean。
  • @ConditionalOnProperty:当配置文件中存在指定的属性时,才创建Bean。也可以设置属性值需要匹配的值。
  • @ConditionalOnResource:当指定的资源存在时,才创建Bean。
  • @ConditionalOnWebApplication:当应用程序是Web应用时,才创建Bean。
  • @ConditionalOnNotWebApplication:当应用程序不是Web应用时,才创建Bean。

使用这些注解可以帮助开发者根据不同的运行环境或配置来灵活地控制Bean的创建,从而实现更智能、更自动化的配置过程。这对于构建可插拔的模块化系统特别有用,因为可以根据实际需求选择性地启用或禁用某些功能。

假设我们来实现这样一个功能:如果IoC容器当中**存在ABean**,就创建B`Bean,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Configuration
public class AppConfig {

@Bean
public A test1(){// 方法的名字作为bean name
return new A();
}

// 如果IoC容器中存在A类型的Bean对象时创建b
@ConditionalOnBean(A.class)
@Bean
public B test2(){
return new B();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//@SpringBootApplication里面集成了这三个注解,componentscan是负责包扫描的,指定这个包下所有的bean可以被加载
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com")
//@SpringBootApplication(scanBasePackages = "com")//将扫描包的范围设定为从com包下开始
public class Springboot315AutoConfigurationApplication {

public static void main(String[] args) {
ConfigurableApplicationContext applicationContext = SpringApplication.run(Springboot315AutoConfigurationApplication.class, args);
//从ioc容器中查看有多少组件,将组件的id打印出来,这个方法能获取所有组件的id
String[] allBeanNames = applicationContext.getBeanDefinitionNames();
for(String allBeanName : allBeanNames){
//System.out.println(allBeanName);
/*if(allBeanName.equals("appBean")){
System.out.println(allBeanName);
}*/
if ("test1".equals(allBeanName)){
System.out.println(allBeanName);
} else if ("test2".equals(allBeanName)) {
System.out.println(allBeanName);//输出test1 test2
}
}
applicationContext.close();
}
}

如果IoC容器当中**不存在Bean**,就创建B`Bean,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Configuration
public class AppConfig {

@Bean
public A a(){
return new A();
}

@ConditionalOnMissingBean(A.class)//输出test1,因为存在所以不创建
@Bean
public B b(){
return new B();
}
}

当类路径当中存在DispatcherServlet类,则启用配置,反之则不启用配置,代码如下:

1
2
3
4
5
6
7
8
9
//当类路径中存在DispatcherServlet这个类时配置生效
@ConditionalOnClass(name = {"org.springframework.web.servlet.DispatcherServlet"})
@Configuration
public class MyConfig {
@Bean
public A getA(){
return new A();
}
}

以上程序自行测试!

自动配置实现原理

我们来深入的分析一个问题:为什么导入web启动器,web开发相关的自动配置就会生效?

在程序没有开始执行之前都导入了哪些依赖

在程序没有开始运行之前,我们先来分析一下,当导入web启动器之后,底层都一连串的导入了哪些依赖!

  1. 从这里开始:导入了spring-boot-starter-web【web启动器】
  2. 然后关联导入了spring-boot-starterspring-boot-starter-jsonspring-boot-starter-tomcatspring-webspring-webmvc
    1. 注意:spring-boot-starter是springboot核心启动器,任何启动器在导入时,都会关联导入springboot核心启动器。
  3. 核心启动器导入之后,关联导入了一个jar包:spring-boot-autoconfigure
    1. 注意:这个jar包中存放的是springboot框架**官方支持的自动配置类**。如下图:

![](./images/Spring Boot 3/1731120167649-5b43660e-d911-4af1-b609-7ba357483cbb.png)

  1. 官方支持的自动配置类有多少个呢,可以通过下图位置查看:

![](./images/Spring Boot 3/1731120316690-38e92299-860c-4f52-8e90-93773af32190.png)

![](./images/Spring Boot 3/1731120338604-18c536ab-a98f-4ba7-adf3-10f47f02056d.png)

得知springboot3.3.5这个版本共152个自动配置类。自动配置类的命名规则是XxxxAutoConfiguration

提示:哪个自动配置类生效,就代表哪个配置文件生效,那么对应的技术就完成了整合,就可以进行对应技术的开发。

从main方法开始执行之后都发生了什么

以上分析的是在项目结构上已经完成了相关依赖的导入,这些自动配置了导入到了项目当中,那么在运行时哪些自动配置类会被加载?哪些自动配置类会生效呢?我们接下来进行程序运行阶段的分析:

  1. 程序从main方法进入执行,主入口类上使用@SpringBootApplication进行了标注。

  2. @SpringBootApplication注解是复合注解,代表以下三个注解的功能:

    1. @SpringBootConfiguration:它被@Configuration标注,说明主入口类就是一个配置类,此时该配置开始加载。
    2. @ComponentScan:默认扫描的是主入口所在包以及子包。因此spring-boot-autoconfigure包是扫描不到的,按说XxxAutoConfiguration自动配置类是无法加载的!!!那么这些自动配置类又是如何加载和生效的呢?
    3. @EnableAutoConfiguration:自动配置类的加载和生效全靠它了。该注解被翻译为:启用自动配置。如果后面加上(excludeName= {“A”, “B”}),就会排除这些不去自动配置
  3. @EnableAutoConfiguration@Import(AutoConfigurationImportSelector.class)标注

    1. @Import(AutoConfigurationImportSelector.class)的作用是:将AutoConfigurationImportSelector作为一个Bean加载到IoC容器中。
    2. 这个Bean的作用是:负责收集和选择所有符合条件的自动配置类。
  4. 添加断点,跟踪AutoConfigurationImportSelector源码:

    ![image-20250426163012567](./images/Spring Boot 3/image-20250426163012567.png)

    通过跟踪得知,这152个自动配置类的类名都会被加载到IoC容器中。注意:加载了152,并不是152个全部生效

    如果给

  5. 152个自动配置类底层是怎么查找的?

    点击这里

    ![image-20250426163734714](./images/Spring Boot 3/image-20250426163734714.png)

    ![image-20250426163759149](./images/Spring Boot 3/image-20250426163759149.png)

    这里的load返回一个list集合,然后这个方法把这个集合return,点进去load方法

    ![](./images/Spring Boot 3/image-20250426164956281.png)

    这里可以看到后面是一个class类,也就是上面的AutoConfiguration.class

    通过以上源码跟踪,得知,是从下图位置加载的:

    ![](./images/Spring Boot 3/1731120316690-38e92299-860c-4f52-8e90-93773af32190-1745657467970-10.png)

    最先获取到152个,经过上图的一层一层的过滤(条件注解),最终筛选了26个自动配置类,为什么这么少,因为你只引入了web starter。这26个配置就是做web开发需要的最少配置

具体怎么排除的,请看以下解释:

  • configurations = removeDuplicates(configurations);

去重:移除 configurations 列表中的重复项,确保每个配置类只出现一次。

  • Set exclusions = getExclusions(annotationMetadata, attributes);

获取排除列表:从注解元数据和属性中获取需要排除的配置类名称集合。因为@EnableAutoConfiguration注解还能这么用:@EnableAutoConfiguration(exclude = {排除列表}, excludeName = {排除列表})

  • checkExcludedClasses(configurations, exclusions);

检查排除:验证 configurations 中是否有被排除的类,如果有,可能会抛出异常或记录警告。

  • configurations.removeAll(exclusions);

移除排除项:从 configurations 列表中移除所有在 exclusions 集合中的配置类。

  • configurations = getConfigurationClassFilter().filter(configurations);

过滤配置类:使用 ConfigurationClassFilter 对 configurations 进行进一步过滤。这一行通过条件注解进行判断,例如 @ConditionalOnClass、@ConditionalOnMissingBean 等。

  • fireAutoConfigurationImportEvents(configurations, exclusions);

触发事件:触发自动配置导入事件,通知其他组件或监听器关于最终确定的配置类和排除的类。

自动配置类都干了啥

自动配置类导入了一堆相关的组件(一个组件一个功能),而每个组件获取配置时都是从属性类中获取,而属性类恰好又和配置文件绑定。

DispatcherServletAutoConfiguration自动配置类为例,这个自动配置类主要是配置了SpringMVC中的前端控制器。

请看源码:

![](./images/Spring Boot 3/1731133277010-5db32db6-3577-48c2-8238-4f845b273a46.png)

![](./images/Spring Boot 3/1730804471502-31c64115-c49a-4d76-92e0-90e9ae543b32-1745658496978-14.png)

通过以上源码得知,DispatcherServletConfiguration组件的配置信息来源于WebMvcProperties属性类。WebMvcProperties类源码如下:

![](./images/Spring Boot 3/1731133562874-9017d21b-32e6-488d-a4c8-6726fa7b0a89.png)

通过以上源码又得知,要对DispatcherServletConfiguration进行配置的话,应该在application.properties中使用这样的前缀配置:spring.mvc....

![](./images/Spring Boot 3/1730804471502-31c64115-c49a-4d76-92e0-90e9ae543b32-1745658496978-14.png)

再来看嵌入式Web服务器工厂自定义程序自动配置EmbeddedWebServerFactoryCustomizerAutoConfiguration,通俗讲:通过它可以配置web服务器。

请看源码:

![](./images/Spring Boot 3/1731134006487-d1331ac9-1145-4d52-aed3-e9b60cdc810e.png)

![](./images/Spring Boot 3/1730804471502-31c64115-c49a-4d76-92e0-90e9ae543b32-1745658496978-14.png)

只要加了@EnableConfigurationProperties()就会在ioc容器中创建serverProperties对象,这个bean就有了,然后把application.Properties的配置信息绑定到serverProperties对象,在application文件中有很多配置,以什么前缀的配置绑定这个对象属性上呢?

![image-20250426185717384](./images/Spring Boot 3/image-20250426185717384.png)

就是以server开头的,比如server.port=8081,8081就会赋给port

通过以上源码得知,这个自动配置类中也有很多组件,有tomcat组件,有jetty组件。单独看Tomcat,要配置Tomcat服务器,需要参照ServerProperties属性类,打开源码看看:

![](./images/Spring Boot 3/1731134070889-7a8b5378-c3bf-4b3f-8fb4-0b70a37e4938.png)

因此配置Tomcat服务器需要在application.properties文件中使用这样的前缀配置:server.

总结自动配置原理

  1. 运行环境准备阶段
    1. 引入web启动器
    2. 最终传递引入了自动配置的jar包
    3. 自动配置的jar包中有152个自动配置类,到此运行环境准备完毕。
  2. 运行阶段
    1. @EnableAutoConfiguration 启用自动配置,将152个自动配置类全部加载到IoC容器中,然后根据开发场景筛选出必须的自动配置类。
    2. 自动配置类加载了一堆组件。
    3. 每个组件需要的数据来自属性类。
    4. 属性类又和配置文件绑定在一起。
  3. 因此,最终一句话:导入启动器,修改配置文件,就可以完成对应功能的开发。

第5章 Spring Boot的web开发

SpringBoot的web自动配置

http请求协议:browser—>Server,http相应协议Server—>browser

新建项目sb3-09-web:添加web启动器,添加Lombok依赖。

![](./images/Spring Boot 3/1729842528256-345582ac-9fbe-464f-9d1a-3ee7800bea8a.png)

web自动配置的依赖是如何传递的(这一部分视频没讲解,随便看看)

  1. 首先引入了web启动器,如下:
1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
  1. web启动器传递引入了spring-boot-starter,如下:
1
2
3
4
5
6
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>3.3.5</version>
<scope>compile</scope>
</dependency>

![](./images/Spring Boot 3/1730804471502-31c64115-c49a-4d76-92e0-90e9ae543b32-1745860695540-1.png)

  1. spring-boot-starter会传递引入一个spring-boot-autoconfigure包,如下:
1
2
3
4
5
6
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>3.3.5</version>
<scope>compile</scope>
</dependency>

![](./images/Spring Boot 3/1729843303324-6c665ade-1fc3-465c-b11b-0ac167c5bd4c.png)

![](./images/Spring Boot 3/1730804471502-31c64115-c49a-4d76-92e0-90e9ae543b32-1745860695540-1.png)

  1. spring-boot-autoconfigure包中的.imports文件中罗列的需要导入的自动配置类,如下图:

![](./images/Spring Boot 3/1729843518728-be8a441f-d6cc-4781-8f0e-a3c67a05b697.png)

![](./images/Spring Boot 3/1730804471502-31c64115-c49a-4d76-92e0-90e9ae543b32-1745860695540-1.png)

web自动配置的实现原理

  1. 从入口程序开始:
1
2
3
4
5
6
7
8
9
10
11
12
package com.powernode.sb309web;

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

@SpringBootApplication
public class Sb309WebApplication {

public static void main(String[] args) {
SpringApplication.run(Sb309WebApplication.class, args);
}
}

![](./images/Spring Boot 3/1730804471502-31c64115-c49a-4d76-92e0-90e9ae543b32-1745860695540-1.png)

入口程序被@SpringBootApplication注解标注。

  1. @SpringBootApplication注解被@EnableAutoConfiguration注解标注。表示启用自动配置。
  2. @EnableAutoConfiguration注解被@Import({AutoConfigurationImportSelector.class})注解标注。
  3. 因此AutoConfigurationImportSelector决定哪些自动配置类是需要导入的。
  4. <font style="color:#080808;background-color:#ffffff;">AutoConfigurationImportSelector</font>底层实现步骤具体如下:

![](./images/Spring Boot 3/1729845866175-609a5c89-9342-4d96-b624-fcc5a7dc2434.png)

![](./images/Spring Boot 3/1729845887890-372b80d6-2e7c-4d7e-8c5c-40cc3402da91.png)

![](./images/Spring Boot 3/1730804471502-31c64115-c49a-4d76-92e0-90e9ae543b32-1745860695540-1.png)

![](./images/Spring Boot 3/1729845909271-f6640224-ae46-4d74-bea2-ff00fa4e95d7.png)

![](./images/Spring Boot 3/1729845939380-9158c6fe-9ac2-43e3-8b4a-fb9b568eefd3.png)

最终找的文件是:META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

注意:任何jar包,包括第三方的依赖,自动配置类所在的路径以及文件名都是完全相同的,都是**META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports**

例如mybatis的自动配置类的列表文件也是这样,如下图:

![](./images/Spring Boot 3/1729846670846-84850d75-8a0f-418c-8c64-f48f00f3e738.png)

![](./images/Spring Boot 3/1730804471502-31c64115-c49a-4d76-92e0-90e9ae543b32-1745860695540-1.png)

通过web自动配置类逆推web配置的prefix

在自动配置列表中找到web自动配置相关的类:

![](./images/Spring Boot 3/1729846834747-246f3a88-f822-46bc-a00c-7770d52c5c26.png)

![](./images/Spring Boot 3/1730804471502-31c64115-c49a-4d76-92e0-90e9ae543b32-1745860695540-1.png)

以下就是web自动配置类列表:

1
2
3
4
5
6
7
8
org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration
org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration

![](./images/Spring Boot 3/1730804471502-31c64115-c49a-4d76-92e0-90e9ae543b32-1745860695540-1.png)

通过web自动配置类的源码可以逆推web配置的prefix:

  1. WebMvcAutoConfiguration

![](./images/Spring Boot 3/1729847170017-0f711f67-f266-4ac2-a25a-c239475b38fe.png)

![](./images/Spring Boot 3/1729847235858-7cab9d78-465f-4289-be9c-9b399570e8d3.png)

![](./images/Spring Boot 3/1729847268539-715a377a-ecb9-48fc-ad58-9194d23b7cdf.png)

![](./images/Spring Boot 3/1730804471502-31c64115-c49a-4d76-92e0-90e9ae543b32-1745860695540-1.png)

  1. MultipartAutoConfiguration

![](./images/Spring Boot 3/1729847417032-2abad318-a48b-49a7-a2dc-26abd0f4d8e5.png)

![](./images/Spring Boot 3/1729847475254-4f486638-6132-40c0-baee-25c4c3091f49.png)

![](./images/Spring Boot 3/1730804471502-31c64115-c49a-4d76-92e0-90e9ae543b32-1745860695540-1.png)

  1. HttpEncodingAutoConfiguration

![](./images/Spring Boot 3/1729847580938-8114e69c-ba29-4305-ba80-3b0d277be2e1.png)

![](./images/Spring Boot 3/1729847644668-2d8aa37a-699d-4009-92c7-3c9a60efcb49.png)

![](./images/Spring Boot 3/1730804471502-31c64115-c49a-4d76-92e0-90e9ae543b32-1745860695540-1.png)

  1. ErrorMvcAutoConfiguration
  2. ServletWebServerFactoryAutoConfiguration
  3. DispatcherServletAutoConfiguration
  4. EmbeddedWebServerFactoryCustomizerAutoConfiguration
  5. RestTemplateAutoConfiguration

通过查看源码,得知,web开发时,在application.properties配置文件中可以配置的前缀是:

1
2
3
4
5
6
7
8
9
10
11
# SpringMVC相关配置
spring.mvc.

# web开发通用配置
spring.web.

# 文件上传配置
spring.servlet.multipart.

# 服务器配置
server.

Web自动配置都默认配置了什么

查看官方文档:

![](./images/Spring Boot 3/1731383415638-90d102e9-1fd0-4f31-9736-9e3bef3936d3.png)

翻译如下:

Spring Boot 为 Spring MVC 提供了自动配置,这在大多数应用程序中都能很好地工作。除了已经实现了 Spring MVC 的默认功能外,自动配置还提供了以下特性:

  • 包括 ContentNegotiatingViewResolverBeanNameViewResolver 的 Bean。
    • ContentNegotiatingViewResolver 自动根据HTTP请求头中Accept字段来选择合适的视图技术渲染响应。
    • <font style="color:rgb(44, 44, 54);">BeanNameViewResolver</font> 的作用是根据视图名称找到视图View对象。
  • 支持提供静态资源,包括对 WebJars的支持。
    • 静态资源路径默认已经配置好了。默认会去static目录下找。
  • 自动注册 ConverterGenericConverterFormatter 的 Bean。
    • Converter:转换器,做类型转换的,例如表单提交了用户数据,将表单数据转换成User对象。
    • Formatter:格式化器,做数据格式化的,例如将Java中的日期类型对象格式化为特定格式的日期字符串。或者将用户提交的日期字符串,转换为Java中的日期对象。
  • 支持 HttpMessageConverters
    • 内置了很多的HTTP消息转换器。例如:MappingJackson2HttpMessageConverter可以将json转换成java对象,也可以将java对象转换为json字符串。
  • 自动注册 MessageCodesResolver
    • SpringBoot会自动注册一个默认的消息代码解析器
    • 帮助你在表单验证出错时生成一些特殊的代码。这些代码让你能够更精确地定位问题,并提供更友好的错误提示。
  • 静态 index.html 文件支持。
    • Spring Boot 会自动处理位于项目静态资源目录下的 index.html 文件,使其成为应用程序的默认主页
  • 自动使用 ConfigurableWebBindingInitializer Bean。
    • 用它来指定默认使用哪个转换器,默认使用哪个格式化器。在这个类当中都已经配好了。

如果您不想使用自动配置并希望完全控制 Spring MVC,可以添加您自己的带有 **<font style="color:#DF2A3F;">@EnableWebMvc</font>** 注解的 @Configuration

如果您希望保留这些 Spring Boot MVC 定制化设置并进行更多的 MVC 定制化(如拦截器、格式化程序、视图控制器等其他功能),可以添加您自己的类型为 WebMvcConfigurer@Configuration 类。但不能使用@EnableWebMvc注解

WebMvcAutoConfiguration原理

通过源码分析的方式,学习WebMvc的自动配置原理。

WebMvc自动配置是否生效的条件

1
2
3
4
5
6
7
8
@AutoConfiguration(after = { DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
ValidationAutoConfiguration.class })
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@ImportRuntimeHints(WebResourcesRuntimeHints.class)
public class WebMvcAutoConfiguration {}
  • @AutoConfiguration(after = { DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,ValidationAutoConfiguration.class })
    • WebMvcAutoConfiguration自动配置类加载顺序在以上自动配置类加载后加载。
  • @ConditionalOnWebApplication(type = Type.SERVLET)
    • WebMvcAutoConfiguration自动配置类只在servlet环境中生效。
  • @ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
    • 类路径中必须存在Servlet.class``DispatcherServlet.class``WebMvcConfigurer.classWebMvcAutoConfiguration自动配置类才会生效。
  • @ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
    • 类路径中不存在WebMvcConfigurationSupport.classWebMvcAutoConfiguration自动配置类才会生效。
    • 注意:当使用@EnableWebMvc注解后,类路径中就会注册一个WebMvcConfigurationSupport这样的bean。
  • @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10) 不重要
    • 指定WebMvcAutoConfiguration自动配置类的加载顺序
  • @ImportRuntimeHints(WebResourcesRuntimeHints.class) 不重要
    • 运行时引入WebResourcesRuntimeHints这个类,这个类的作用是给JVM或者其他组件提示信息的,提示一下系统应该如何处理类和资源。

总结来说,WebMvcAutoConfiguration类将在以下条件下生效:

  1. 应用程序是一个Servlet类型的Web应用;
  2. 环境中有Servlet、DispatcherServlet和WebMvcConfigurer类;
  3. 容器中没有WebMvcConfigurationSupport的bean。

如果这些条件都满足的话,那么这个自动配置类就会被激活,并进行相应的自动配置工作。

WebMvc自动配置生效后引入了两个Filter Bean

引入了HiddenHttpMethodFilter Bean
1
2
3
4
5
6
@Bean
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled")
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
return new OrderedHiddenHttpMethodFilter();
}

这个过滤器是专门处理Rest请求的。GET POST PUT DELETE请求。

引入了FormContentFilter Bean
1
2
3
4
5
6
@Bean
@ConditionalOnMissingBean(FormContentFilter.class)
@ConditionalOnProperty(prefix = "spring.mvc.formcontent.filter", name = "enabled", matchIfMissing = true)
public OrderedFormContentFilter formContentFilter() {
return new OrderedFormContentFilter();
}

OrderedFormContentFilter 是 Spring Boot 中用于处理 HTTP 请求的一个过滤器,特别是针对 PUT 和 DELETE 请求。这个过滤器的主要作用是在处理 PUT 和 DELETE 请求时,确保如果请求体中有表单格式的数据,这些数据会被正确解析并可用。

比如get请求是在请求行上提交数据,post请求正常在请求体中提交数据,put和delete如果请求体当中有form表单数据提交

WebMvc自动配置生效后引入了WebMvcConfigurer接口的实现类

在SpringBoot框架的WebMvcAutoConfiguration类中提供了一个内部类:WebMvcAutoConfigurationAdapter

![](./images/Spring Boot 3/1730345630650-62e11666-3e95-4c64-9787-c0b18d083c91.png)

SpringBoot在这个类WebMvcAutoConfigurationAdapter中进行了一系列的Spring MVC相关配置。

我们开发中要对Spring MVC的相关配置进行修改,可以编写一个类继承WebMvcAutoConfigurationAdatper,然后重写对应的方法即可。

因此,通过对WebMvcAutoConfigurationAdapter</font>类中的方法进行重写来修改Web MVC的默认配置。

![image-20250430153936545](./images/Spring Boot 3/image-20250430153936545.png)

关于WebMvcConfigurer接口

这个接口不是SpringBoot框架提供的,是Spring MVC提供的,在Spring框架4.3版本中引入的。这个接口的作用主要是允许开发者通过实现这个接口来定制Spring MVC的行为

在这个接口中提供了很多方法,需要改变Spring MVC的哪个行为,则重写对应的方法即可,下面是这个接口中所有的方法,以及每个方法对应的Spring MVC行为的解释:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public interface WebMvcConfigurer {
// 用于定制 Spring MVC 如何匹配请求路径到控制器
default void configurePathMatch(PathMatchConfigurer configurer) {}
// 用于定制 Spring MVC 的内容协商策略,以确定如何根据请求的内容类型来选择合适的处理方法或返回数据格式
default void configureContentNegotiation(ContentNegotiationConfigurer configurer) {}
// 用于定制 Spring MVC 处理异步请求的方式
default void configureAsyncSupport(AsyncSupportConfigurer configurer) {}
// 用于定制是否将某些静态资源请求转发WEB容器默认的Servlet处理
default void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {}
// 用于定制 Spring MVC 解析视图的方式,以确定如何将控制器返回的视图名称转换为实际的视图资源。
default void configureViewResolvers(ViewResolverRegistry registry) {}
// 用于定制 Spring MVC 如何处理 HTTP 请求和响应的数据格式,包括 JSON、XML 等内容类型的转换
default void configureMessageConverters(List<HttpMessageConverter<?>> converters) {}
// 用于定制 Spring MVC 如何处理控制器方法中发生的异常,并提供相应的错误处理逻辑。
default void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {}

// 用于定制 Spring MVC 如何处理数据的格式化和解析,例如日期、数值等类型的对象的输入和输出格式。
default void addFormatters(FormatterRegistry registry) {}
// 用于定制 Spring MVC 如何使用拦截器来处理请求和响应,包括在请求进入控制器之前和之后执行特定的操作。
default void addInterceptors(InterceptorRegistry registry) {}
// 用于定制 Spring MVC 如何处理静态资源(如 CSS、JavaScript、图片等文件)的请求。
default void addResourceHandlers(ResourceHandlerRegistry registry) {}
// 用于定制 Spring MVC 如何处理跨域请求,确保应用程序可以正确地响应来自不同域名的 AJAX 请求或其他跨域请求。
default void addCorsMappings(CorsRegistry registry) {}
// 用于快速定义简单的 URL 到视图的映射,而无需编写完整的控制器类和方法。
default void addViewControllers(ViewControllerRegistry registry) {}
// 用于定制 Spring MVC 如何解析控制器方法中的参数,包括如何从请求中获取并转换参数值。
default void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {}
// 用于定制 Spring MVC 如何处理控制器方法的返回值,包括如何将返回值转换为实际的 HTTP 响应。
default void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> handlers) {}

// 用于定制 Spring MVC 如何处理 HTTP 请求和响应的数据格式,允许你添加或调整默认的消息转换器,以支持特定的数据格式。
default void extendMessageConverters(List<HttpMessageConverter<?>> converters) {}
// 用于定制 Spring MVC 如何处理控制器方法中抛出的异常,允许你添加额外的异常处理逻辑。
default void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {}
}

![](./images/Spring Boot 3/1730804471502-31c64115-c49a-4d76-92e0-90e9ae543b32-1746000464694-3.png)

WebMvcConfigurer接口的实现类WebMvcAutoConfigurationAdapter

WebMvcAutoConfigurationAdapter是Spring Boot框架提供的,实现了Spring MVC中的WebMvcConfigurer接口,对Spring MVC的所有行为进行了默认的配置。

如果想要改变这些默认配置,应该怎么办呢?看源码:

![](./images/Spring Boot 3/1730353008999-076f3b3f-2a0b-4936-a9f2-5d6cfe88b346.png)

可以看到,该类上有一个注解@EnableConfigurationProperties({ WebMvcProperties.class, WebProperties.class }),该注解负责启用配置属性。会将配置文件application.propertiesapplication.yml中的配置传递到该类中。因此可以通过application.propertiesapplication.yml配置文件来改变Spring Boot对SpringMVC的默认配置。WebMvcPropertiesWebProperties源码如下:

![](./images/Spring Boot 3/1730354494823-2adf83a4-aa2a-4fed-a5df-66b156de15c5.png)

![](./images/Spring Boot 3/1730354507221-6424a72a-0728-478b-92eb-e52d3d5a6f38.png)

通过以上源码得知要改变SpringBoot对SpringMVC的默认配置,需要在配置文件中使用以下前缀的配置:

  • spring.mvc:主要用于配置 Spring MVC 的相关行为,例如路径匹配、视图解析、静态资源处理等
  • spring.web:通常用于配置一些通用的 Web 层设置,如资源处理、安全性配置等。

自动配置中的静态资源处理

web站点中的静态资源指的是:js、css、图片等。

静态资源处理源码分析

关于SpringBoot对静态资源处理的默认配置,查看WebMvcAutoConfigurationAdapter源码,核心源码如下:

![](./images/Spring Boot 3/1730356581883-80169fed-c487-4b68-a1f2-70ef95d6c9a6.png)

对以上源码进行解释:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {

// 检查 resourceProperties 中的 addMappings 属性是否为 false。如果为 false,则表示不启用默认的静态资源映射处理。
// 在application.properties配置文件中进行`spring.web.resources.add-mappings=false`配置,可以将其设置为false。
// 当然,如果没有配置的话,默认值是true。
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
return;
}

// 配置 WebJars 的静态资源处理。
// this.mvcProperties.getWebjarsPathPattern()的执行结果是:/webjars/**
// 也就是说,如果请求路径是 http://localhost:8080/webjars/** ,则自动去类路径下的 /META-INF/resources/webjars/ 目录中找静态资源。
// 如果要改变这个默认的配置,需要在application.properties文件中进行这样的配置:`spring.mvc.webjars-path-pattern=...`
addResourceHandler(registry, this.mvcProperties.getWebjarsPathPattern(),
"classpath:/META-INF/resources/webjars/");

// 配置普通静态资源处理
// this.mvcProperties.getStaticPathPattern()的执行结果是:/**
// this.resourceProperties.getStaticLocations()的执行结果是:{ "classpath:/META-INF/resources/","classpath:/resources/", "classpath:/static/", "classpath:/public/" }
// 也就是说,如果请求路径是:http://localhost:8080/**,根据控制器方法优先原则,会先去找合适的控制器方法,如果没有合适的控制器方法,静态资源处理才会生效,则自动去类路径下的/META-INF/resources/、/resources/、/static/、/public/ 4个位置找。
// 如果要改变这个默认的配置,需要在application.properties中进行如下的两个配置:
// 配置URL:spring.mvc.static-path-pattern=...
// 配置物理路径:spring.web.resources.static-locations=...,...,...,...
addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
registration.addResourceLocations(this.resourceProperties.getStaticLocations());
if (this.servletContext != null) {
ServletContextResource resource = new ServletContextResource(this.servletContext, SERVLET_LOCATION);
registration.addResourceLocations(resource);
}
});
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 让springboot的静态资源处理失效
#spring.web.resources.add-mappings=false
#spring.web.resources.add-mappings=true


# 配置静态资源webjars的path pattern
#spring.mvc.webjars-path-pattern=/webjars/**
spring.mvc.webjars-path-pattern=/wjs/**


# 配置普通静态资源的path pattern
#spring.mvc.static-path-pattern=/**
#spring.mvc.static-path-pattern=/static/**
# 配置普通静态资源文件存储位置
#spring.web.resources.static-locations=classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/

# 配置普通静态资源的path pattern
#spring.mvc.static-path-pattern=/abc/**
# 配置普通静态资源文件存储位置
#spring.web.resources.static-locations=classpath:/static1/,classpath:/static2/

关于WebJars静态资源处理

默认规则是:当请求路径是/webjars/**,则会去classpath:/META-INF/resources/webjars/找。前面的路径可以更改,后面的是固定的无法更改

WebJars介绍

WebJars 是一种将常用的前端库(如 jQuery、Bootstrap、Font Awesome 等)打包成 JAR 文件的形式,方便在 Java 应用程序中使用。WebJars 提供了一种标准化的方式来管理前端库,使其更容易集成到 Java 项目中,并且可以利用 Maven 的依赖管理功能。

WebJars在SpringBoot中的使用

WebJars官网:https://www.webjars.org/

![](./images/Spring Boot 3/1730364165294-a000b7b4-9fdb-4c40-99f5-c47f2f25ad9e.png)

![](./images/Spring Boot 3/1730804471502-31c64115-c49a-4d76-92e0-90e9ae543b32-1746088949741-11.png)

在官网上可以找到某个webjars的maven依赖,将依赖加入到SpringBoot项目中,例如我们添加vue的依赖:

1
2
3
4
5
6
<!--webjars:将前端库打成jar包,方便在java项目中引入,也可以使用maven进行依赖管理。-->
<dependency>
<groupId>org.webjars.npm</groupId>
<artifactId>vue</artifactId>
<version>3.5.13</version>
</dependency>

如下图表示加入成功:

![](./images/Spring Boot 3/1730364253405-dc6801f0-6122-49eb-9e77-36ef92668f5b.png)

在jar包列表中也可以看到:

![](./images/Spring Boot 3/1730364333436-43d60b44-fb7b-454d-b586-f948930ea146.png)

![](./images/Spring Boot 3/1730804471502-31c64115-c49a-4d76-92e0-90e9ae543b32-1746088949741-11.png)

在SpringBoot中,对WebJars的默认访问规则是:当请求路径是/webjars/**,则会去classpath:/META-INF/resources/webjars/找。

因此我们要想访问上图的index.js,则应该发送这样的请求路径:http://localhost:8080/webjars/vue/3.5.12/index.js

启动服务器,打开浏览器,访问,测试结果如下:

![](./images/Spring Boot 3/1730364535656-fd9fb574-ce9d-4278-96a4-7d71207e1446.png)

![](./images/Spring Boot 3/1730804471502-31c64115-c49a-4d76-92e0-90e9ae543b32-1746088949741-11.png)

和IDEA中的文件对比一下,完全一样则表示测试成功:

![](./images/Spring Boot 3/1730364567084-7ac48323-e220-4e10-a4b6-dc5a30ab6955.png)

![](./images/Spring Boot 3/1730804471502-31c64115-c49a-4d76-92e0-90e9ae543b32-1746088949741-11.png)

关于普通静态资源处理

SpringBoot对普通静态资源处理的规则是:

当请求路径是http://localhost:8080/**,根据控制器方法优先原则,会先去找合适的控制器方法,如果没有合适的控制器方法,静态资源处理才会生效,则自动去类路径下的以下4个位置查找:

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

我们可以在项目中分别创建以上4个目录,在4个目录当中放入静态资源,例如4张图片:

![image-20250502145100506](./images/Spring Boot 3/image-20250502145100506.png)

![image-20250502145102928](./images/Spring Boot 3/image-20250502145102928.png)

然后启动服务器,打开浏览器,访问,测试是否可以正常访问图片:

![](./images/Spring Boot 3/1730365066712-a89265e6-eb88-4306-b95b-bc85d2e72f9d.png)

关于静态资源缓存处理

不管是webjars的静态资源还是普通静态资源,统一都会执行以下这个方法,这个方法最后几行代码就是关于静态资源的缓存处理方式。

![](./images/Spring Boot 3/1730362324912-ff074309-5a29-4ee9-84df-804311a07314.png)

什么是静态资源缓存,谁缓存,有什么用?

静态资源缓存指的是浏览器的缓存行为,浏览器可以将静态资源(js、css、图片、声音、视频)缓存到浏览器中,只要下一次用户访问同样的静态资源直接从缓存中取,不再从服务器中获取,可以降低服务器的压力,提高用户的体验。而这个缓存策略可以在服务器端程序中进行设置,SpringBoot对静态资源缓存的默认策略就是以下这三行代码:

![](./images/Spring Boot 3/1730365697951-98a6687d-dc95-49fe-bf63-6afac28ac62b.png)

以上三行代码的解释如下:

  • registration.setCachePeriod(getSeconds(this.resourceProperties.getCache().getPeriod()));
    • 设置缓存的过期时间(如果没有指定单位,默认单位是秒)
    • 浏览器会根据响应头中的缓存控制信息决定是否从本地缓存中加载资源,而不是每次都从服务器重新请求。这有助于减少网络流量和提高页面加载速度。
    • 假设你配置了静态资源缓存过期时间为 1 小时(3600 秒),那么浏览器在首次请求某个静态资源后,会在接下来的一小时内从本地缓存加载该资源,而不是重新请求服务器。
    • 可以通过application.properties的来修改默认的过期时间,例如:spring.web.resources.cache.period=3600或者spring.web.resources.cache.period=1h
  • registration.setCacheControl(this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl());
    • 设置静态资源的 Cache-Control HTTP 响应头,告诉浏览器如何去缓存这些资源。
    • Cache-Control HTTP 响应头 是HTTP响应协议的一部分内容。如下图响应协议的响应头信息中即可看到Cache-Control的字样:

![](./images/Spring Boot 3/1730367060571-fb49d8ba-39d5-4a04-9c6b-cbce0add9283.png)

常见的 Cache-Control 指令包括:
    * max-age=<seconds>:表示响应在多少秒内有效。
    * public:表示响应可以被任何缓存机制(如代理服务器)缓存。
    * private:表示响应只能被用户的浏览器缓存
    * no-cache:表示在使用缓存的资源之前必须重新发送一次请求进行验证。
    * no-store:表示不缓存任何响应的资源。
- 例如:max-age=3600, public:表示响应在 3600 秒内有效,并且可以被任何缓存机制缓存。
- 可以通过`spring.web.resources.cache.cachecontrol.max-age=3600`以及`spring.web.resources.cache.cachecontrol.cache-public=true`进行重新配置。
  • registration.setUseLastModified(this.resourceProperties.getCache().isUseLastModified());
    • 设置静态资源在响应时,是否在响应头中添加资源的最后一次修改时间。SpringBoot默认配置的是:在响应头中添加响应资源的最后一次修改时间。
    • 浏览器发送请求时,会将缓存中的资源的最后修改时间和服务器端资源的最后一次修改时间进行比对,如果没有变化,仍然从缓存中获取。
    • 可以通过spring.web.resources.cache.use-last-modified=false来进行重新配置。

静态资源缓存测试

根据之前源码分析,得知静态资源缓存相关的配置应该使用spring.web.resources.cache

![](./images/Spring Boot 3/1730429586708-40337969-1725-4459-879b-2299ab2a4405.png)

![](./images/Spring Boot 3/1730429704722-ad0a5409-038f-48f1-9986-809c24f82369.png)

![](./images/Spring Boot 3/1730804471502-31c64115-c49a-4d76-92e0-90e9ae543b32-1746172740747-15.png)

![](./images/Spring Boot 3/1730429724705-e10888fe-f2aa-4b87-9581-e2641c3e30fd.png)

![](./images/Spring Boot 3/1730429747795-51fcc7b3-9dc6-49e3-8cbf-647cfff03b09.png)

application.properties文件中对缓存进行如下的配置:

1
2
3
4
5
6
7
8
9
10
# 静态资源缓存设置
# 1. 缓存有效期
#spring.web.resources.cache.period=3600
spring.web.resources.cache.period=100
# 2. 缓存控制(cachecontrol配置的话,period会被覆盖失效)
spring.web.resources.cache.cachecontrol.max-age=20
# 3. 是否使用缓存的最后修改时间(默认是:使用)
spring.web.resources.cache.use-last-modified=true
# 4. 是否开启静态资源默认处理方式(默认是:开启)
spring.web.resources.add-mappings=true

注意:cachecontrol.max-age配置的话,period会被覆盖。

![](./images/Spring Boot 3/1730430084806-2086f0b6-646a-4e6a-a39c-8fa8dba74be0-1746172755878-26.png)

启动服务器测试:看看是否在20秒内走缓存,20秒之后是不是就不走缓存了!!!

第一次访问:请求服务器

![image-20250502160244349](./images/Spring Boot 3/image-20250502160244349.png)

第二次访问:20秒内开启一个新的浏览器窗口,再次访问,发现走了缓存

![image-20250502160251111](./images/Spring Boot 3/image-20250502160251111.png)

第三次访问:20秒后开启一个新的浏览器窗口,再次访问,发现重新请求服务器

![image-20250502160257426](./images/Spring Boot 3/image-20250502160257426.png)

提示,为什么显示304,这是因为这个配置:spring.web.resources.cache.use-last-modified=true要去和最后一次修改作对比

web应用的欢迎页面

欢迎页测试

先说结论:只要在静态资源路径下提供index.html,默认被当做欢迎页面。静态资源路径指的是之前的4个路径:

1
{ "classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/" }

测试一下,在classpath:/static/目录下新建index.html页面:

![](./images/Spring Boot 3/1730422047114-993504b3-e8b2-4570-a789-53d1427bb0be.png)

启动服务器,测试结果如下:

![](./images/Spring Boot 3/1730422096295-c5fedb9e-df71-4cac-9a24-f2d6da9b071f.png)

如果同时在4个静态资源路径下都提供index.html,哪个页面会被当做欢迎页呢?

![](./images/Spring Boot 3/1730422239619-ba967027-498c-4f90-a139-c1565c91328d.png)

启动服务器,测试结果如下:

![](./images/Spring Boot 3/1730422275754-3b1e145f-f34b-4eac-ae6f-0187eb852282.png)

原因是什么呢?这是因为classpath:/META-INF/resources/是数组的首元素,因此先从这个路径下找欢迎页。

![](./images/Spring Boot 3/1730422431137-93db3613-613e-434b-b7b8-c9f1af68636c.png)

欢迎页源码分析

WebMvcAutoConfiguration类中有一个内部类EnableWebMvcConfiguration,这个类中有这样一段代码:

![](./images/Spring Boot 3/1730424626175-06c18960-d1bc-4ea7-a9f2-84079dd105ca.png)

![](./images/Spring Boot 3/1730424702185-b359161c-2221-4106-837c-1a6a4bb16683.png)

![](./images/Spring Boot 3/1730424718260-3d97f911-f050-42a6-b2c3-78a739d39f2d.png)

![](./images/Spring Boot 3/1730424745404-720c4fe8-118e-4133-9514-efab29e7e39e.png)

通过以上源码追踪,得出结论:只要请求路径是/**的,会依次去{ "classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/" }这四个位置找index.html页面作为欢迎页。

一个小小的疑惑

我们来看一下WebMvcAutoConfiguration的生效条件:

![](./images/Spring Boot 3/1730424986145-0d76b7da-06c5-4942-a5f2-3e55f0249c89.png)

上图红框内表示,要求Spring容器中缺失WebMvcConfigurationSupport这个Bean,WebMvcAutoConfiguration才会生效。

![](./images/Spring Boot 3/1730804471502-31c64115-c49a-4d76-92e0-90e9ae543b32-1746178039575-48.png)

但是我们来看一下EnableWebMvcConfiguration的继承结构:

![](./images/Spring Boot 3/1730425120392-dda2febe-d932-452c-9599-6df5859a2062.png)

![image-20250502203407401](./images/Spring Boot 3/image-20250502203407401.png)

很明显,EnableWebMvcConfiguration就是一个WebMvcConfigurationSupport这样的Bean。

那疑问就有了:既然容器中存在WebMvcConfigurationSupport这样的Bean,WebMvcAutoConfiguration为什么还会生效呢?

原因是因为:EnableWebMvcConfigurationWebMvcAutoConfiguration类的内部类。在WebMvcAutoConfiguration进行加载的时候,EnableWebMvcConfiguration这个内部类还没有加载。因此这个时候在容器中还不存在WebMvcConfigurationSupport的Bean,所以WebMvcAutoConfiguration仍然会生效。

以上所说的WebMvcAutoConfiguration类中的内部类EnableWebMvcConfiguration,是用来启用Web MVC默认配置的。

注意区分:WebMvcAutoConfiguration的两个内部类:

  • WebMvcAutoConfigurationAdapter作用是用来:修改配置的
  • EnableWebMvcConfiguration作用是用来:启用配置的
favorite icon(小图标)

favicon(也称为“收藏夹图标”或“网站图标”)是大多数现代网页浏览器的默认行为之一。当用户访问一个网站时,浏览器通常会尝试从该网站的根目录下载名为 favicon.ico 的文件,并将其用作标签页的图标。

如果网站没有提供 favicon.ico 文件,浏览器可能会显示一个默认图标,或者根本不显示任何图标。为了确保良好的用户体验,网站开发者通常会在网站的根目录下放置一个 favicon.ico 文件。

与其他静态资源一样,Spring Boot 会在配置的静态内容位置检查是否存在 favicon.ico文件。如果存在这样的文件,它将自动作为应用程序的 favicon 使用。

以上官方说明的:将favicon.ico文件放到静态资源路径下即可。

web站点没有提供favicon.ico时:

![](./images/Spring Boot 3/1730427725460-6c4062dc-df53-495c-8709-431dd38f2337.png)

我们在https://www.iconfont.cn/ (阿里巴巴提供的图标库)上随便找一个图标,然后将图片名字命名为favicon.ico,然后将其放到SpringBoot项目的静态资源路径下:

![](./images/Spring Boot 3/1730427919469-60975be4-d0d3-43ba-8435-98d9da427282.png)

启动服务器测试:记住(ctrl + F5强行刷新一下,避免影响测试效果)

![](./images/Spring Boot 3/1730428251364-894a09fe-92fb-408c-89d0-42d8e27b222b.png)

SpringBoot的web手动配置(静态资源处理)

如果你对SpringBoot默认的静态资源处理方式不满意。可以通过两种方式来改变这些默认的配置:

  • 如果你对SpringBoot默认的静态资源处理方式不满意。可以通过两种方式来改变这些默认的配置:

    • 第一种:配置文件方式
      • 通过修改application.propertiesapplication.yml
      • 添加spring.mvcspring.web相关的配置。

    ![](./images/Spring Boot 3/1730431752471-f806b5a3-924c-4b84-9c13-6999dad0493b-1746261760607-7.png)

    ![](./images/Spring Boot 3/1730431765824-5d3ec11d-aad5-44c8-a32e-dd255bec24ea-1746261760607-9.png)

    ![](./images/Spring Boot 3/1730431775131-4bec5477-fd86-4ab2-afa1-4b1927b2aafc-1746261760607-11.png)

    • 第二种:编写代码方式
      • SpringMVC框架为我们提供了WebMvcConfigurer接口,需要改变默认的行为,可以编写一个类实现WebMvcConfigurer接口,并对应重写接口中的方法即可改变默认的配置行为。

配置文件方式

要修改访问静态资源URL的前缀,这样配置:

1
2
3
4
5
6
7
# 让springboot的静态资源处理失效,这个不影响META-INF
#spring.web.resources.add-mappings=true
# Spring MVC的相关配置
# 1. 设置webjars静态资源的请求路径的前缀
spring.mvc.webjars-path-pattern=/wjs/**
# 2. 设置普通静态资源的请求路径的前缀(path-pattern),也就是浏览器的链接
spring.mvc.static-path-pattern=/abc/**

要修改静态资源的存放位置,这样配置:

1
spring.web.resources.static-locations=classpath:/static1/,classpath:/static2/

进行以上配置之后:

  1. 访问webjars的请求路径应该是这样的:http://localhost:8080/wjs/….
  2. 访问普通静态资源的请求路径应该是这样的:http://localhost:8080/static/….
  3. 普通静态资源的存放位置也应该放到classpath:/static1/,classpath:/static2/下面,其他位置无效。

访问webjars测试结果如下:

![](./images/Spring Boot 3/1730433063487-fe214e35-a43c-4ad0-9760-742f6bfcdcf6.png)

访问普通静态资源测试结果如下:

![](./images/Spring Boot 3/1730433000967-37e76b55-5715-4387-a135-5daacd53a755.png)

![image-20250503164646779](./images/Spring Boot 3/image-20250503164646779.png)

如果访问dog2.jpg,就无法访问了:

![image-20250503170828711](./images/Spring Boot 3/image-20250503170828711.png)

但是,存储在classpath:/META-INF/resources/目录下的dog1.jpg仍然是可以访问的:

![](./images/Spring Boot 3/1730433537233-e7fe1330-22d3-4b36-8c43-bbdc9a578f99.png)

因此,存储在classpath:/META-INF/resources/位置的静态资源会被默认加载,不受手动配置的影响。

编写代码方式

我们在前面提到过,想要定制Spring MVC的行为,也可以编写类实现Spring MVC框架提供的一个接口WebMvcConfigurer,想定制哪个行为就重写哪个方法即可。

编写的类只要纳入IoC容器的管理。因此有以下两种实现方式:

  • 第一种:编写类实现WebMvcConfigurer接口,重写对应的方法。
  • 第二种:以组件的形式存在:编写一个方法,用@Bean注解标注。

第一种方式

编写配置类,对于web开发来说,配置类一般起名为:WebConfig。配置类一般存放到config包下,因此在SpringBoot主入口程序同级目录下新建config包,在config包下新建WebConfig

![image-20250504032848114](./images/Spring Boot 3/image-20250504032848114.png)

重写使用快捷键ctrl+o然后搜addResourceHandlers

1
2
3
4
5
6
7
8
9
10
11
12
13
// 这里的这种配置:在springboot默认的自动配置基础之上进行扩展配置。也就是说springboot之前的配置仍然生效。
// 使用该注解标注,表示该类为配置类。
//@Configuration
public class WebConfig implements WebMvcConfigurer {

// 静态资源处理,需要重写的方法是:addResourceHandlers
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// 使用注册器 registry 绑定path pattern以及真实的静态资源文件存储路径
registry.addResourceHandler("/abc/**") // 配置路径访问模式
.addResourceLocations("classpath:/static1/", "classpath:/static2/"); // 配置静态资源路径
}
}

注意:将application.properties文件中之前的所有配置全部注释掉。让其恢复到最原始的默认配置。

![image-20250504033257856](./images/Spring Boot 3/image-20250504033257856.png)

启动服务器进行测试:

![](./images/Spring Boot 3/1730444208880-ca582b52-a68f-4918-9ead-f342c5ebbf38.png)

![](./images/Spring Boot 3/1730444226844-d1e40613-8c6c-43a5-95a1-1c0d34d101bb.png)

通过测试,我们的配置是生效的。

我们再来看看,默认的配置是否还生效?

![](./images/Spring Boot 3/1730444289901-3304e315-67e2-4d6c-93b8-0db5f48a24a7.png)

我们可以看到,Spring Boot对Spring MVC的默认自动配置是生效的。

因此,以上的方式只是在Spring MVC默认行为之外扩展行为。

如果你不想再继续使用SpringBoot提供的默认行为,可以使用@EnableWebMvc进行标注。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 添加这个注解之后,表示不在使用springboot的提供的默认的自动配置。
@EnableWebMvc
// 以下配置完全使用自己的,不在基于springboot的配置进行扩展。
@Configuration
public class WebConfig implements WebMvcConfigurer {

// 静态资源处理,需要重写的方法是:addResourceHandlers
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// 使用注册器 registry 绑定path pattern以及真实的静态资源文件存储路径
registry.addResourceHandler("/abc/**") // 配置路径访问模式
.addResourceLocations("classpath:/static1/", "classpath:/static2/"); // 配置静态资源路径
}

}

测试结果:

![](./images/Spring Boot 3/1730444605358-87fe9bc0-edfb-4fff-9bc0-ae4a97b50042.png)

可以看到,默认配置已经不再生效。

再来看看,我们自己的配置是否仍然生效:

![](./images/Spring Boot 3/1730444650423-57b49f40-b595-4c27-935e-1c6361f20ba4.png)

仍然生效。

第二种方式

采用@Bean注解提供一个WebMvcConfigurer组件,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration

public class WebConfig2 {

@Bean
public WebMvcConfigurer aaa(){//方法名随意
return new WebMvcConfigurer() {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/xyz/**")
.addResourceLocations("classpath:/static1/", "classpath:/static2/");
}
};
}
}

测试结果如下:

![](./images/Spring Boot 3/1730444971338-0aca22a6-ff00-4644-9e21-2d08765b62e4.png)

通过了测试,并且以上代码也是在原有配置基础上进行扩展。

如果要弃用默认配置,仍然使用 @EnableWebMvc注解进行标注。自行测试!

其他配置也这样做即可

以上对静态资源处理进行了手动配置,也可以做其他配置,例如拦截器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

//@EnableWebMvc
@Configuration
public class WebConfig2 {
//拦截器配置
@Bean
public WebMvcConfigurer bbb(){
return new WebMvcConfigurer() {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new HandlerInterceptor() {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("拦截器的preHandle");
return true;
}

@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("拦截器的postHandle");
}

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("拦截器的afterCompletion");
}
});
}
};
}
}

启动服务器,打开浏览器,发送请求http://localhost:8080/static/dog5.jpg,后台执行结果如下:

![](./images/Spring Boot 3/1730445490551-7c8fbe3f-a792-4307-8c46-21a365e0b25d.png)

这说明拦截器生效。

为什么只要容器中有WebMvcConfigurer组件即可呢

源码分析:

WebMvcAutoConfiguration部分源码:

![](./images/Spring Boot 3/1730446108216-7ff9fb75-c9dd-4ba3-8b59-23f6c78d0b1a.png)

WebMvcAutoConfiguration类的内部类EnableWebMvcConfiguration,这个类继承了DelegatingWebMvcConfiguration(Delegating是委派的意思。)

DelegatingWebMvcConfiguration中的setConfigurers()方法用来设置配置。而配置参数是一个List集合,这个List集合中存放的是WebMvcConfigurer接口的实例,并且可以看到这个方法上面使用了@Autowired进行了自动注入,这也就是说为什么只要是IoC容器中的组件就能生效的原因。

我们再次进入到this.configurers.addWebMvcConfigurers(configurers);方法中进一步查看源码:

![](./images/Spring Boot 3/1730446847673-e2758f97-fcc9-4c72-bc9c-ea4463afe751.png)

对于WebMvcConfigurerComposite类的代码来说,它是一个非常典型的**<font style="color:#DF2A3F;">组合模式</font>**

组合模式的关键点:

  1. 组合多个 WebMvcConfigurer 实例:WebMvcConfigurerComposite 通过 delegates 列表组合了多个 WebMvcConfigurer 实例。
  2. 统一接口:WebMvcConfigurerComposite 实现了 WebMvcConfigurer 接口,因此可以像一个单一的 WebMvcConfigurer 一样被使用。
  3. 代理调用:在实现 WebMvcConfigurer 接口的方法时,WebMvcConfigurerComposite 会遍历 delegates 列表,调用每个 WebMvcConfigurer 实例的相应方法。

总结:WebMvcConfigurerComposite 主要采用了组合模式的思想,将多个 WebMvcConfigurer 实例组合在一起,形成一个整体。

注意:组合模式是GoF 23种设计模式中的结构型设计模式。

web请求的路径匹配

我们在学习SpringMVC的时候,路径匹配规则中学习了Ant风格的路径匹配规则。大家可以翻看一下之前的Spring MVC视频。

Spring Boot3中,对web请求的路径匹配提供了两种规则:

  • 第一种:AntPathMatcher(Ant风格)【较旧
  • 第二种:PathPatternParser(从Spring5.3中引入的。在SpringBoot2.4中引入的。)【较新:效率高
    • 效率比Ant高,一般新项目中使用

SpringBoot3中默认使用的是PathPatternParser,不需要任何配置。如果要使用AntPathMatcher,就需要进行如下的配置:

1
spring.mvc.pathmatch.matching-strategy=ant_path_matcher

AntPathMatcher

Ant风格的路径匹配规则回顾:

* 匹配任意长度的任意字符序列(不包括路径分隔符)。示例:/foo/*.html 匹配 /foo/bar.html 和 /foo/baz.html。

****** 匹配任意数量的目录层级。示例:/foo/** 匹配 /foo/bar、/foo/bar/baz 和 /foo/bar/baz/qux。

? 匹配任意单个字符**。示例:/foo?bar 匹配 /foobar 和 /fooxbar。

[] 匹配指定范围内的单个字符。示例:/foo[a-z]bar 匹配 /fooabar、/foobbar 等。

{} 路径变量,用于提取路径的一部分作为参数。示例:/users/{userId} 匹配 /users/123,提取 userId=123。

![](./images/Spring Boot 3/1730804471502-31c64115-c49a-4d76-92e0-90e9ae543b32-1746344666214-9.png)

如果在SpringBoot3中启用Ant风格,记得配置:

1
spring.mvc.pathmatch.matching-strategy=ant_path_matcher

如下代码:请分析以下路径匹配的是什么样的路径。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@RestController
public class UserController {

//+代表1-n个字符,如果去掉就是任意一个字符。
//?一个字符
//**任意层级
//最后必须是.do结尾
@GetMapping("/{path:[a-z]+}/a?/**/*.do")
//HttpServletRequest是请求对象,后面是拿到用户传进来的path
public String testPath(HttpServletRequest request, @PathVariable String path){
String requsetURI = request.getRequestURI();
return requsetURI + "path = " + path;
}
}

启动服务器并测试路径:

![](./images/Spring Boot 3/1730472107928-af151fcc-d9b2-439a-825e-660f87f2c952.png)

PathPatternParser

项目中不做配置,或者按照以下方式配置,都是PathPatternParser

1
spring.mvc.pathmatch.matching-strategy=path_pattern_parser

PathPatternParser风格是兼容Ant风格的。只有一个地方PathPatternParser不支持,Ant支持。在Ant风格中,**可以出现在任意位置。在PathPatternParser中只允许**出现在路径的末尾。

可以测试一下,将配置文件中的Ant风格注释掉,采用PathPatternParser风格。然后控制器代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.powernode.springboot.controller;

import jakarta.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class PathController {

@GetMapping("/{path:[a-z]+}/a?/**/*.do")
public String path(HttpServletRequest request, @PathVariable String path){
return request.getRequestURI() + "," + path;
}
}

启动服务器报错:

![](./images/Spring Boot 3/1730472557923-8f34cf00-6454-465b-b5e8-562520bbf095.png)

提示你,如果在路径当中出现了**,需要将路径匹配规则替换为Ant风格。因此路径当中如果出现**,那么必须使用Ant风格。除此之外,PathPatternParser均可用。

我们再来测试一下,**放到末尾,对于PathPatternParser是否可用?

1
2
3
4
@GetMapping("/{path:[a-z]+}/a?/*.do/**")
public String path(HttpServletRequest request, @PathVariable String path){
return request.getRequestURI() + "," + path;
}

启动服务器测试,可用:

![](./images/Spring Boot 3/1730472772231-d18e628f-1b62-4757-9299-eaa604344ce6.png)

路径匹配相关源码

底层选择路径匹配规则的源码是:

![image-20250505170404560](./images/Spring Boot 3/image-20250505170404560.png)

内容协商

内容协商机制是Spring MVC框架提供的,接下来主要是学习在SpringBoot中是如何支持SpringMVC内容协商机制的。

对内容协商的理解

内容协商机制是指服务器根据客户端的请求来决定返回资源的最佳表示形式。

白话文描述:客户端要什么格式的数据,咱后端就应该返回什么格式的数据。

  • 客户端要JSON,咱就响应JSON。
  • 客户端要XML,咱就响应XML。
  • 客户端要YAML,咱就响应YAML。

你可能会有疑问:客户端接收数据时统一采用一种格式,例如JSON,不就行了吗。哪那么多事儿呀!!!

但在实际的开发中,不是这样的,例如:

  • 遗留的老客户端系统,仍然处理的是XML格式的数据。
  • 要求处理速度快的这种客户端系统,一般要求返回JSON格式的数据。
  • 要求安全性高的客户端系统,一般要求返回XML格式的数据。

因此,在现代的开发中,不同的客户端可能需要后端系统返回不同格式的数据。总之后端应该满足这种多样化的需求。

实现内容协商的两种方式

通常通过HTTP请求头(如 Accept)或请求参数(如 format)来指定客户端偏好接收的内容类型(如JSON、XML等)。服务器会根据这些信息选择最合适的格式进行响应。

通过HTTP请求头(如 Accept)

SpringBoot框架中,在程序员不做任何配置的情况下,优先考虑的是这种方式。

服务器会根据客户端发送请求时提交的请求头中的”Accept: application/json” 或 “Accept: application/xml” 或 “Accept: text/html”来决定响应什么格式的数据。

客户端发送请求给服务器的时候,如何设置请求头的Accept?有以下几种常见实现方式:

  • 写代码
    • fetch API
    • ajax的XMLHttpRequest
    • axios库
    • jQuery库……
  • 用工具
    • 接口测试工具,例如:Postman、Apifox等。
    • 命令行工具:curl

对于我们编写的以下Controller来说:

1
2
3
4
5
6
7
8
9
10
11
@RestController // 相当于@Controller + @ResponseBody ,默认是将数据转换成json字符串进行响应。
public class UserController {

@Autowired
private UserService userService;

@GetMapping("/detail")
public User detail(){
return userService.findUserById();
}
}

我们使用了@RestController,也就是使用了@ResponseBody。因此默认支持的是返回JSON数据。怎么才能支持返回XML格式的数据呢?需要做以下两步:

第一步:引入一个依赖

1
2
3
4
5

<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>

第二步:在实体类上添加一个注解

1
2
3
4
5
6
7
8
@JacksonXmlRootElement
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private String name;
private String password;
}

然后是接口的实现:

1
2
3
public interface UserService {
User findUserById();
}
1
2
3
4
5
6
7
@Service
public class UserServiceImpl implements UserService {
@Override
public User findUserById() {
return new User("jackson", 30);
}
}

遇到问题:实际参数列表和形式参数列表长度不同、java: 无法将类xxx中的构造器xxx应用到给定类型| lombok相关

idea运行maven项目时,报错这个(如标题)
解决方案记录:
找到了之前的、能运行成功不报错的 maven项目。参考其pom.xml文件中lombok相关部分,将<path>标签下的lombok加个版本号,就运行成功了:

1
2
3
4
5
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.36</version> // 添加了这行
</path>

接下来,我们使用curl命令行工具,来模拟发送请求,并在请求头中设置Accept

![image-20250505173456946](./images/Spring Boot 3/image-20250505173456946.png)

可以很清楚的看到,服务器根据不同的请求头返回了不同格式的数据。

  • Accept: application/xml则返回XML格式的数据
  • Accept: application/json则返回JSON格式的数据

通过请求参数(如 format

接下来我们使用请求参数的方式,来测试一下服务器的响应,注意:默认的请求参数名为format

我们仍然使用curl命令行工具进行测试:

![image-20250507032900090](./images/Spring Boot 3/image-20250507032900090.png)

我们可以看到,并没有达到我们想要的效果,这是因为SpringBoot优先考虑的不是通过请求参数format方式。如何优先考虑使用format方式呢?做如下配置:

1
2
3
# 采用format方式实现内容协商,如果没有配置这个选项,默认采用的是Accept字段方式实现内容协商。
# 注意format参数方式,参数名固定是 format
spring.mvc.contentnegotiation.favor-parameter=true

再次测试:

![image-20250507194814197](./images/Spring Boot 3/image-20250507194814197.png)

可以看到,现在SpringBoot已经优先考虑使用请求参数format方式了。

当然,请求参数的名字可以不使用format吗?支持定制化吗?答案是支持的,例如你希望请求参数的名字为type,可以做如下配置:

1
2
# 内容协商时,设置请求参数的名字,默认为format
spring.mvc.contentnegotiation.parameter-name=type

再次使用curl工具进行测试:

![](./images/Spring Boot 3/1730517197351-b400c853-b45b-474b-9380-53d4d901d697.png)

HttpMessageConverter接口

HttpMessageConverter的理解

HttpMessageConverter接口被翻译为:Http消息转换器。它起到转换Http消息的作用。

什么是Http消息?所谓的Http消息本质上就是浏览器向服务器发送请求时提交的数据,或者服务器向浏览器响应的数据。

HttpMessageConverter接口就是负责完成请求/响应时的数据格式转换的。

在Spring MVC中提供了很多HttpMessageConverter接口的实现类,不同的Http消息转换器具有不同的转换效果,有的负责将Java对象转换为JSON格式的字符串,有的负责将Java对象转换成XML格式的字符串。

常见的HttpMessageConverter

内置的常见的HttpMessageConverter的实现类包括:

  • 【请求】提交的表单(form)数据转换成Java对象的主要任务是由 FormHttpMessageConverter 消息转换器完成的
  • 【请求】提交的JSON数据转换成Java对象的主要任务是由 MappingJackson2HttpMessageConverter 消息转换器完成的。(我们通常使用的<font style="color:#DF2A3F;">@RequestBody</font>注解)
  • 【响应】将Java对象转换成JSON格式的数据,并将其写入HTTP响应体的任务是由 MappingJackson2HttpMessageConverter 消息转换器完成。(我们通常使用的@ResponseBody注解)
  • 【响应】将Java对象转换成XML格式的数据,并将其写入HTTP响应体的任务通常由 Jaxb2RootElementHttpMessageConverter 消息转换器完成。
  • 【响应】将 String 直接写入到响应体的任务是由 StringHttpMessageConverter 消息转换器完成。
  • ……

请求时通过哪些条件确定使用哪个转换器

请求时通常根据以下条件来确定使用哪个消息转换器:

  1. 请求的 Content-Type 字段:

Spring MVC 会检查请求的 Content-Type 字段,以确定请求体的数据格式(例如 application/json、application/x-www-form-urlencoded、application/xml 等)。

  1. 方法参数类型:

控制器方法中接收请求体的参数类型(例如 @RequestBody)。

![image-20250515190721635](./images/Spring Boot 3/image-20250515190721635.png)

响应时通过哪些条件确定使用哪个转换器

响应时通常根据以下条件来确定使用哪个消息转换器:

  1. 请求提交时,请求头上的Accept字段 :

Spring MVC 会检查客户端请求的 Accept 字段,以确定客户端期望的响应格式(例如 application/json、application/xml 等)。

  1. 方法返回值类型:

控制器方法的返回值类型(例如 @ResponseBody)。

例如1:@ResponseBody + 控制器方法的返回值是String,例如public String name(){},则使用StringHttpMessageConverter转换器。(将字符串直接写入响应体)

例如2:@ResponseBody + 控制器方法的返回值是User,例如public User name(){},则使用MappingJackson2HttpMessageConverter转换器。(将java对象转换成json格式的字符串写入到响应体)

![image-20250515190726743](./images/Spring Boot 3/image-20250515190726743.png)

系统默认提供了哪些HttpMessageConverter

查看源码:

WebMvcAutoConfiguration.EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport

WebMvcAutoConfiguration内部类EnableWebMvcConfiguration

EnableWebMvcConfiguration继承了DelegatingWebMvcConfiguration

DelegatingWebMvcConfiguration继承了WebMvcConfigurationSupport

WebMvcConfigurationSupport类中有这样一个方法:addDefaultHttpMessageConverters() 用来添加默认的HttpMessageConverter对象。

通过断点调试,可以发现默认支持6个HttpMessageConverter,如下:

![](./images/Spring Boot 3/1730774897755-ad87ddc8-4700-40e4-baee-a28d94000e7a.png)

这6个HttpMessageConverter作用如下:

  1. ByteArrayHttpMessageConverter:

用于将字节数组(byte[])与HTTP消息体之间进行转换。这通常用于处理二进制数据,如图片或文件。

  1. StringHttpMessageConverter:

用于将字符串(String)与HTTP消息体之间进行转换。它支持多种字符集编码,能够处理纯文本内容。

  1. ResourceHttpMessageConverter:

用于将Spring的Resource对象与HTTP消息体之间进行转换。Resource是Spring中表示资源的接口,可以读取文件等资源。这个转换器对于下载文件或发送静态资源有用。

  1. ResourceRegionHttpMessageConverter:

用于处理资源的部分内容(即“Range”请求),特别是当客户端请求大文件的一部分时。这对于实现视频流媒体等功能很有用。

  1. AllEncompassingFormHttpMessageConverter:

用于处理表单,是一个比较全面的form消息转换器。处理标准的application/x-www-form-urlencoded格式的数据,以及包含文件上传的multipart/form-data格式的数据。

  1. MappingJackson2HttpMessageConverter:

使用Jackson库来序列化和反序列化JSON数据。可以将Java对象转换为JSON格式的字符串,反之亦然。

另外,通过以下源码,也可以看到SpringBoot是根据类路径中是否存在某个类,而决定是否添加对应的消息转换器的:

![](./images/Spring Boot 3/1730775823305-dfc2b0c5-6ca1-4e8f-902b-506e3d86246d.png)

![](./images/Spring Boot 3/1730775878229-960b3144-1727-4467-899a-6bd16f6bc965.png)

因此,我们只要引入相关的依赖,让类路径存在某个类,则对应的消息转换器就会被加载。

定义自己的HttpMessageConverter

可以看到以上6个消息转换器中没有yaml相关的消息转换器,可见,如果要实现yaml格式的内容协商,yaml格式的消息转换器就需要我们自定义了。

第一步:引入能够处理yaml格式的依赖

任何一个能够处理yaml格式数据的库都可以,这里选择使用jackson的库,因为它既可以处理json,xml,又可以处理yaml。

1
2
3
4
5
<!--这个依赖也是jackson家族提供的,可以将java对象转换成yaml格式的字符串。-->
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
</dependency>

编写测试程序,简单测试一下这个库的用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//这只是一个测试程序,测试jackson家族提供的将对象转换成yaml格式字符串的api
public class TestYAMLApi {
public static void main(String[] args) throws JsonProcessingException {

// 创建yamlfactory对象,输出--- username: "jackson" age: 30,这三个横是yaml文档里单独的文档标记,意思是独立文档
//YAMLFactory yamlFactory = new YAMLFactory();
//去掉这三个横的方式:disable是使失效的意思,把 写文档开始头 的给禁用
YAMLFactory yamlFactory = new YAMLFactory().disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER);

//创建 对象映射器 对象,把yaml传进去就是专门做的一个yaml一个转换
ObjectMapper objectMapper = new ObjectMapper(yamlFactory);

// 准备一个java对象
User user = new User("jackson", 30);

// 将java对象转换成字符串
// 到底转成什么格式取决于这个objectmapper传的工厂是什么
String str = objectMapper.writeValueAsString(user);
System.out.println(str);
}
}

执行结果如下:

![image-20250523171507850](./images/Spring Boot 3/image-20250523171507850.png)

第二步:新增一种媒体类型yaml

默认支持xml和json两种媒体类型,并没有支持yaml,如果要支持yaml格式的,需要新增一个yaml媒体类型,在springboot的配置文件中进行如下配置:

1
2
3
4
5
6
# 新增一种媒体类型yaml
# 媒体类型的名字可以自定义。不一定叫做yaml。具体的媒体类型值也是可以修改的,不一定是 text/yaml
# 以下配置中媒体类型的名字是:yaml,媒体类型的值是:text/yaml
#在这里配置内容协商,后面的.yaml是类型,等号后面是媒体类型的名字,这个名字是随便写的,text是父媒体类型,yaml是子媒体类型
# http://localhost:8080?format=yaml
spring.mvc.contentnegotiation.media-types.yaml=text/yaml

注意,以上types后面的yaml是媒体类型的名字,名字随意,如果媒体类型起名为xyz,那么发送请求时的路径应该是这样的:http://localhost:8080/detail?format=xyz

第三步:自定义HttpMessageConverter

编写类YamlHttpMessageConverter继承AbstractHttpMessageConverter,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 这是一个消息转换器,专门处理yaml格式数据的。
// 注意,所有的消息转换器必须实现 HttpMessageConverter接口,或者继承AbstractHttpMessageConverter抽象类。并实现/重写方法。
public class YamlHttpMessageConverter extends AbstractHttpMessageConverter {

//对象映射器,定义成属性,给下面的将java对象转换成yaml格式的字符串用,new yamlFactory返回的也是yamlFactory
private ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory().disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER));

// 非常重要的一步:要将媒体类型 text/yaml 和 消息转换器进行绑定。通过构造方法,前面写媒体主类型,后面写媒体子类型,在配置文件里,Charset.forName是指定字符集
public YamlHttpMessageConverter() {
super(new MediaType("text", "yaml", Charset.forName("UTF-8")));
}

// 这个方法用来指定此消息转换器只适合于哪些类型的对象。
@Override
protected boolean supports(Class clazz) {
// 表示只有User类型的对象才能够使用该消息转换器进行转换。
return User.class.isAnnotationPresent(clazz);
}

// 这个是将yaml格式的字符串转换成java对象
// @RequestBody
@Override
protected Object readInternal(Class clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
return null;
}

// 将java对象转换成yaml格式的字符串
// @ResponseBody
@Override
protected void writeInternal(Object o, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
// 要转换的话需要拿到一个ObjectMapper对象,就需要在上面创建一个对象映射器,通过这个去写
// ObjectMapper对象,outputMessage.getBody()的执行结果是输出流,o是个java对象,传入这两个objectMapper就可以把这个对象转换成yaml格式的字符串
objectMapper.writeValue(outputMessage.getBody(), o);
}
}

第四步:配置消息转换器

addefaultHttpjMessageConverters这个方法默认往整个容器当中添加httpmessage转换器,默认是加了六个转换器,现在往里面再配一个新的转换器,也就是配进去

重写WebMvcConfigurer接口的configureMessageCongnverters方法:

1
2
3
4
5
6
7
8
9
10
//配置的意思
@Configuration
public class webConfig implements WebMvcConfigurer {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
// 将新的消息转换器进行配置,上面方法前面的list集合是消息转换器的集合,去调用这个converters
converters.add(new YamlHttpMessageConverter());

}
}

SpringBoot整合Thymeleaf

传统web应用和前后端分离

如果你是做前后端分离的项目,这一章节的内容将用不上。

现代开发大部分应用都会采用前后端分离的方式进行开发,前端是一个独立的系统,后端也是一个独立的系统,后端系统只给前端系统提供数据(JSON数据),不需要后端解析模板页面,前端系统拿到后端提供的数据之后,前端负责填充数据即可。因此这一章节内容作为了解。

传统的WEB应用(非前后端分离):浏览器页面上展示成什么效果,后端服务器说了算,这是传统web应用最大的特点。

![](./images/Spring Boot 3/1730789733440-1dc2d749-19c5-4363-b35d-bf6e6008265c.png)

前后端分离的应用:前端是一个独立的系统,后端也是一个独立的系统,后端系统不再负责页面的渲染,后端系统只负责给前端系统提供开放的API接口,后端系统只负责数据的收集,然后将数据以JSON/XML等格式响应给前端系统。前端系统拿到接口返回的数据后,将数据填充到页面上。

![image-20250526035425532](./images/Spring Boot 3/image-20250526035425532.png)

前后端分离的好处:

  • 职责清晰:前端专注于用户界面和用户体验,后端专注于业务逻辑和数据处理。
  • 开发效率高:前后端可以并行开发,互不影响,提高开发速度。
  • 可维护性强:代码结构更清晰,便于维护和扩展。
  • 技术栈灵活:前后端可以独立选择最适合的技术栈。
  • 响应式设计:前端可以更好地处理不同设备和屏幕尺寸。
  • 性能优化:前后端可以独立优化,提升整体性能。
  • 易于测试:前后端接口明确,便于单元测试和集成测试。

SpringBoot整合Thymeleaf

Java的模板技术有很多,SpringBoot支持以下的模板技术:

  1. Thymeleaf
    • 特点:Thymeleaf 是一个现代的服务器端Java模板引擎,它支持HTML5,XML,TEXT,JAVASCRIPT,CSS等多种模板类型。它能够在浏览器中预览,这使得前端开发更加便捷。Thymeleaf 提供了一套强大的表达式语言,可以轻松地处理数据绑定、条件判断、循环等。
    • 优势与Spring框架集成良好,也是SpringBoot官方推荐的
  2. FreeMarker
    • 特点:FreeMarker 是一个用Java编写的模板引擎,主要用来生成文本输出,如HTML网页、邮件、配置文件等。它不依赖于Servlet容器,可以在任何环境中运行。
    • 优势:模板语法丰富,灵活性高,支持宏和函数定义,非常适合需要大量定制化的项目。
  3. Velocity
    • 特点:Velocity 是另一个强大的模板引擎,最初设计用于与Java一起工作,但也可以与其他技术结合使用。它提供了简洁的模板语言,易于学习和使用。
    • 优势:轻量级,性能优秀,特别适合需要快速生成静态内容的应用。
  4. Mustache
    • 特点:Mustache 是一种逻辑无感知的模板语言,可以用于多种编程语言,包括Java。它的设计理念是让模板保持简单,避免模板中出现复杂的逻辑。
    • 优势:逻辑无感知,确保模板的简洁性和可维护性,易于与前后端开发人员协作。
  5. Groovy Templates
    • 特点:Groovy 是一种基于JVM的动态语言,它可以作为模板引擎使用。Groovy Templates 提供了非常灵活的模板编写方式,可以直接嵌入Groovy代码。
    • 优势:对于熟悉Groovy语言的开发者来说,使用起来非常方便,可以快速实现复杂逻辑。

这些模板技术各有千秋,选择哪一种取决于项目的具体需求和个人偏好。Spring Boot 对这些模板引擎都提供了良好的支持,通常只需要在项目中添加相应的依赖,然后按照官方文档配置即可开始使用。

提醒:SpringBoot内嵌了Servlet容器(例如:Tomcat、Jetty等),使用SpringBoot不太适合使用JSP模板技术,因为SpringBoot项目最终打成jar包之后,放在jar包中的jsp文件不能被Servlet容器解析。

要在SpringBoot中整合Thymeleaf,按照以下步骤操作:

第一步:引入thymeleaf启动器

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

第二步:编写配置文件,指定前缀和后缀(默认不配置就是以下配置

这个是给视图解析器使用的,方法里会有个返回值用于返回逻辑视图名称,如果返回的是一个hello,这时后端的视图解析器就起作用,把下面的前缀加在hello的前面,后缀加后面,变成classpath:/templates/hello.html,但这个不是html文件而是thymeleaf模板文件,这个就成了物理视图名称。引入web启动器的时候会在resources下自动生成这个文件目录

1
2
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html

第三步:编写控制器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.powernode.springboot.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

// 不能使用 @RestController,这个是返回json,我们不能返回json,我们是跳页面
@Controller
public class HelloController {

@GetMapping("/h")
public String helloThymeleaf(@RequestParam("name") String name, Model model) {
// 将接收到的name数据存储到域对象中
model.addAttribute("name", name);
// 逻辑视图名
return "hello"; // 最终的物理视图名:classpath:/templates/hello.html
}
}

第四步:编写thymeleaf模板页面

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>hello, thymeleaf</title>
</head>
<body>
<h1>hello,<span th:text="${name}"></span></h1>
</body>
</html>

启动服务器,测试地址为:http://localhost:8080/hello?name=jackson

![image-20250526231739354](./images/Spring Boot 3/image-20250526231739354.png)

Thymeleaf的自动配置

Thymeleaf的自动配置类:ThymeleafAutoConfiguration.java

@EnableConfigurationProperties这个注解可以把配置文件里的配置绑定到thymeleaf属性对象上,

![image-20250526232140913](./images/Spring Boot 3/image-20250526232140913.png)

我们通过查看上图中红框中的类,可以找到thymeleaf的相关配置:也就是以spring.thymeleaf前缀开头的配置信息绑定到属性类对象上

![image-20250526232506026](./images/Spring Boot 3/image-20250526232506026.png)

与thymeleaf相关的配置统一使用spring.thymeleaf即可。并且通过源码可以进一步了解到默认的前缀和后缀:

![image-20250526232938532](./images/Spring Boot 3/image-20250526232938532.png)

也就是说,默认情况下,只要放在classpath:/templates/目录下的xxx.html会被自动当做为thymeleaf的模板文件被thymeleaf模板引擎解析。因此放在classpath:/templates/目录下的html文件不是静态页面,而是动态的thymeleaf模板页面。

自定义的话修改配置文件

1
2
spring.thymeleaf.prefix=classpath:/t/
spring.thymeleaf.suffix=.thymeleaf

然后创建对应的文件

![image-20250526234327526](./images/Spring Boot 3/image-20250526234327526.png)

这时之前的hello.html就失效了

Thymeleaf核心语法

th:text与th:utext 替换标签体内容

th:text与th:utext都是用来替换标签体内容的,例如:如果什么都不写的效果是

1
<h1>hello,<span >这里是标签体</span></h1>

![image-20250526234928844](./images/Spring Boot 3/image-20250526234928844.png)

注意:在根标签中引入 xmlns:th=”http://www.thymeleaf.org",在编写th:语法时有智能提示。

加上后就可以替换,运行效果为hello,jackson

1
2
<h1>hello,<span th:text="${name}">这里是标签体</span></h1>
<h1>hello,<span th:utext="${name}">这里是标签体</span></h1>

测试结果:![image-20250527001115509](./images/Spring Boot 3/image-20250527001115509.png)

提示:标签体中的内容即使是一段HTML代码,也只是会被当做普通文本对待。例如我们让存储在域中的文本内容是一段HTML代码:

1
2
3
4
5
6
7
8
9
10
11
@Controller
public class HelloController {

@GetMapping("/hello")
public String hello(@RequestParam("name") String name, Model model){
model.addAttribute("name", name);
// 向域对象当中绑定一段html代码
model.addAttribute("htmlCode","<a href='http://www.bjpowernode.com'>动力节点</a>");
return "hello";
}
}
1
<div th:text="${htmlCode}">我是一个DIV</div>

测试结果:![image-20250527000813168](./images/Spring Boot 3/image-20250527000813168.png)

th:utext会将内容当做一段HTML代码解析并替换。将以上测试代码修改为:

1
<div th:utext="${htmlCode}">我是一个DIV</div>

测试结果:![image-20250527000930037](./images/Spring Boot 3/image-20250527000930037.png)

th:任意属性名 动态替换该属性的值

分开设置:

1
2
3
// 向域对象当中绑定公司名和公司地址
model.addAttribute("company", "动力节点");
model.addAttribute("companyUrl", "http://www.bjpowernode.com");
1
<a th:href="${companyUrl}" href="https://www.baidu.com" th:text="${company}">百度</a>

测试结果:由![image-20250527002116948](./images/Spring Boot 3/image-20250527002133005.png)

也就是说th后面加了href就可以动态替换链接,加了text就可以替换文本

th:attr 属性合并设置

分开设置:

合并设置:使用th:attr

1
2
// 向域对象中存储一个样式
model.addAttribute("style", "color:red");
1
2
3
4
<a th:href="${companyUrl}" href="http://www.baidu.com" th:style="${style}" style="color:green" th:text="${company}">百度</a>
<br>
<!--属性合并-->
<a th:attr="href=${companyUrl},style=${style}" href="http://www.baidu.com" style="color:green" th:text="${company}">百度</a>

测试结果:![image-20250527003204961](./images/Spring Boot 3/image-20250527003204961.png)

th:指令

指令非常多,具有代表性的例如:th:if,该指令用来控制元素隐藏显示。true就是显示,flase就是不显示

static静态资源目录下存放一张图片:dog1.jpg![image-20250527192830398](./images/Spring Boot 3/image-20250527192830398.png)

然后编写这样的模板代码:

1
2
<!--默认会去static去找这个文件-->
<img src="dog1.jpg" th:if="true">

测试结果,图片是显示的:![image-20250527193044240](./images/Spring Boot 3/image-20250527193044240.png)

如果th:if的值修改为false,我们会发现隐藏了。

也可以通过网址参数进行控制,在hellocontroller里添加代码

1
2
3
4
5
6
7
8
9
10
11
12
13
@GetMapping("/hello")
public String hello(@RequestParam("name") String name, Model model, @RequestParam("flag") Integer flag){
// 根据flag向域对象当中存储数据
//可以通过
if(1 == flag){
model.addAttribute("display", "true");
}else if(0 == flag){
model.addAttribute("display", "false");
}

return "hello";
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!--修改代码-->
<img src="./images/Spring Boot 3/dog1.jpg">
~~~

这时候访问图片就需要在http://localhost:8080/dog1.jpg里加上/myweb,如果需要@{}来进行维护,自动添加这些,代码是

~~~html
<img th:src="@{${imgUrl}}" th:if="${display}"/>
~~~

#### thymeleaf的内置工具

内置工具很多,可以参考官方文档:[https://www.thymeleaf.org/doc/tutorials/3.1/usingthymeleaf.html#strings](

![image-20250529014408038](./images/Spring Boot 3/image-20250529014408038.png)

例如我们要完成这样一个效果,用户提交用户名时,如果名字中含有`jack`则显示`狗狗图片`,否则不显示。

模板代码如下:

```html
<img th:src="@{${imgUrl}}" th:if="${#strings.contains(name,'jack')}">

测试结果1:

![](./images/Spring Boot 3/1730798493853-7e6e9545-f7dd-43b3-b157-7c853fb2da66.png)

测试结果2

![](./images/Spring Boot 3/1730798509202-cb67e7fe-f8ab-487e-ba9e-470ab50dcfbf.png)

thymeleaf也支持运算符

例如>运算符,我们实现这样一个功能,如果提供的用户名长度大于6,则显示狗狗图片:

1
2
<!--名字长度大于6就为true-->
<h1 th:text="${name}" th:if="${#strings.length(name) > 6}"></h1>

测试结果1:

![](./images/Spring Boot 3/1730798722013-6be35719-74db-4349-86d9-d5c8b99f0d5b.png)

另外thymeleaf也支持三目运算符:

1
2
// 向域对象当中存储一个性别
model.addAttribute("gender", false);

如果性别是true,则显示男,false,则显示女。

1
2
<!--传进来的如果是true就是男,否则是女-->
<h1 th:text="${gender ? '男' : '女'}"></h1>

thymeleaf的字符串拼接

加单引号就是字符串,不加就是一个对象

第一种方式:使用加号 +,然后访问http://localhost:8080/hello?name=jackson&flag=1

1
2
<!--strings.toUpperCase(name)是变大写-->
<h1>hello,<span th:text="${'姓名:' + #strings.toUpperCase(name) + '。'}">这里是标签体</span> </h1>

![image-20250529021028901](./images/Spring Boot 3/image-20250529021028901.png)

第二种方式:使用竖线 ||

1
<h1>hello,<span th:text="|姓名:${#strings.toUpperCase(name)}。|">这里是标签体</span> </h1>

![image-20250529021241021](./images/Spring Boot 3/image-20250529021241021.png)

循环遍历

创建bean包,user类

1
2
3
4
5
private String name;
private Integer age;
private Boolean gender;
private String desc;
private String location;

创建service包,Userservice类

1
2
3
public interface UserService {
List<User> getAll();
}

再创建impl,userserviceimpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Service
public class UserServiceImpl implements UserService {

@Override
public List<User> getAll() {
// 创建集合对象
List<User> users = new ArrayList<>();
users.add(new User("jackson", 20, true, "", "BEIJING"));
users.add(new User("lucy", 21, false, "my name is lucy", "TIANJIN"));
users.add(new User("tom", 22, false, "my name is tom", "SHANGHAI"));
users.add(new User("smith", 23, true, "my name is smith", "SHENZHEN"));
users.add(new User("ford", 24, true, "my name is ford", "WUHAN"));
users.add(new User("king", 24, true, "", ""));
return users;
}
}

再在controller包下新建UserController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Controller
public class UserController {
@Autowired
private UserService userService;

@GetMapping("/list")
public String list(Model model){
// 获取数据
List<User> users = userService.getAll();
// 绑定到域对象中
model.addAttribute("user", users);
return "List";
}
}

然后由ai生成了js和css样式,这里就不再复制

在templates里新建一个list.html

说明:

  • ${users}:代表集合
  • user:代表集合中的每个元素
  • ${user.name}:元素的name属性值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>动态表格示例</title>
<link rel="stylesheet" th:href="@{'/style.css'}">
</head>
<body>
<div class="container">
<h1>动态表格</h1>

<form id="add-row-form">
<input type="text" id="name" placeholder="姓名" required>
<input type="email" id="email" placeholder="电子邮件" required>
<button type="submit">添加行</button>
</form>

<table>
<thead>
<tr>
<th>序号</th>
<th>姓名</th>
<th>年龄</th>
<th>性别</th>
<th>简介</th>
<th>居住地</th>
<th>状态信息</th>
</tr>
</thead>
<tbody id="table-body">

<!--这里的user对应的是下面的user名称,都不是固定的,user是集合中的元素,在后面加个逗号可以传过来一个state状态对象,名字随便写-->
<!-- 状态对象是保存循环过程中的一个状态信息的-->
<tr th:each="user,state : ${users}">
<td>1</td>
<td th:text="${user.name}">张三</td>
<td th:text="${user.age}">30</td>
<td th:text="${user.gender ? '男' : '女'}"></td>
<td th:text="${user.desc}">我是一个很棒的人</td>
<td>
<!--行内写法[[]]和[()]都可以,一样的效果-->
状态信息:内联表达式<br>
下标:[[${state.index}]]<br>
序号:[(${state.count})]<br>
当前对象:[(${state.current})]<br>
元素总数:[(${state.size})]<br>
是否为偶数行:[(${state.even})]<br>
是否为奇数行:[(${state.odd})]<br>
是否是第一个元素:[(${state.first})]<br>
是否是最后一个元素:[(${state.last})]<br>
</td>
</tr>

访问网址:http://localhost:8080/list

![image-20250529033645437](./images/Spring Boot 3/image-20250529033645437.png)

注意:以上[[${state.index}]]这种语法属于thymeleaf中的内联表达式写法。也可以写成:[(${state.index})]

另外,状态对象state的属性包括:

  • index:下标
  • count:序号
  • current:当前对象
  • size:元素总数
  • even:是否为偶数行
  • odd:是否为奇数行
  • first:是否为第一个元素
  • last:是否为最后一个元素

条件判断th:if

th:if 语法用来决定元素是否显示:true显示。false隐藏。

我是一个div元素
,则显示该div
我也是一个div元素
,则隐藏该div

实现这样一个功能:用户如果没有留下简介,则显示你比较懒没有留下任何介绍信息,如果留下了简介,则显示具体的简介信息。

1
2
3
<!-- 这两种写法都可以<td th:if="为空">你这个人很懒</td>,为空就很懒,不为空就显示简介-->
<td th:if="${#strings.isEmpty(user.desc)}" th:text="你这个人很懒"></td>
<td th:if="${not #strings.isEmpty(user.desc)}" th:text="${user.desc}"></td>

条件判断th:switch

实现一个这样的功能:如果城市是beijing,就显示汉字北京,以此类推,其他值显示未知。

1
2
3
4
5
6
7
8
<td th:switch="${user.location}">
<span th:case="BEIJING">北京</span>
<span th:case="SHANGHAI">上海</span>
<span th:case="TIANJIN">天津</span>
<span th:case="WUHAN">武汉</span>
<span th:case="SHENZHEN">深圳</span>
<span th:case="*">未知</span>
</td>

![image-20250529181805712](./images/Spring Boot 3/image-20250529181805712.png)

thymeleaf属性优先级

thymeleaf的属性优先级非常重要,因为它直接决定了模板的解析和执行顺序。

以下是Thymeleaf属性的优先级从高到低的列表,以表格形式展示:

优先级 属性 描述
1 th:if 如果条件为真,则渲染该元素。
2 th:unless 如果条件为假,则渲染该元素。
3 th:with 定义局部变量。
4 th:switch 开始一个 switch 语句。
5 th:case 定义 switch 语句中的 case 分支。
6 th:each 遍历列表,用于循环。
7 th:remove 移除元素或其属性。
8 th:attr 设置或修改元素的属性。
9 th:classappend 追加 CSS 类。
10 th:styleappend 追加内联样式。
11 th:src 设置元素的 src 属性。
12 th:href 设置元素的 href 属性。
13 th:value 设置元素的 value 属性。
14 th:text 设置元素的文本内容。
15 th:utext 设置元素的未转义文本内容。
16 th:html 设置元素的 HTML 内容。
17 th:fragment 定义模板片段。
18 th:insert 插入一个模板片段。
19 th:replace 替换当前元素为一个模板片段。
20 th:include 包含一个模板片段的内容。
21 th:block 用于逻辑分组,不产生任何HTML输出。

对于thymeleaf属性优先级,我总结了以下一段话,把它记住即可:

“先控制,再遍历,后操作,末内容。”

具体来说:

  1. 先控制th:ifth:unless 用于条件控制,决定是否渲染元素。
  2. 再遍历th:each 用于遍历列表,生成多个元素。
  3. 后操作th:withth:switchth:caseth:removeth:attr 等用于局部变量定义、条件分支、属性操作等。
  4. 末内容th:textth:utextth:html 等用于设置元素的内容。

*{…} 表达式

*{...} 主要用于在上下文中访问对象的属性。这种表达式通常在表单处理和对象绑定场景中使用。

语法*{property}:访问当前**上下文**对象的某个属性。

使用场景

  • 表单绑定:在表单中绑定对象的属性。
  • 对象属性访问:在模板中访问对象的属性,特别是当对象是当前上下文的一部分时。
1
2
3
4
5
6
7
<!--<tr th:each="user,state : ${users}">-->
<!--将每一次循环的user对象作为tr上下文对象绑定,这样下面的user.就不用写了,外面的$改为*-->
<tr th:object="${user}" th:each="user,state : ${users}">
<!-- <td th:text="${user.name}">张三</td>
<td th:text="${user.age}">30</td>-->
<td th:text="*{name}">张三</td>
<td th:text="*{age}">30</td>

示例

  1. 表单绑定

假设你有一个 User 对象,包含 nameage 属性,你可以在表单中使用 *{...} 表达式来绑定这些属性:

1
2
3
4
5
6
7
<form th:object="${user}" method="post" action="/submit">
<label for="name">Name:</label>
<input type="text" id="name" name="name" th:field="*{name}" />
<label for="age">Age:</label>
<input type="number" id="age" name="age" th:field="*{age}" />
<button type="submit">Submit</button>
</form>

在这个例子中:

  • th:object="${user}"user 对象设置为当前上下文对象。
  • th:field="*{name}"th:field="*{age}" 分别绑定到 user 对象的 nameage 属性。
  1. 对象属性访问

假设你有一个 User 对象,包含 nameage 属性,你可以在模板中使用 *{...} 表达式来访问这些属性:

1
2
3
4
<div th:object="${user}">
<p>Name: <span th:text="*{name}">Default Name</span></p>
<p>Age: <span th:text="*{age}">Default Age</span></p>
</div>

在这个例子中:

  • th:object="${user}"user 对象设置为当前上下文对象。
  • *{name}*{age} 分别访问 user 对象的 nameage 属性。

**与 **${...} 的区别

  • ${...}:标准表达式,用于访问模型中的变量和执行简单的表达式。
  • *{...}:属性选择表达式,用于在上下文中访问对象的属性,通常与 th:object 一起使用。

代码片段共享

片段是Thymeleaf中用于代码复用的基本机制。你可以将共享的部分提取到单独的HTML文件中,然后在其他模板中引用这些片段。

页面中公共的header.html

1
2
3
4
5
6
7
8
9
10
<!--th:fragment的作用是把这段代码起个名叫h,方面以后用-->
<header th:fragment="h">
<nav>
<ul>
<!--@{/a}的作用是绑定一个路径-->
<li><a th:href="@{/a}">Home</a></li>
<li><a th:href="@{/b}">About</a></li>
</ul>
</nav>
</header>

页面中公共的footer.html

1
2
3
<footer th:fragment="f">
<p>&copy; 2024 北京动力节点</p>
</footer>

a.html中包含以上两个公共部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>主页</title>
</head>
<body>
<!-- ~{}是专门用来做代码共享的,里面的header是文件名,引用在这,然后是h是代码片段的名字 -->
<div th:replace="~{header :: h}"></div>

<main>
<h1>欢迎来到主页</h1>
<p>这是主页的主要内容.</p>
</main>

<div th:replace="~{footer :: f}"></div>
</body>
</html>

b.html中包含以上两个公共部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>关于我们</title>
</head>
<body>

<div th:replace="~{header :: h}"></div>

<main>
<h1>关于我们</h1>
<p>动力节点专注IT培训16年.</p>
</main>

<div th:replace="~{footer :: f}"></div>
</body>
</html>

然后在UserController里加上

1
2
3
4
5
6
7
8
9
10
// 只编写一个控制器方法,不做任何业务逻辑,只是为了跳转视图。
@RequestMapping("/a")
public String a(){
return "a";
}

@RequestMapping("/b")
public String b(){
return "b";
}

效果图:![image-20250530171447529](./images/Spring Boot 3/image-20250530171447529.png)

主要作用是代码复用。实现此功能的主要代码:

  • 在公共代码部分使用th:fragment="片段名称"来声明公共代码片段的名字。

  • 在需要引入的地方使用th:replace="~{文件名去掉后缀 :: 片段名称}"来引入。

小插曲:在springboot中如何实现:直接将请求路径映射到特定的视图,而不需要编写controller?

以前是在springmvc配置文件中配置一个viewName对应的控制器,现在新建:

![image-20250530172345613](./images/Spring Boot 3/image-20250530172345613.png)

使用ViewControllerRegistry进行视图与控制器的注册

1
2
3
4
5
6
7
8
9
10
11
// 基于springboot的自动配置扩展配置
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
// 添加 视图控制器 与 视图名称 的映射关系
// /a是视图控制器,a是视图名字,这样就不用在UserController里写方法了
registry.addViewController("/a").setViewName("a");
registry.addViewController("/b").setViewName("b");
}
}

thymeleaf页面修改如何立即生效

第一步:引入springboot提供的dev tools

1
2
3
4
5
6
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>

第二步:关闭应用重启功能,如果不关闭会导致每一次修改java代码后立即重启应用,不建议。我们现在只希望做到的功能是,修改thymeleaf模板文件后立即生效。

1
2
#关闭应用重启功能
spring.devtools.restart.enabled=false

第三步:修改代码后在IDEA中按组合键ctrl+f9

以上三步配合即可。

异常处理

在controller层如果程序出现了异常,并且这个异常未被捕获,springboot提供的异常处理机制将生效。

Spring Boot 提供异常处理机制主要是为了提高应用的健壮性和用户体验。它的好处包括:

  1. 统一错误响应:可以定义全局异常处理器来统一处理各种异常,确保返回给客户端的错误信息格式一致,便于前端解析。
  2. 提升用户体验:能够优雅地处理异常情况,避免直接将技术性错误信息暴露给用户,而是显示更加友好的提示信息。
  3. 简化代码:开发者不需要在每个可能抛出异常的方法中重复编写异常处理逻辑,减少冗余代码,使业务代码更加清晰简洁。
  4. 增强安全性:通过控制异常信息的输出,防止敏感信息泄露,增加系统的安全性。

自适应的错误处理机制

springboot会根据请求头的Accept字段来决定错误的响应格式。

这种机制的好处就是:客户端设备自适应,提高用户的体验。

![](./images/Spring Boot 3/1731424148788-8a9c52cc-7683-479a-a8b8-9075d779bc4e.png)

![](./images/Spring Boot 3/1730804471502-31c64115-c49a-4d76-92e0-90e9ae543b32-1748680254183-2.png)

![](./images/Spring Boot 3/1731424190201-4c9b9781-08ae-48f7-8b4a-9956b3b700d7.png)

![](./images/Spring Boot 3/1730804471502-31c64115-c49a-4d76-92e0-90e9ae543b32-1748680254183-2.png)

SpringMVC的错误处理方案

重点:如果程序员使用了SpringMVC的错误处理方案,SpringBoot的错误处理方案不生效。

局部控制 @ExceptionHandler

在控制器当中编写一个方法,方法使用@ExceptionHandler注解进行标注,凡是这个控制器当中出现了对应的异常,则走这个方法来进行异常的处理。局部生效。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@RestController//响应json,相当于下面两个注解
/*@Controller
@ResponseBody*/
public class UserController {

// RESTFul风格
@GetMapping("/resource/{id}")
//被@ResponseBody标注了返回的如果是user对象,消息转换器返回的是json,这里已经是String了就会转换成String
public String getResource(@PathVariable("id") Long id){//上面的id通过路径的方式传到这里的id
// 通过http://localhost:8080/resource/2来正常运行,但如果改为1就会捕捉异常
if (id == 1){
// IllegalArgumentException是lang包下的叫无效参数异常
throw new IllegalArgumentException("无效ID:" + id);
}
return"ID = " + id;
}

下面的加这里
}

http://localhost:8080/resource/2:![image-20250531164616038](./images/Spring Boot 3/image-20250531164616038.png)

http://localhost:8080/resource/1:这是springboot自带的异常

![image-20250531164633636](./images/Spring Boot 3/image-20250531164633636.png)

用mvc局部方式,接着添加:

1
2
3
4
5
6
7
8
// springmvc中的异常处理方案:局部方式。
// 只有在UserController中发生 IllegalArgumentException 异常时,会走下面这个方法进行异常处理。
// 把异常类型传给这个注解,这句话的意思是只要当前这个控制器出现了异常,就会走这个方法
// 会把上面new的异常内存地址传到带有这个注解的方法参数上
@ExceptionHandler(IllegalArgumentException.class)
public String handlerIllegalArgumentException(IllegalArgumentException e){
return "错误信息:" + e.getMessage();
}

![image-20250531170439631](./images/Spring Boot 3/image-20250531170439631.png)

可以再编写一个OtherController,让它也发生IllegalArgumentException异常,看看它会不会走局部的错误处理机制。

1
2
3
4
5
6
7
8
9
10
11
@RestController
public class OtherController {

@GetMapping("/other/{id}")
public String getResource(@PathVariable("id") Long id){
if(id == 1){
throw new IllegalArgumentException("无效ID:" + id);
}
return "ID = " + id;
}
}

http://localhost:8080/other/1,仅局部生效![image-20250531170706304](./images/Spring Boot 3/image-20250531170706304.png)

全局控制 @ControllerAdvice + @ExceptionHandler

也可以把以上局部生效的方法单独放到一个类当中,这个类使用@ControllerAdvice注解标注,凡是任何控制器当中出现了对应的异常,则走这个方法来进行异常的处理。全局生效。

(弹幕里看到说有什么异常的话记得加上thymeleaf插件)

将之前的局部处理方案的代码注释掉。使用全局处理方式,编写以下类:

1
2
3
4
5
6
7
8
9
10
11
12
// @ControllerAdvice + @ExceptionHandler 可以用来定义全局异常处理器。
// 针对于所有的控制器都有效。

@ControllerAdvice//注释掉表示springmvc不再管理这个异常
public class GlobalExceptionHandler {
@ExceptionHandler(IllegalArgumentException.class)
@ResponseBody//为什么之前在otherController不用加?因为使用了@RestController
//不加就会认为下面return的是一个逻辑视图名字
public String handlerIllegalArgumentException(IllegalArgumentException e){
return "错误信息:" + e.getMessage();
}
}

SpringBoot的错误处理方案

重点:如果SpringMVC没有对应的处理方案,会开启SpringBoot默认的错误处理方案。

SpringBoot默认的错误处理方案如下:

  1. 如果客户端要的是json,则直接响应json格式的错误信息。
  2. 如果客户端要的是html页面,则按照下面方案:
  • 第一步(精确错误码文件):去classpath:/templates/error/目录下找404.html``500.html精确错误码.html文件。如果找不到,则去静态资源目录下的/error目录下找。如果还是找不到,才会进入下一步。

  • 第二步(模糊错误码文件):去classpath:/templates/error/目录下找4xx.html``5xx.html模糊错误码.html文件。如果找不到,则去静态资源目录下的/error目录下找。如果还是找不到,才会进入下一步。

  • 第三步(通用错误页面):去找classpath:/templates/error.html如果找不到则进入下一步。

  • 第四步(默认错误处理):如果上述所有步骤都未能找到合适的错误页面,Spring Boot 会使用内置的默认错误处理机制,即 /error 端点。

比如随便输入一个没有任何html的文件网址,就会走第四步显示error错误

在文件下有这样的网页![image-20250531185737696](./images/Spring Boot 3/image-20250531185737696.png)

优先去templates找,但如果把这个404改成404a,精确查找找不到了就会桉顺序去找static下的404,直到四个路径都找不到了再进行模糊错误码查找

如果以上都没有了,就会去这里找,这是一个通用的错误

![image-20250531190534893](./images/Spring Boot 3/image-20250531190534893.png)

如何在错误页获取错误信息

Spring Boot 默认会在模型Model中放置以下信息:

  • timestamp: 错误发生的时间戳
  • status: HTTP 状态码
  • error: 错误类型(如 “Not Found”)
  • exception: 异常类名
  • message: 错误消息
  • trace: 堆栈跟踪

在thymeleaf中使用 ${message}即可取出信息。

注意:**springboot3.3.5**版本默认只向Model对象中绑定了timestamp``status``error。如果要保存exception``message``trace,需要开启以下三个配置:

1
2
3
server.error.include-stacktrace=always
server.error.include-exception=true
server.error.include-message=always

在error里写上代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>error</title>
</head>
<body>
<h1>error通用错误</h1>
异常发生时间:<span th:text="${timestamp}"></span><br>
HTTP状态码:<span th:text="${status}"></span><br>
错误类型:<span th:text="${error}"></span><br>
异常类名:<span th:text="${exception}"></span><br>
错误信息:<span th:text="${message}"></span><br>
堆栈跟踪:<span th:text="${trace}"></span><br>
</body>
</html>

![image-20250531191142990](./images/Spring Boot 3/image-20250531191142990.png)

配置后:

![image-20250531191222271](./images/Spring Boot 3/image-20250531191222271.png)

前后端分离项目的错误处理方案

统一使用SpringMVC的错误处理方案,定义全局的异常处理机制:@ControllerAdvice + @ExceptionHandler

返回json格式的错误信息,其它的就不需要管了,因为前端接收到错误信息怎么处理是他自己的事儿。

服务器端负责页面渲染的项目错误处理方案

建议使用SpringBoot的错误处理方案:

  1. 如果发生的异常是HTTP错误状态码:
    1. 建议常见的错误码给定精确错误码.html
    2. 建议不常见的错误码给定模糊错误码.html
  2. 如果发生的异常不是HTTP错误状态码,而是业务相关异常:
    1. 在程序中处理具体的业务异常,自己通过程序来决定跳转到哪个错误页面。
  3. 建议提供classpath:/templates/error.html来处理通用错误。

国际化(了解)

在Spring Boot中实现国际化(i18n)(i到n之间有18个字母)是一个常见的需求,它允许应用程序根据用户的语言和地区偏好显示不同的文本。

实现国际化

第一步:创建资源文件

创建包含不同语言版本的消息文件。这些文件通常放在src/main/resources目录下,并且以.properties为扩展名。例如:

  • messages.properties (默认语言,如英语)
  • messages_zh_CN.properties (简体中文)
  • messages_fr.properties (法语)

注意这个zh_CN或者fr是固定的不能动,messages可以动

每个文件都应包含相同的消息键,但值应对应于相应的语言。例如:

messages.properties:

1
welcome.message=Welcome to our application!

messages_zh_CN.properties:

1
welcome.message=欢迎来到我们的应用!

messages_fr.properties:

1
welcome.message=Bienvenue dans notre application !

修改好后需要在settings里找到file encodings把字符设置成utf-8

第二步:在模板文件中取出消息

语法格式为:#{welcome.message}井号是资源文件当中的key

1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>welcome</title>
</head>
<body>
<!--把配置文件中的前面那里拿过来-->
<h1 th:text="#{welcome.message}"></h1>
</body>
</html>

要跳转到这个页面还需要写个控制器

![image-20250601024131104](./images/Spring Boot 3/image-20250601024131104.png)

1
2
3
4
5
6
7
@Controller
public class WebConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/welcome").setViewName("welcome");
}
}

http://localhost:8080/welcome![image-20250601024201624](./images/Spring Boot 3/image-20250601024201624.png)

如果将浏览器默认语言环境修改为法文

![](./images/Spring Boot 3/1731512808484-d61096e2-4d1d-446c-a26f-33c1505d76e4.png)

![](./images/Spring Boot 3/1731512824927-f649b549-0add-4450-9dc3-9e2a4331c025.png)

国际化实现原理

做国际化的自动配置类是:MessageSourceAutoConfiguration

![](./images/Spring Boot 3/1731513242246-ac4e59e3-a1e9-45c3-a346-41ae1b41f658.png)

通过以上源码得知,国际化对应的配置前缀是:spring.message,只要是这个开头的都会绑定到MessageSourceProperties上,这个文件的默认值就是message,但也可以修改

例如在application.properties中进行如下配置:

1
2
3
4
5
# 配置国际化文件命名的基础名称
spring.messages.basename=messages

# 指定国际化信息的字符编码方式
spring.messages.encoding=UTF-8

注意:标准标识符:en_US 和 zh_CN 这样的标识符是固定的,不能更改。可以设置的是basename。

在程序当中如何获取国际化信息

也就是通过java程序获取properties里面后面的那个welcome.message的配置信息

在国际化自动配置类中可以看到这样一个Bean:MessageSource,它是专门用来处理国际化的。我们可以将它注入到我们的程序中,然后调用相关方法在程序中获取国际化信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 在java程序当中获取属性资源文件中的配置信息。
// 获取国际化的信息。
@RestController
public class MyController {

@Autowired
private MessageSource messageSource;

@GetMapping("/test")
public String getSource(HttpServletRequest request){
// 第一个是属性资源文件中的key(配置文件中的welcome.message就是key),第二个传个空,第三个传语言环境(通过浏览器发送请求,请求协议的请求头当中肯定有语言环境)
String message = messageSource.getMessage("welcome.message", null, request.getLocale());
return message;
}
}

定制web容器

web服务器切换为jetty

pringboot默认嵌入的web服务器是Tomcat,如何切换到jetty服务器?

实现方式:排除Tomcat,添加Jetty依赖

切换前:![image-20250601174925674](./images/Spring Boot 3/image-20250601174925674.png)

**修改 **pom.xml 文件:在 pom.xml 中,确保你使用 spring-boot-starter-web 并排除 Tomcat,然后添加 Jetty 依赖。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- 排除 Tomcat -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 添加 Jetty 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>

再次重启就变成这样:![image-20250601175200907](./images/Spring Boot 3/image-20250601175200907.png)

web服务器切换原理

从哪里可以看出springboot是直接将tomcat服务器嵌入到应用中的呢?看这个类:ServletWebServerFactoryAutoConfiguration

![](./images/Spring Boot 3/1731572949358-481582b6-0e79-4f4c-b556-b1ec3c13882c.png)

以上代码显示嵌入的是3个服务器。但并不是都生效,我们来看一下生效条件:

![](./images/Spring Boot 3/1731573044414-b1cb4767-de21-4897-9d38-36e6382a8581.png)

生效条件是,看类路径当中是否有对应服务器相关的类,如果有则生效。spring-boot-web-starter这个web启动器引入的时候,大家都知道,它间接引入的是tomcat服务器的jar包。因此默认Tomcat服务器被嵌入。如果想要切换web服务器,将tomcat相关jar包排除掉,引入jetty的jar包之后,jetty服务器就会生效,这就是切换web服务器的原理。

web服务器优化

通过以下源码得知,web服务器的相关配置和ServerProperties有关系:

![](./images/Spring Boot 3/1731573299936-414ae5c8-87a8-4810-b448-ac1b67f3cfb2.png)

查看ServerProperties源码:

![](./images/Spring Boot 3/1731573337370-7d40853c-9c5a-4750-a40d-cf00d1528fb4.png)

得知web服务器的配置都是以server开头的。

那么如果要配置tomcat服务器怎么办?要配置jetty服务器怎么办?请看一下源码

![](./images/Spring Boot 3/1731573416792-229c5dbb-4a42-4037-934d-bd004bc46a7d.png)

![](./images/Spring Boot 3/1731573452454-a150e6b1-6fa0-411c-b2a5-063f5b03be6b.png)

通过以上源码得知,如果要对tomcat服务器进行配置,前缀为:server.tomcat

如果要对jetty服务器进行配置,前缀为:server.jetty

在以后的开发中关于tomcat服务器的常见优化配置有:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# 这个参数决定了 Tomcat 在接收请求时,如果在指定的时间内没有收到完整的请求数据,将会关闭连接。这个超时时间是从客户端发送请求开始计算的。
# 防止长时间占用资源:如果客户端发送请求后,长时间没有发送完所有数据,Tomcat 会在这个超时时间内关闭连接,从而释放资源,避免资源被长时间占用。
server.tomcat.connection-timeout=20000

# 设置 Tomcat 服务器处理请求的最大线程数为 200。
# 如果超过这个数量的请求同时到达,Tomcat 会将多余的请求放入一个等待队列中。
# 如果等待队列也满了(由 server.tomcat.accept-count 配置),新的请求将被拒绝,通常会返回一个“503 Service Unavailable”错误。
server.tomcat.max-threads=200

# 用来设置等待队列的最大容量,也就是301时会返回一个503错误
server.tomcat.accept-count=100

# 设置 Tomcat 服务器在空闲时至少保持 10 个线程处于活动状态,以便快速响应新的请求。
server.tomcat.min-spare-threads=10

# 允许 Tomcat 服务器在关闭后立即重新绑定相同的地址和端口,即使该端口还在 TIME_WAIT 状态
# 当一个网络连接关闭时,操作系统会将该连接的端口保持在 TIME_WAIT 状态一段时间(通常是 2-4 分钟),以确保所有未完成的数据包都能被正确处理。在这段时间内,该端口不能被其他进程绑定。
server.tomcat.address-reuse-enabled=true

# 设置 Tomcat 服务器绑定到所有可用的网络接口,使其可以从任何网络地址访问。
server.tomcat.bind-address=0.0.0.0

# 设置 Tomcat 服务器使用 HTTP/1.1 协议处理请求。
server.tomcat.protocol=HTTP/1.1

# 设置 Tomcat 服务器的会话(session)超时时间为 30 分钟。具体来说,如果用户在 30 分钟内没有与应用进行任何交互,其会话将被自动注销。
server.tomcat.session-timeout=30

# 设置 Tomcat 服务器的静态资源缓存时间为 3600 秒(即 1 小时),这意味着浏览器会在 1 小时内缓存这些静态资源,减少重复请求。
server.tomcat.resource-cache-period=3600

# 解决get请求乱码。对请求行url进行编码。
server.tomcat.uri-encoding=UTF-8

# 设置 Tomcat 服务器的基础目录为当前工作目录(. 表示当前目录)。这个配置指定了 Tomcat 服务器的工作目录,包括日志文件、临时文件和其他运行时生成的文件的存放位置。 生产环境中可能需要重新配置。
server.tomcat.basedir=.

第6章 SpringBoot实用技术整合

logo设置

关闭logo图标

就是这个东西![image-20250601180758039](./images/Spring Boot 3/image-20250601180758039.png)

配置方式

1
2
3
spring.main.banner-mode=off
#默认是console打印到控制台上
#off是关闭,log是打印到日志文件上

代码方式

第一种代码:

1
2
3
4
5
6
7
8
9
10
11
12
@SpringBootApplication
public class Springboot322WebServerApplication {

public static void main(String[] args) {
//SpringApplication.run(Springboot322WebServerApplication.class, args);

// 关闭logo使用代码方式实现(第一种
SpringApplication springApplication = new SpringApplication(Springboot322WebServerApplication.class);
springApplication.setBannerMode(Banner.Mode.OFF);
springApplication.run(args);
}
}

第二种代码:流式编程/链式编程

1
2
3
4
5
6
7
8
9
10
11
12
13
@SpringBootApplication
public class Springboot322WebServerApplication {

public static void main(String[] args) {
//SpringApplication.run(Springboot322WebServerApplication.class, args);

// 流式编程/链式编程(第二种
new SpringApplicationBuilder()
.sources(Springboot322WebServerApplication.class)
.bannerMode(Banner.Mode.OFF)
.run(args);
}
}

修改logo图标

src/main/resources目录下存放一个banner.txt文件。文件名固定。

利用一些网站生成图标:

https://www.bootschool.net/ascii (支持中文、英文)

http://patorjk.com/software/taag/ (只支持英文)

https://www.degraeve.com/img2txt.php (只支持图片)

获取图标粘贴到banner.txt文件中运行程序即可。

PageHelper整合

官网地址:https://pagehelper.github.io/

引入依赖

1
2
3
4
5
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>

编写代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@RestController
public class VipController {
@Autowired
private VipService vipService;

@GetMapping("/list/{pageNo}")
public PageInfo<Vip> list(@PathVariable("pageNo") Integer pageNo) {
// 1.设置当前页码和每页显示的记录条数
PageHelper.startPage(pageNo, Constant.PAGE_SIZE);
// 2.获取数据(PageHelper会自动给SQL语句添加limit)
List<Vip> vips = vipService.findAll();
// 3.将分页数据封装到PageInfo
PageInfo<Vip> vipPageInfo = new PageInfo<>(vips);
return vipPageInfo;
}
}

新建项目引入![image-20250601182848347](./images/Spring Boot 3/image-20250601182848347.png)

然后用mybatis工程逆向生成,右击数据库的表选择![image-20250601182926687](./images/Spring Boot 3/image-20250601182926687.png)

生成了以下这些文件:![image-20250602030329716](./images/Spring Boot 3/image-20250602030329716.png)

然后把这些文件都重新写一下

Vipmapper.java:

1
2
3
public interface VipMapper {
List<Vip> selectAll();
}

VipMapper.xml:

1
2
3
4
5
6
7
8
<?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.powernode.ph.repository.VipMapper">

<select id="selectAll" resultType="com.powernode.ph.bean.Vip">
select * from t_vip
</select>
</mapper>

application.properties

1
2
3
4
5
6
7
8
9
10
11
12
13
spring.application.name=springboot3-23-ph

# 数据源配置
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.username=root
spring.datasource.password=liuqi3711022
spring.datasource.url=jdbc:mysql://localhost:3306/springboot

# mybatis配置(说确保表里的cardnumber与bean的vip里的cardnumber对应上
mybatis.configuration.map-underscore-to-camel-case=true
# 这个是mapper配置文件
mybatis.mapper-locations=classpath:/mapper/*.xml

然后主入口程序:

1
2
3
4
5
6
7
8
9
//这样可以扫描到vipmapper
@MapperScan(basePackages = "com.powernode.ph.repository")
@SpringBootApplication
public class Springboot323PhApplication {

public static void main(String[] args) {
SpringApplication.run(Springboot323PhApplication.class, args);
}
}

包的路径复制可以使用以下操作:右击文件夹然后选择copypath

![image-20250602025817341](./images/Spring Boot 3/image-20250602025817341.png)

![image-20250602025833119](./images/Spring Boot 3/image-20250602025833119.png)

然后创建以下:![image-20250602030943496](./images/Spring Boot 3/image-20250602030943496.png)

1
2
3
public interface VipService {
List<Vip> findAll();
}
1
2
3
4
5
6
7
8
9
10
11
@Service
public class VipServiceImpl implements VipService {

@Autowired
private VipMapper vipMapper;

@Override
public List<Vip> findAll() {
return vipMapper.selectAll();
}
}

然后新建Controller包和VipController类,引入时可以设置每页显示的数量为一个常数,新建util包和Constant类

1
2
3
4
5
6
7
8
9
/**
* 系统常量类
*/
public class Constant {
/**
* 每页显示3条记录
*/
public static final Integer PAGE_SIZE = 3;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@RestController
public class VipController {

@Autowired
private VipService vipService;

// RESTFul编程风格
@GetMapping("/List/{pageNo}")//这个括号会传进来页码
public PageInfo<Vip> list(@PathVariable int pageNo){
// 1.设置当前页码和每页显示的记录条数
// 这条语句执行时PageHelper会自动为查询语句添加limit ,再执行下面的findAll,虽然查询语句是*,但不会全查
PageHelper.startPage(pageNo, Constant.PAGE_SIZE);
// 2.查询数据
List<Vip> vips = vipService.findAll();
// 3.将数据封装到PageInfo对象当中即可
PageInfo<Vip> vipPageInfo = new PageInfo<>(vips);
return vipPageInfo;
}
}

然后访问http://localhost:8080/list/1

web层响应结果封装

web层其实就是Controller层,对于前后端分离的系统来说,为了降低沟通成本,我们有必要给前端系统开发人员返回统一格式的JSON数据。多数开发团队一般都会封装一个R对象来解决统一响应格式的问题。

封装R对象

在上面的代码继续新建result包,新建一个类R

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
/**
* 统一的WEB层返回数据。统一格式,降低前后端沟通成本。
* @param <T>
*/
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Data
public class R<T> {
// 响应的状态码: 200、500、404
private int code;
// 响应的消息描述:成功,失败,删除失败,修改失败,添加失败
private String msg;
// 响应的对象:支持任意类型的对象
private T data;

// 为了方便返回数据,一般这里的方法都是静态方法。
// 方法一般包括两大类:一类是成功,一类是失败。(重载)
// 响应成功的消息,但是不携带任何数据,因为不一定每次返回R对象都会有数据data
public static <T> R<T> OK(){
return R.<T>builder()
.code(200)
.msg("成功")
.build();
}

// 响应成功的消息,携带数据data
public static <T> R<T> OK(T data){
return R.<T>builder()
.code(200)
.msg("成功")
.data(data)
.build();
}

// 响应失败的消息。默认的code是400,失败的消息:失败
// 失败是不可能携带数据。
public static <T> R<T> FAIL(){
return R.<T>builder()
.code(400)
.msg("失败")
.build();
}

// 响应失败的消息,controller层的程序员可以通过此方法指定特定的code和特定的消息。
public static <T> R<T> FAIL(int code, String msg) {
return R.<T>builder()
.code(code)
.msg(msg)
.build();
}
}

如果出现了builder错误,就看https://blog.csdn.net/weixin_69669164/article/details/146015501?utm_medium=distribute.pc_relevant.none-task-blog-2~default~baidujs_baidulandingword~default-1-146015501-blog-134867178.235^v43^pc_blog_bottom_relevance_base3&spm=1001.2101.3001.4242.2&utm_relevant_index=3

测试数据

再新建个bean的User

1
2
3
4
5
6
7
@NoArgsConstructor
@AllArgsConstructor
@Data
public class User {
private String name;
private int age;
}

然后vipController添加

1
2
3
4
5
6
7
8
9
10
@GetMapping("/detail")
public R<User> detail(){// 是下面return返回的
// 这个数据是service返回的数据
User user = new User("jackson", 20);
// 相应给前端
//return R.OK(user);
//return R.OK();// 不携带数据
//return R.FAIL();
return R.FAIL(404, "资源找不到");
}

改进R对象

以上R对象存在的问题是,难以维护,项目中可能会出现很多这样的代码:R.FAIL(400, “修改失败”)。

引入枚举类型进行改进:创建enums包和codeEnum类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// 专门维护code+msg的一个枚举类型
//@Data 不能用data,就需要手动添加setter getter注解或者方法
@NoArgsConstructor
@AllArgsConstructor
//@Setter这里也不能添加
public enum CodeEnum {

// ;
//这地方没有写枚举值,那么由于后面有其他代码,这里至少要写一个“;”

//这些会传到下面的字段上,统一去维护
OK(200, "成功"),
FAIL(400, "失败"),
BAD_REQUEST(400, "请求错误"),
NOT_FOUND(404, "Not Found"),
INTERNAL_ERROR(500, "内部服务器错误"),
MODIFICATION_FAILED(400, "修改失败"),
DELETION_FAILED(400, "删除失败"),
CREATION_FAILED(400, "创建失败");


// javase当中详细讲过枚举类型
// 枚举类型当中可以有属性
// 枚举类型当中可以有构造方法
// 枚举类型当中可以有setter和getter方法。
// 在枚举类型当中有一个语法要求,枚举的大括号一开始的位置必须编写枚举值。
// 每一个枚举值就是一个枚举类型的实例。


@Setter// 只能加在字段上
@Getter
private int code;

@Setter
@Getter
private String msg;
}

enum Season {
// 这些就是枚举值
// 其中每一个枚举值都是 Season类型的对象/实例。
SPRING, SUMMER, AUTUMN, WINTER;

// 枚举值下面如果没有代码,枚举值列表最后的那个“;”可以省略。
// 枚举值下面如果有代码,则枚举值列表最后的";"是不能省略的。
private String name;
}

改进R:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
@Builder // 建造模式
@Data
@NoArgsConstructor
@AllArgsConstructor
public class R<T> {
// 响应的状态码: 200、500、404
private int code;
// 响应的消息描述:成功,失败,删除失败,修改失败,添加失败
private String msg;
// 响应的对象:支持任意类型的对象
private T data;

// 为了方便返回数据,一般这里的方法都是静态方法。
// 方法一般包括两大类:一类是成功,一类是失败。(重载)
// 响应成功的消息,但是不携带任何数据
public static <T> R<T> OK() {
return R.<T>builder()
.code(CodeEnum.OK.getCode())
.msg(CodeEnum.OK.getMsg())
.build();
}

// 响应成功的消息,携带数据data
public static <T> R<T> OK(T data) {
return R.<T>builder()
.code(CodeEnum.OK.getCode())
.msg(CodeEnum.OK.getMsg())
.data(data)
.build();
}

// 响应失败的消息。默认的code是400,失败的消息:失败
// 失败是不可能携带数据。
public static <T> R<T> FAIL() {
return R.<T>builder()
.code(CodeEnum.FAIL.getCode())
.msg(CodeEnum.FAIL.getMsg())
.build();
}

// 响应失败的消息,controller层的程序员可以通过此方法指定特定的code和特定的消息。
/*public static <T> R<T> FAIL(int code, String msg) {
return R.<T>builder()
.code(code)
.msg(msg)
.build();
}*/

// 改成了传入一个codeEnum对象,然后调用里面的方法,获得code和msg
public static <T> R<T> FAIL(CodeEnum codeEnum) {
return R.<T>builder()
.code(codeEnum.getCode())
.msg(codeEnum.getMsg())
.build();
}
}

修改VipController

1
2
3
4
5
6
7
8
9
10
11
12
13
@GetMapping("/detail")
public R<User> detail(){// 是下面return返回的
// 这个数据是service返回的数据
User user = new User("jackson", 20);
// 相应给前端
//return R.OK(user);
//return R.OK();// 不携带数据
//return R.FAIL();
//return R.FAIL(404, "资源找不到");

//改进后的写法:这样就传过去了一个codeEnum对象到Rjava里的方法上
return R.FAIL(CodeEnum.NOT_FOUND);
}

事务管理

SpringBoot中的事务管理仍然使用的Spring框架中的事务管理机制,在代码实现上更为简单了。不需要手动配置事务管理器,SpringBoot自动配置完成了。我们只需要使用@Transactional注解标注需要控制事务的方法即可。另外事务的特性等仍然延用Spring框架。大家可以在老杜发布的Spring视频教程中详细学习事务管理机制。以下代码是在SpringBoot框架中进行的事务控制:

创建bean包account类

1
2
3
4
5
6
7
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Account {
private String actNo; // 数据库字段名 act_no
private Double balance;
}

创建repository包AccountMapper类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public interface AccountMapper {

/**
* 根据账号获取账户信息
* @param actNo 账号
* @return 账户对象
*/
Account selectByActNo(String actNo);

/**
* 更新账户信息,注意账号不能更新,其他字段都可以更新。
* @param account 账户对象
* @return 1表示更新成功
*/
int update(Account account);

}

然后使用mybatis插件生成mapper文件![image-20250603015727039](./images/Spring Boot 3/image-20250603015727039.png)

生成到resources的mapper文件

然后挨个生成查询语句

![image-20250603015909182](./images/Spring Boot 3/image-20250603015909182.png)

AccountMapper.xml:

1
2
3
4
5
6
7
8
9
10
<?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.powernode.transaction.repository.AccountMapper">
<update id="update">
update t_act set balance = #{balance} where act_no = #{actNo}
</update>
<select id="selectByActNo" resultType="Account">
select * from t_act where act_no = #{actNo}
</select>
</mapper>

入口:

1
2
3
4
5
6
7
8
9
10
//扫描
@MapperScan(basePackages = "com.powernode.transaction.repository")
@SpringBootApplication
public class Spingboot324TransactionApplication {

public static void main(String[] args) {
SpringApplication.run(Spingboot324TransactionApplication.class, args);
}

}

配置文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 数据源
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.password=liuqi3711022
spring.datasource.url=jdbc:mysql://localhost:3306/springboot

# mybatis
mybatis.mapper-locations=classpath:/mapper/*.xml
mybatis.configuration.map-underscore-to-camel-case=true
mybatis.type-aliases-package=com.powernode.transaction.bean

server.error.include-message=always

在service下新建AccountService

1
2
3
4
5
6
7
8
9
10
11
12
public interface AccountService {

/**
* 转账的业务方法
* @param fromActNo 转出账号
* @param toActNo 转入账号
* @param money 转账金额
*/
void transfer(String fromActNo, String toActNo, double money);

}

实现接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
@Service
public class AccountServiceImpl implements AccountService {

@Autowired
private AccountMapper accountMapper;

@Override
public void transfer(String fromActNo, String toActNo, double money) {
// 转账的业务逻辑
// 判断转出账户的余额是否足够
Account fromAct = accountMapper.selectByActNo(fromActNo);
if(fromAct.getBalance() < money){
//钱少于余额时报错
throw new TransferException("余额不足");
}
// 执行到这说明余额充足
Account toAct = accountMapper.selectByActNo(toActNo);

fromAct.setBalance(fromAct.getBalance() - money);
toAct.setBalance(toAct.getBalance() + money);

// 更新数据库
int count = accountMapper.update(fromAct);

// 模拟异常
/*if(1 == 1){
throw new TransferException("转账失败");
}*/

count += accountMapper.update(toAct);

// 如果上面更新的count是两条就说明成功了,否则失败
if(count != 2){
throw new TransferException("转账失败");
}
}
}

新建自定义异常,在exception包下建TransferException类

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 转账异常
*/
public class TransferException extends RuntimeException {

public TransferException() {
}

public TransferException(String message) {
super(message);
}
}

然后是控制层

1
2
3
4
5
6
7
8
9
10
11
12
13
@Controller
public class AccountController {

@Autowired
private AccountService accountService;

@PostMapping("/transfer")
public String transfer(String fromActNo, String toActNo, double money) {
// 如果出现异常,springmvc没有对这个异常进行处理的话,springboot就会接管这个异常处理
accountService.transfer(fromActNo, toActNo, money);
return "success";
}
}

然后再resources的templates里弄三个html,error index success

1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>fail</title>
</head>
<body>
<!--使用message时需要在配置文件里配置server.error.include-message=always-->
[[${message}]]
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<form th:action="@{/transfer}" method="post">
转出账号:<input type="text" name="fromActNo"><br>
转入账号:<input type="text" name="toActNo"><br>
转账金额:<input type="text" name="money"><br>
<input type="submit" value="转账">
</form>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>success</title>
</head>
<body>
<h1>转账成功</h1>
</body>
</html>

然后需要为index配置东西,建config包webconfig类

1
2
3
4
5
6
7
8
@Configuration
public class WebConfig implements WebMvcConfigurer {

@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/index").setViewName("index");
}
}

然后访问http://localhost:8080/index

![image-20250603031017672](./images/Spring Boot 3/image-20250603031017672.png)

SpringBoot打war包

第一步:将打包方式设置为war

1
<packaging>war</packaging>

![image-20250603175623666](./images/Spring Boot 3/image-20250603175623666.png)

第二步:排除内嵌tomcat

1
2
3
4
5
6
7
8
9
10
11
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!--内嵌的tomcat服务器排除掉-->
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>

第三步:添加servlet api依赖(引入tomcat,但scope设置为provided,这样这个tomcat服务器就不会打入war包了)

1
2
3
4
5
6
<!--额外添加一个tomcat服务器,实际上是为了添加servlet api。scope设置为provided表示这个不会被打入war包当中。-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>

第四步:修改主类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@MapperScan(basePackages = "com.powernode.transaction.repository")
@SpringBootApplication
public class Springboot324TransactionApplication extends SpringBootServletInitializer{

@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Springboot324TransactionApplication.class);
}

public static void main(String[] args) {
SpringApplication.run(Springboot324TransactionApplication.class, args);
}

}

第五步:把target文件删掉,![image-20250603180308269](./images/Spring Boot 3/image-20250603180308269.png)执行clean和package命令打war包

生成在这里![image-20250603180458371](./images/Spring Boot 3/image-20250603180458371.png)

第六步:配置tomcat环境,将war包放入到webapps目录下,启动tomcat服务器,并访问。

现在不叫javaee了叫JakarataEE 9

https://tomcat.apache.org/选tomcat10然后![image-20250603181306099](./images/Spring Boot 3/image-20250603181306099.png)

然后解压配置环境

![image-20250603182415314](./images/Spring Boot 3/image-20250603182415314.png)

![image-20250603181829786](./images/Spring Boot 3/image-20250603181829786.png)

![image-20250603182457572](./images/Spring Boot 3/image-20250603182457572.png)

然后以管理员身份运行

![image-20250603183104661](./images/Spring Boot 3/image-20250603183104661.png)

如果乱码了需要去conf文件下的logging.properties配置,把所有utf-8改成gbk编码

![image-20250603183429762](./images/Spring Boot 3/image-20250603183429762.png)

然后关闭窗口,把之前的war包扔到tomcat的webapps文件夹里,然后再次启动startup.bat就开始自动部署,然后访问http://localhost:8080/springboot3-24-transaction-0.0.1-SNAPSHOT/index

![image-20250603183751064](./images/Spring Boot 3/image-20250603183751064.png)

SpringBoot的日志处理

日志概述

用system.out.println输出到控制台没有隐私等问题,其他的等后面再看吧

日志框架包括抽象层面的和具体层面的

抽象方面的:SFL4J,Commons logging

具体方面的:log4j,log4j2,logback(SpringBoot默认集成的),JUL

日志级别概述:

不同日志级别反应的严重程度不一样

1.TRACE:这是最低级别的日志信息,用于记录最详细的信息,通常是在调用应用时使用

2.DEBUG:用于记录程序运行时的详细信息,比如变量的值,进入或退出某个方法等,主要用于开发阶段的调试

3.INFO:记录应用程序的一般信息,如系统启动、服务初始化完成等,表示程序运行正常

4.WARN:警告信息,表示可能存在问题或者异常的情况,但是还不至于导致应用停止工作

5.ERROR:错误信息,表示发生了一个错误事件,该错误可能会导致某些功能无法正常工作

在生产环境中,我们可能会将日志级别设置为INFO或更高级别,以避免输出过多的调试信息。这也是SpringBoot默认的日志级别

在开发或测试环境中,降低为DEBUG甚至TRACE,便于进行详细的错误追踪和调试

日志级别从低到高是1-5.级别越低打印的日志信息就越多

更改日志级别:

新建工程然后引用web和lombok,在入口程序这样写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Slf4j // 这是lombok的一个注解,这个注解的作用是:为我们维护一个日志对象 log
@SpringBootApplication
public class Springboot325LogApplication {

public static void main(String[] args) {
SpringApplication.run(Springboot325LogApplication.class, args);

// 直接使用lombok的日志对象 log 记录日志
// 只能输出下面三个是因为默认是info,所以最多输出到info不能再多了
log.trace("trace级别日志");
log.debug("debug级别日志");
log.info("info级别日志");
log.warn("warn级别日志");
log.error("error级别日志");
}
}

配置级别:

1
2
# 配置日志级别
logging.level.root=DEBUG

丰富启动日志

SpringBoot项目启动过程中的日志的丰富,启动完成后,项目进入正常运行阶段,这两个配置没用

这两个配置不会对我们程序员手动记录的日志起作用

1
2
trace=true
debug=true

trace=true:启动SpringBoot的启动跟踪,生成包含启动信息的详细跟踪文件,内容是包含debug=true输出的内容

debug=true:启动SpringBoot的调用模式,增加启动日志的详细程度并显示自动配置报告

日志的粗细粒度

1
2
3
4
5
6
7
# 测试日志控制的粗细粒度
# 全局的日志级别设置为ERROR
logging.level.root=ERROR
# 设置某个包下的所有类的日志级别为INFO
logging.level.com.powernode.springboot.controller=INFO
# 设置某个类的日志级别为TRACE
logging.level.com.powernode.springboot.service.impl=TRACE

怎么在项目中打印sql语句?默认级别是info,不会显示sql,但想降低级别又不应用到全局,只sql相关的使用debug

1
2
3
4
#全局的日志级别
logging.level.root=INFO
# mapper相关的日志界别(xxx是文件路径
logging.level.com.powernode.xxx.repository=DEBUG

日志输出到文件

这两种方式不可共存

1
2
3
4
5
6
7
8
9
10
11
# 以下的两种实现方式不可共存,其实是SpringBoot框架设计的一个小bug,可以在github提交一下
#将日志输出到文件,第一种配置方式:使用path
# 自动在指定的目录下生成一个固定不变的文件:spring.log,名字不可配置
#路径可以随便配置,可以是硬盘上的任意有权限的路径
logging.file.path=./log/

#将日志输出到文件,第二种配置方式:使用name
# name 和 path 的配置同时存在的话,name生效path无效(path和name不能同时使用)
# 这种方式只能设置日志文件的名字,无法设置日志文件存储的位置
# 日志文件生成到项目的根路径下
logging.file.name=my.log

滚动日志

滚动日志是一种日志管理机制,用于防止日志文件无线增长,通过将日志文件分割成多个文件,每个文件只包含一定时间段或大小的日志记录,滚动日志可以帮助你更好地管理和维护日志文件,避免单个日志文件过大导致难以处理

但这个滚动策略只适合于logback框架,如果使用log4j2就需要单独在log4j2.xml里配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#滚动日志配置
# 日志文件达到多大时进行归档,也就是达到100kb就不再增长然后自动打一个压缩包
logging.logback.rollingpolicy.max-file-size=100KB

# 所有的归档日志文件总共达到多大时进行删除,默认是0b表示不删除
logging.logback.rollingpolicy.total-size-cap=50GB

# 归档日志文件最多保留几天
logging.logback.rollingpolicy.max-history=60

# 启动项目时是否要清理归档日志文件
logging.logback.rollingpolicy.clean-history-on-start=false

# 归档日志文件的格式,第一个$取的是spring.log那个文件名放在这,然后年月日,gz是linux中的压缩包扩展名
logging.logback.rollingpolicy.file-name-pattern=${LOG_FILE}.$d{yyyy-MM-dd}.%i.gz

日志框架切换

也就是排除logback的依赖然后添加别的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<!--排除默认的日志依赖-->
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>

<!--使用log4j2日志依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>

注意:一旦切换log4j2的日志框架,对于之前的配置来说,只有logback的滚动日志不再生效,其他配置仍然生效,如果要在log4j2中进行滚动日志,需要编写log4j2相关的xml配置文件,比较麻烦