SpringBoot3 基础教程之五基础特性
大纲
- SpringBoot3 基础教程之一快速入门
- SpringBoot3 基础教程之二常规配置
- SpringBoot3 基础教程之三 Web 开发
- SpringBoot3 基础教程之四 Web 开发
- SpringBoot3 基础教程之五基础特性
- SpringBoot3 基础教程之六场景整合
- SpringBoot3 基础教程之七场景整合
- SpringBoot3 基础教程之八场景整合
- SpringBoot3 基础教程之九核心原理
前言
本章节所需的案例代码,可以直接从 GitHub 下载对应章节 spring-boot3-06
,除特别说明外。
SpringApplication
自定义 banner
推荐使用 Spring Boot Banner 在线生成工具,制作并下载英文 banner.txt
文件,然后将它放到项目的 /src/main/resources
目录下,这样就可以实现应用的个性化启动。
自定义 SpringApplication
1 | import org.springframework.boot.Banner; |
上面的代码等同于以下配置内容,值得一提的是,配置文件的优先级更高
1 | # 关闭Banner |
FluentBuilder API
SpringBoot 支持以 Builder 方式构建 SpringApplication
,通过 FluentBuilder API 设置应用属性。
1 | import org.springframework.boot.Banner; |
Profiles 环境隔离
简单介绍
Profiles 提供环境隔离能力,支持快速切换开发、测试、生产环境,使用步骤如下:
- 1、
指定环境
:指定哪些组件、配置在哪个环境生效 - 2、
激活环境
:这个环境对应的所有组件和配置就应该生效
基础使用
指定环境
- Spring Profiles 提供一种隔离配置的方式,使其仅在特定环境生效
- 任何
@Component
、@Configuration
或@ConfigurationProperties
都可以使用@Profile
注解,来指定自身何时被加载。值得一提的是,Spring 容器中的组件都可以被@Profile
注解标记。
1 |
|
1 |
|
环境激活
在 application.properties
配置文件中,指定需要激活的环境
1 | dev = |
或者同时激活多个环境
1 | dev,test = |
提示
- 也可以使用命令行激活环境,如
java -jar xxxx.jar --spring.profiles.active=dev,test
- 还可以配置默认环境,即不标注
@Profile
注解的组件永远都会生效 - 以前默认环境叫
default
- 自定义默认环境
spring.profiles.default=test
, - 不推荐使用自定义默认环境的方式,而是推荐使用激活方式激活指定环境
环境包含
包含指定的环境,即不管激活哪个环境,包含指定的环境都会生效
1 | dev = |
最佳实践
- 生效的环境 = 激活的环境 / 默认环境 + 包含的环境
- 企业项目里面的使用规则
- 基础的配置内容,如 MyBatis、Log 写到包含环境中
- 需要动态切换变化的配置内容,如 DataBase、Redis 写到激活的环境中
Profiles 分组
创建 prod
分组,指定包含的 db
和 mq
配置。当使用命令行激活 java -jar xxx.jar --spring.profiles.active=prod
,就会激活 prod
分组,包括激活 db
,mq
的配置文件。
1 | db,mq = |
或者使用数组的写法
1 | db = |
Profiles 配置文件
application-{profile}.properties
可以作为指定环境的配置文件- 激活这个环境,对应的配置文件就会生效,最终生效的所有配置如下
application.properties
主配置文件,任意时候都会生效application-{profile}.properties
指定环境配置文件,激活指定环境时会生效- 如果发生了配置冲突,默认以激活的环境配置文件为准,即
application-{profile}.properties
的优先级高于application.properties
特别注意
spring.profiles.default
、spring.profiles.active
、spring.profiles.include
的配置信息只能写在 application.properties
中,如果写在 application-{profile}.properties
是无效的。
外部化配置
线上应用如何快速修改配置,并应用最新配置?
- SpringBoot 使用
配置优先级
+外部配置
,可以简化配置更新、简化运维。 - 只需要往 Jar 应用所在的文件夹放一个
application.properties
最新配置文件,重启项目后就能自动应用最新的配置信息,无需重新编译打包代码。
配置优先级
SpringBoot 允许将配置外部化,以便可以在不同的环境中使用相同的应用程序代码。支持使用各种外部配置源,包括 Properties 文件、YAML 文件、环境变量和命令行参数。使用 @Value
注解可以获取到配置参数的值,也可以用 @ConfigurationProperties
注解将所有属性绑定到 POJO 中。
- 以下是 SpringBoot 属性源的加载顺序,后面的会覆盖前面的值。由低到高,高优先级配置会覆盖低优先级配置。
- 默认属性(通过
SpringApplication.setDefaultProperties()
指定的) @PropertySource
加载指定的配置(需要写在@Configuration
类上才可生效)- 配置文件(
application.properties/yml
等) RandomValuePropertySource
支持的random.*
配置(如@Value("${random.int}")
)- 系统环境变量
- Java 系统属性(
System.getProperties()
) - JNDI 属性(来自
java:comp/env
) - ServletContext 初始化参数
- ServletConfig 初始化参数
- SPRING_APPLICATION_JSON 属性(内置在环境变量或系统属性中的 JSON)
- 命令行参数
- 测试属性 (
@SpringBootTest
进行测试时指定的属性) - 测试类
@TestPropertySource
注解 - Devtools 设置的全局属性(
$HOME/.config/spring-boot
)
- 默认属性(通过
总结
配置信息可以写在很多位置,常见的优先级顺序: 命令行
> 配置文件
> SpringApplication 配置
。
- 配置文件的优先级如下(越往后优先级越高)
- Jar 包内的
application.properties/yml
- Jar 包内的
application-{profile}.properties/yml
- Jar 包外的
application.properties/yml
- Jar 包外的
application-{profile}.properties/yml
- Jar 包内的
提示
- 优先级顺序:
包外
>包内
; 同级情况:Profiles 配置
>Application 配置
- 建议使用同一种格式的配置文件。如果
xxx.properties
和xxx.yml
同时存在,则xxx.properties
的优先级更高 - 所有参数均可由命令行传入,使用
--参数项=参数值
格式,参数将会被添加到环境变量中,且优先级大于配置文件。比如java -jar app.jar --name="Spring"
,可以使用@Value("${name}")
获取参数值。
- 实际应用场景
- 包内:
application.properties
server.port=8000
- 包内:
application-dev.properties
server.port=9000
- 包外:
application.properties
server.port=8001
- 包外:
application-dev.properties
server.port=9001
- 应用启动后的端口: 命令行 > 9001 > 8001 > 9000 > 8000
- 包内:
外部配置说明
SpringBoot 应用在启动的时候,会自动寻找 application.properties
和 application.yaml
配置文件,然后进行加载。配置文件的加载顺序如下(越往后优先级越高):
类路径(内部)
- 类根路径(
classpath
) - 类根路径(
classpath
)下的/config
子目录
- 类根路径(
当前路径(Jar 应用所在的位置)
- 当前路径
- 当前路径下的
/config
子目录 /config
目录的直接子目录
外部配置总结
最终效果:优先级由高到低,前面的覆盖后面的
命令行
>包外 config 直接子目录
>包外 config 目录
>包外根目录
>包内目录
- 同级比较
Profiles 配置
>Application 配置
Properties 配置
>YAML 配置
规律总结:最外层的最优先
- 命令行 > 所有方式
- 包外 > 包内
/config
子目录 > 根目录- Profiles 配置 > Application 配置
- 配置不同就都生效(互补),配置相同则高优先级会覆盖低优先级的配置
导入配置使用
使用 spring.config.import
可以导入额外的配置。
1 | classpath:/my.properties = |
无论以上写法的先后顺序是怎样,my.properties
的值总是优先于直接在文件中编写的 my.property
。
属性占位符使用
配置文件中可以使用 ${name:default}
形式取出之前配置过的值。
1 | MyApp = |
JUnit 5 单元测试
整合单元测试
SpringBoot 提供一系列测试工具集及注解方便开发者进行测试。spring-boot-test
提供核心测试能力,spring-boot-test-autoconfigure
提供测试的一些自动配置。值得一提的是,一般只需要导入 spring-boot-starter-test
即可整合单元测试。
1 | <dependency> |
spring-boot-starter-test
默认提供了以下库供开发者进行单元测试使用:
使用单元测试
组件测试
直接通过 @Autowired
注入容器中的组件即可进行测试。
1 | import org.junit.jupiter.api.Test; |
特别注意
单元测试类所在包的名称,必须与主启动类所在的包或者其子包的名称相同;否则单元测试类需要通过 @SpringBootTest
注解的 class
属性指定主启动类。
常用注解
提示
JUnit5 的注解与 JUnit4 的注解有所区别,详细说明请参考 官方文档。
@Test
: 表示方法是测试方法。但是与 JUnit4 的@Test
不同,它的职责非常单一不能声明任何属性,拓展的测试将会由 Jupiter 提供额外测试@ParameterizedTest
: 表示方法是参数化测试,下面会有详细介绍@RepeatedTest
: 表示方法可重复执行,下面会有详细介绍@DisplayName
: 为测试类或者测试方法设置展示名称@BeforeEach
: 表示在每个单元测试之前执行@AfterEach
: 表示在每个单元测试之后执行@BeforeAll
: 表示在所有单元测试之前执行@AfterAll
: 表示在所有单元测试之后执行@Tag
: 表示单元测试类别,类似于 JUnit4 中的@Categories
@Disabled
: 表示测试类或测试方法不执行,类似于 JUnit4 中的@Ignore
@Timeout
: 表示测试方法运行如果超过了指定时间将会返回错误@ExtendWith
: 为测试类或测试方法提供扩展类引用
1 | import static org.junit.jupiter.api.Assertions.fail; |
常用断言
方法 | 说明 |
---|---|
assertEquals | 判断两个对象或两个原始类型是否相等 |
assertNotEquals | 判断两个对象或两个原始类型是否不相等 |
assertSame | 判断两个对象引用是否指向同一个对象 |
assertNotSame | 判断两个对象引用是否指向不同的对象 |
assertTrue | 判断给定的布尔值是否为 true |
assertFalse | 判断给定的布尔值是否为 false |
assertNull | 判断给定的对象引用是否为 null |
assertNotNull | 判断给定的对象引用是否不为 null |
assertArrayEquals | 数组断言 |
assertAll | 组合断言 |
assertThrows | 异常断言 |
assertTimeout | 超时断言 |
fail | 快速失败 |
嵌套测试
JUnit 5 可以通过 Java 中的内部类和 @Nested
注解实现嵌套测试,从而可以更好的把相关的测试方法组织在一起。在内部类中可以使用 @BeforeEach
和 @AfterEach
注解,而且嵌套的层次没有限制。
1 | package com.clay.boot; |
参数化测试
参数化测试是 JUnit5 很重要的一个新特性,它使得用不同的参数多次运行测试成为了可能,也为单元测试带来许多便利。利用 @ValueSource
等注解,指定传入的参数,将可以使用不同的参数进行多次单元测试,而不需要每新增一个参数就新增一个单元测试,省去了很多冗余代码。
@ValueSource
: 为参数化测试指定入参来源,支持八大基础类以及 String 类型,Class 类型@NullSource
: 表示为参数化测试提供一个null
的入参@EnumSource
: 表示为参数化测试提供一个枚举入参@CsvFileSource
:表示读取指定 CSV 文件内容作为参数化测试入参@MethodSource
:表示读取指定方法的返回值作为参数化测试入参(注意方法的返回值需要是一个流)
1 | import org.junit.jupiter.api.Assertions; |
SpringBoot 事件驱动开发
SpringBoot 提供了基于事件驱动的编程模型,允许开发者将代码以应用程序与 IOC 容器之间的事件进行的方式来组织,实现松散耦合和高内聚。在 SpringBoot 事件驱动的过程中,当事件被触发时,IOC 容器会通过 ApplicationEventPublisher
发布事件。事件监听器通过实现 ApplicationListener
接口,并指定其感兴趣的事件类型来响应此事件。SpringBoot 事件驱动支持异步监听,可以在异步环境中执行事件响应函数,达到事件和响应函数的解耦。例如可以基于 SimpleAsyncTaskExecutor
执行器实现简单的异步处理。本节完整的案例代码,可以直接从 GitHub 下载对应章节 spring-boot3-08
。
事件驱动使用介绍
SpringBoot 事件驱动开发,依赖应用启动过程生命周期事件感知(9 大事件)、应用运行中事件感知(无数种),详细介绍请看 这里。
- 事件发布:实现
ApplicationEventPublisherAware
接口,或者注入ApplicationEventMulticaster
- 事件监听:实现
ApplicationListener
接口,或者使用@EventListener
注解
事件驱动使用场景
SpringBoot 事件驱动的常见使用场景
系统级别
:例如在应用启动或关闭时执行一些任务。业务处理
:例如用户完成注册时,在后台发送电子邮件或短信等。更改数据或状态
:例如在用户完成订单时,将订单数据写入数据库,并发送通知邮件。
SpringBoot 事件监听的常见使用场景
对事件进行处理
:如发送邮件,写日志,执行业务逻辑等。事件发布 / 订阅
:多个组件可以监听同一事件,每个组件可以以自己独特的方式响应事件,例如使用事件获取数据等。事件模型
:自定义 SpringBoot 事件可以成为一个轻量级的消息传递系统。
事件驱动使用案例
这里将模拟用户成功登录后,通过 SpringBoot 的事件驱动模型,分别执行增加用户积分、发送优惠券、记录系统日志等操作。
- 实体类
1 |
|
- 控制器类
1 |
|
- 自定义事件,继承
ApplicationEvent
类
1 | public class LoginSuccessEvent extends ApplicationEvent { |
- 定义事件发送器,实现
ApplicationEventPublisherAware
接口,获取 IOC 容器中的ApplicationEventPublisher
1 |
|
- 接收事件,实现
ApplicationListener
接口(第一种方式)
1 |
|
- 接收事件,使用
@EventListener
注解(第二种方式),可以通过@Order
注解指定接收事件的顺序
1 |
|
1 |
|