在上一篇 Spring Boot 测试教程 中,我们学习了如何为 Spring Boot 应用编写单元测试、片段测试和集成测试。
在本教程中,你将学习如何使用 properties
和 YAML
文件配置 Spring Boot 应用程序,以便在不同环境中运行应用。
外部化 Spring Boot 的配置 {#外部化-spring-boot-的配置}
大多数应用程序都会有一些配置项目,你希望这些项目是可以灵活配置的,而不是在程序代码中硬编码这些值。
Spring Boot 提供了许多 配置 application properties 的方法,其中我们最有可能使用的只有以下方法:
- 默认
src/main/resources/application.{properties/yml}
中的属性值。 - 特定 profile
src/main/resources/application-{profile}.{properties/yml}
中的属性值。 - 使用环境变量(Environment Variables)或系统属性(System Properties)覆盖默认配置。
在我们的应用程序中,通常会有两种配置属性:
- Spring Boot 定义了用于配置 DataSource、Kafka 等服务的属性。例如:
spring.datasource.url
、spring.datasource.username
、spring.datasource.password
等。 - 应用特定的配置属性。
配置默认属性 {#配置默认属性}
Spring Boot 默认从 src/main/resources/application.{properties/yml}
中加载属性。例如,我们可以使用 application.properties
文件配置应用属性,如下所示:
spring.datasource.url=jdbc:postgresql://localhost:5432/postgres
spring.datasource.username=postgres
spring.datasource.password=secret123
ftp.host=ftpsrv001
ftp.port=21
ftp.username=appuser1
ftp.password=secret321
同样的配置值也可以使用 application.yml
文件进行配置,如下所示:
spring:
datasource:
url: jdbc:postgresql://localhost:5432/postgres
username: postgres
password: secret123
ftp:
host: ftpsrv001
port: 21
username: appuser1
password: secret321
配置特定于 Profile 的属性 {#配置特定于-profile-的属性}
我们的应用可能会在不同的环境中运行,如本地、开发、QA、暂存和生产环境。我们可能希望为不同的环境配置不同的值。在这种情况下,我们可以使用 Spring 的 profile 概念来为不同环境配置不同的值。
假设我们想根据运行环境配置不同的数据库连接属性。我们可以在默认的 application.properties
文件中配置本地数据库参数。使用 qa
和 staging
properties 文件来配置 qa
和 staging
环境的属性,如下所示:
application.properties:
# application.properties
spring.datasource.url=jdbc:postgresql://localhost:5432/postgres
spring.datasource.username=postgres
spring.datasource.password=secret123
application-qa.properties:
# application-qa.properties
spring.datasource.url=jdbc:postgresql://qadb:5432/postgres
spring.datasource.username=postgres
spring.datasource.password=secret123
application-staging.properties:
# application-staging.properties
spring.datasource.url=jdbc:postgresql://stagingdb:5432/postgres
spring.datasource.username=postgres
spring.datasource.password=secret123
使用特定 profile 运行应用程序时,首先加载默认配置文件中的属性,然后会用特定 profile 中的属性覆盖它们。
与其为不同的 profile 创建单独的 properties 文件,还不如将所有配置文件属性放在 application.properties
文件中,并使用 "#---"
或 "!---"
分隔符将特定于 profile 的配置分开,如下所示:
application.properties:
spring.datasource.url=jdbc:postgresql://localhost:5432/postgres
spring.datasource.username=postgres
spring.datasource.password=secret123
#---
spring.config.activate.on-profile=qa
spring.datasource.url=jdbc:postgresql://qadb:5432/postgres
spring.datasource.username=postgresqa
spring.datasource.password=secret1235
#---
spring.config.activate.on-profile=staging
spring.datasource.url=jdbc:postgresql://stagingdb:5432/postgres
spring.datasource.username=postgresstaging
spring.datasource.password=secret1236
如果使用 YAML 文件,则可以在同一个 application.yml
文件中使用 "---"
分隔符配置不同的 profile 属性,如下所示:
spring:
datasource:
url: jdbc:postgresql://localhost:5432/postgres
username: postgres
password: secret123
---
spring:
config:
activate:
on-profile: "qa"
datasource:
url: jdbc:postgresql://qadb:5432/postgres
username: postgres
password: secret1235
使用环境变量 {#使用环境变量}
一种常见的方法是在配置文件中定义默认属性和特定 profile 的属性,这些配置文件将打包到 jar 文件中,然后根据需要使用环境变量覆盖这些值。
Spring Boot 支持 宽松绑定(Relaxed Binding),允许我们使用环境变量,变量名以大写字母和下划线分隔,如下所示:
spring.datasource.url
属性值可由环境变量SPRING_DATASOURCE_URL
覆盖。app.jwt.expiryDuration
属性值可被环境变量APP_JWT_EXPIRY_DURATION
覆盖。
如何在应用中使用配置的属性? {#如何在应用中使用配置的属性}
我们可以通过多种方式使用配置的属性值。在上述示例中,我们配置了 DataSource
属性,Spring Boot 将使用这些属性创建 DataSource
Bean。
让我们看看如何使用已配置的 FTP server 详情。
使用 Environment {#使用-environment}
你可以使用 Spring 的 Environment
访问应用的所有属性。你可以注入 Environment
Bean 并按如下方式访问属性:
import org.springframework.core.env.Environment;
@Service
class MyService {
private final Environment environment;
public MyService(Environment environment) {
this.environment = environment;
}
void upload(File file) {
String host = environment.getProperty("ftp.host", "localhost");
Integer port = environment.getProperty("ftp.port", Integer.class, 21);
String username = environment.getRequiredProperty("ftp.username");
String password = environment.getRequiredProperty("ftp.password");
...
...
}
}
如你所见,我们可以使用 environment.getProperty("...")
获取配置值。你还可以使用其他重载方法来指定默认值、属性的数据类型等。使用 environment.getRequiredProperty("...")
时,如果属性值为 null
,则会抛出 IllegalStateException
。
Spring 的 Environment
会将代码与 Spring 框架耦合在一起,不如使用 @Value
注解直接注入属性值。
使用 @Value("${property.name}")
{#使用-valuepropertyname}
你可以使用 @Value
注解注入属性值,如下所示:
@Service
class MyService {
private final String ftpHost;
private final Integer ftpPort;
private final String ftpUsername;
private final String ftpPassword;
public MyService(@Value("${ftp.host:localhost}") String ftpHost,
@Value("${ftp.port:21}") Integer ftpPort,
@Value("${ftp.username}") String ftpUsername,
@Value("${ftp.password}") String ftpPassword
) {
this.ftpHost = ftpHost;
this.ftpPort = ftpPort;
this.ftpUsername = ftpUsername;
this.ftpPassword = ftpPassword;
}
void upload(File file) {
...
...
}
}
我们使用了 Spring 属性引用语法 @Value("${property.name}")
来注入配置值。另外,我们还可以使用 @Value("${property.name:defaultValue}")
指定默认值,以注入配置值。
你也可以使用 "字段注入" 来注入属性值,但这是非常不推荐的。
@Service
class MyService {
@Value("${ftp.host:localhost}")
private String ftpHost;
@Value("${ftp.port:21}")
private Integer ftpPort;
@Value("${ftp.username}")
private String ftpUsername;
@Value("${ftp.password}")
private String ftpPassword;
}
使用 @Value("${property.name}")
方法虽然有效,但却非常啰嗦。还有另一种方法,即使用类型安全的配置属性绑定来使用配置的属性。
使用类型安全的配置属性绑定 {#使用类型安全的配置属性绑定}
与其使用 @Value
分别绑定每个属性值,我们可以创建一个包含属性的类,然后让 Spring 框架通过使用 @ConfigurationProperties
将配置值绑定到该配置属性类的实例中,如下所示:
@ConfigurationProperties(prefix = "ftp")
public class FtpProperties {
private String host;
private Integer port;
private String username;
private String password;
}
定义配置属性类后,我们需要使用 @EnableConfigurationProperties(FtpProperties.class)
启用绑定。这通常在应用程序 main 类中完成,如下所示:
@SpringBootApplication
@EnableConfigurationProperties(FtpProperties.class)
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
我们可以拥有任意多的配置属性类,并使用 @EnableConfigurationProperties({FtpProperties.class, SomeProperties.class, OtherProperties.class})
进行注册。也可以使用 @ConfigurationPropertiesScan
来扫描所有注解了 @ConfigurationProperties
的类并自动注册它们,而不是显式地指定每个配置属性类。
@SpringBootApplication
@ConfigurationPropertiesScan
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
不过,一种常见的方法是使用一个配置属性类,并为不同的属性组使用嵌套类,如下所示:
@ConfigurationProperties(prefix = "app")
public class ApplicationProperties {
private final FtpProperties ftp = new FtpProperties();
private final Jwt jwt = new Jwt();
public static class FtpProperties {
private String host;
private Integer port;
private String username;
private String password;
//setters & getters
}
public static class Jwt {
private String secret;
private Long expiryDuration;
//setters & getters
}
}
现在,你可以使用 app.{prefix}
对 application properties 进行如下配置:
app.ftp.host=ftpsrv001
app.ftp.port=21
app.ftp.username=appuser1
app.ftp.password=secret321
app.jwt.secret=supersecret
app.jwt.expiryDuration=3600000
使用 Java Record 进行配置属性绑定 {#使用-java-record-进行配置属性绑定}
通常,一旦应用启动且 Spring 初始化了配置属性类,我们就不会更改该对象中的属性值。在这种情况下,Java Record 比普通类更适合。
让我们看看如何使用 Java Record 作为配置属性类。
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.bind.DefaultValue;
@ConfigurationProperties(prefix = "app")
public record ApplicationProperties(
FtpProperties ftp,
Jwt jwt){
public record FtpProperties(
@DefaultValue("localhost")
String host,
@DefaultValue("21")
Integer port,
String username,
String password){
}
public record Jwt(
String secret,
Long expiryDuration){
}
}
使用 record 绑定配置属性将有助于防止意外修改值,因为你无法更改记录的值。
校验绑定的配置属性 {#校验绑定的配置属性}
我们可以使用 Java Bean Validation API 来验证配置属性值,以便在应用启动过程中出现配置无效的情况时能快速失效。
为了使用 Java Bean Validation API,我们需要添加以下依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
现在,我们可以使用 Bean 验证注解,如 @NotNull
、@NotEmpty
、@Min
、@Max
,如下所示:
import jakarta.validation.Valid;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotEmpty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;
@ConfigurationProperties(prefix = "app")
@Validated
public class ApplicationProperties {
@Valid
private final FtpProperties ftp = new FtpProperties();
@Valid
private final Jwt jwt = new Jwt();
public static class FtpProperties {
@NotEmpty
private String host;
private Integer port;
@NotEmpty
private String username;
@NotEmpty
private String password;
//setters & getters
}
public static class Jwt {
@NotEmpty
private String secret;
@NotNull
@Min(30000)
private Long expiryDuration;
//setters & getters
}
}
请注意,为了触发验证,我们需要在顶层类上添加 @Validated
注解,而为了验证嵌套类属性的属性,我们需要在属性声明中添加 @Valid
注解。
高级配置选项 {#高级配置选项}
除上述方法外,Spring Boot 还支持更多高级选项来配置应用属性。
- Spring Cloud Config Server
- Spring Cloud Vault
- AWS Secrets Manager 和 Parameter Store
- Kubernetes ConfigMaps 和 Secrets
这些配置选项将在今后的教程中单独讲解。
总结 {#总结}
我们学习了如何使用默认属性、特定 profile 属性和环境变量将应用配置外部化。我们还学习了如何从应用程序中使用这些配置属性。最后,我们还学习了如何使用 Java Bean Validation API 注解验证应用配置属性值。
参考:https://www.sivalabs.in/spring-boot-application-configuration-tutorial/