理解如何处理Spring Boot的外部配置值

我想介绍一下本次内容,即Spring Boot如何处理外部配置值(即在属性文件、JVM系统属性、环境变量等中定义的配置值)。在之前介绍的“理解Spring Boot的自动配置机制”中,还支持根据条件引用外部配置值的Bean定义机制,并且可以从各个自动配置的配置类中引用外部配置值并进行Bean定义!

前提版本

    • Spring Boot 3.1.1 (1.4.1.BUILD-SNAPSHOT→2.4.5)

Spring Framework 6.0.10 (4.3.3.BUILD-SNAPSHOT→5.3.6)

变更记录
[2023/6/25]
由于投稿已经持续约7年的固定访问量,因此将内容更新为最新的Spring(Spring Boot)版本。此外,还添加了以下部分以反映之前未能实现的功能添加和改进内容。需要注意的是,并未对所有内容进行操作验证,但我认为这些内容在Spring Boot 2.7.x和3.0.x中仍然有效。- 使用@DynamicPropertySource
– 包含配置文件
– 使用配置树
– 使用多文档

[2021/5/7]
由于投稿已经持续约5年的固定访问量,因此将内容更新为最新的Spring(Spring Boot)版本。由于在投稿时的Spring Boot 1.4.x系列之后,外部配置部分进行了相当多的功能添加和改进,

我打算将更新分为两次:
– “根据版本升级对内容进行修订(最低限度的修改)”
– “添加功能部分的说明”

首先,我将进行“根据版本升级对内容进行修订”。

需要将其作为外部设置来处理的值是什么?

在创建应用程序时,经常需要处理不能(或不想)在源代码中进行硬编码的值。具体而言,以下的值可能被视为外部配置值的候选项。

    • データベースへの接続情報などの環境に依存する値

 

    • 機能要件で決められたアプリケーションの動作を変更するためのパラメータ値

 

    • 機能要件上はパラメータ化する必要はないけどテスト容易性などを考慮して外部化しておいた方がよい値

 

    • 性能要件に応じてチューニングが必要になる事が想定されるパラメータ値

 

    etc..

获取外部设置值的方法

在Spring Boot应用程序中,您可以通过以下任一方式获取外部配置值。

    • Spring Bootが提供している「Type-safe Configuration Properties」の仕組みを利用する (基本的にはこの仕組みを使うのがお勧めです )

 

    • Spring Frameworkが提供している@Valueを使用する

 

    Spring Frameworkが提供しているEnvironmentインタフェースを利用する

让我们分别简单介绍一下获取方法吧。

此外,「@Value」和「Type-safe Configuration Properties」在功能上有一些区别(限制),因此建议参考Spring Boot的参考资料。在使用「Type-safe Configuration Properties」时无法使用SpEL,所以只有在需要在属性指定时使用SpEL时,才需要使用「@Value」。

app.timezone=Asia/Tokyo

使用类型安全的配置属性

给在DI容器中管理的类添加@ConfigurationProperties注解,可以自动将配置值(即“prefix属性指定的前缀 + ‘.’ + 属性名”的值)注入到类的属性中。如果想要设置默认值,请指定属性的初始值。

@ConfigurationProperties("app")
public class AppProperties {

  private TimeZone timezone = TimeZone.getTimeZone("UTC"); // ← "app.timezone"の設定値がインジェクションされる

  // ... getter/setter

}
package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;

@SpringBootApplication
@ConfigurationPropertiesScan // @ConfigurationPropertiesが付与されたクラスをスキャンしてDIコンテナに登録する
public class SpringBootPropertiesDemoApplication {

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

}

这种方法也在Spring Boot的AutoConfigure中使用。

@ConfigurationPropertiesScan 是在Spring Boot 2.2中新增加的注解。

@Value的使用

使用@Value(${设置名:默认值})的格式在DI容器中管理的类的字段、setter方法和构造函数参数等上指定时,会注入指定的设置值。默认值是可选的,但是如果在未指定默认值的情况下没有指定设置值,将会出错。

@Component
public class AppProperties {

  @Value("${app.timezone:UTC}") 
  private TimeZone timezone; // ← "app.timezone"の設定値がインジェクションされる

  // ... getter/setter

}

使用Environment接口

外部设定值可以通过Spring Framework提供的Environment接口的方法进行获取。我认为基本上不会使用这种方式,但如果你想在程序内动态地切换获取来源(设置名称)的使用方式,可以考虑使用这种方法。

@Service
public class MyService {
  private static final TimeZone DEFAULT_TIMEZONE= TimeZone.getTimeZone("UTC");
  private final Environment environment;
  public MyService(Environment environment) { // Environmentをインジェクション
    this.environment = environment;
  }
  public void printApplicationManagedTimezone() {
    TimeZone timezone = environment.getProperty("app.timezone", TimeZone.class, DEFAULT_TIMEZONE);
    System.out.println(timezone);
  }
}

指定外部设定值的方法

以下是外部配置值的主要指定方法(按优先级高低排列)的介绍。对于我个人认为可能不会使用的方法,我将省略解释。(有关支持的指定方法的全部信息,请参阅Spring Boot的参考资料)

指定方法説明Developer tools用のプロパティファイルSpring Boot Developer tools用のプロパティファイル(~/.config/spring-boot/spring-boot-devtools.properties)に「設定名=値」の形式で指定する。(Spring Boot Developer toolsを適用している場合のみ)@TestPropertySource@TestPropertySource(テスト用のプロパティファイル or アノテーションの属性値)に「設定名=値」の形式で指定する。テストケース内で設定値を指定したい場合に利用。@DynamicPropertySourceテストケース内に@DynamicPropertySourceを付与したstaticメソッドを用意し、そのメソッド内で動的に値を設定する。これはTestcontainersなどとの接続情報を実行時に取得してSpring Bootの設定値に反映したい場合に使える仕組みです。(Spring Boot 3.1よりService Connectionsと呼ばれる仕組みが導入されたため、Testcontainersを使ったテストで@DynamicPropertySourceを使う必要はなくなっています)@SpringBootTest@SpringBootTest(@XxxxTest)のproperties属性に「設定名=値」の形式で指定する。テストケース内で設定値を指定したい場合に利用。コマンドライン引数コマンドライン引数に「--設定名=値」の形式で指定する。JSONアプリケーションプロパティ環境変数「SPRING_APPLICATION_JSON」(プロパティ名「spring.application.json」)にJSON構造で設定名と設定値を指定するJVMのシステムプロパティJVMの起動引数に「-D設定名=値」の形式で指定する。OSの環境変数OSの環境変数に「設定名=値」の形式で指定する。Spring Boot専用のプロパティファイルSpring Boot専用のプロパティファイル/YAMLファイルに「設定名=値」の形式で指定する。@PropertySource@PropertySourceに指定した任意のプロパティファイルに「設定名=値」の形式で指定する。
如果以war的形式部署,可以在JNDI、servlet容器参数和servlet初始化参数中指定属性值,但考虑到war部署的情况并不常见,本篇文章不详细说明。

开发者工具的属性文件(Developer tools的属性文件)

如果您正在使用Spring Boot开发工具,您可以在操作系统用户的主目录下的”~/.config/spring-boot/spring-boot-devtools.properties”文件中指定每个开发者的配置值。
请注意,这个属性文件应只指定与应用程序的功能性操作规范无关的配置值(例如日志级别)。否则可能会导致在您的环境中正常工作但在其他环境中无法工作的问题发生。

为了确保与之前的版本兼容性,系统会检查是否存在”~/.config/spring-boot/spring-boot-devtools.properties”文件,如果不存在,则会读取”~/.spring-boot-devtools.properties”文件。

使用@SpringBootTest的properties属性

如果只想在特定的测试案例类中更改设定值,可以直接在@SpringBootTest的properties属性中指定设定值。

// ...
@SpringBootTest(properties = "app.timezone=Asia/Tokyo")
public class IntegrationTests {
    // ...
}

@JdbcTest等注解支持的properties属性也是相同处理。

有关具体注解,请参考Spring Boot的参考手册。

@TestPropertySource 的使用

如果您希望在多个测试用例类中共享测试设置值,可以使用属性文件来实现。

// ...
@TestPropertySource(locations = "/test.properties")
public class IntegrationTests {
    // ...
}
app.timezone=Asia/Tokyo

如果只想在特定的测试用例类中更改设置值,可以直接在properties属性中指定设置值。这可以通过前面提到的@SpringBootTest的properties属性来替代。

// ...
@TestPropertySource(properties = "app.timezone=Asia/Tokyo")
public class IntegrationTests {
    // ...
}

请查看Spring Framework参考文档以获取有关@TestPropertySource的详细使用方法。

在中国语境中,使用@DynamicPropertySource

使用Spring Boot 3.0之前的版本进行测试时,可以使用Testcontainers。如果想要在测试运行时将第三方服务或自定义的模拟功能的连接信息识别为Spring Boot的配置值,可以使用DynamicPropertySource。

@SpringBootTest
class MyIntegrationTests {

  static MockTcpServer mockTcpServer;

  @BeforeAll
  static void setupMockServer() {
    // port=0の場合は空きポートで起動
    mockTcpServer = new MockTcpServer(0);
  }

  // ...

  @DynamicPropertySource
  static void mockTcpServerProperties(DynamicPropertyRegistry registry) {
    // MockTcpServerに割り当てられたポート番号を設定値として追加
    registry.add("external.tcp-server.port", mockTcpServer::getPort);
  }

  @Test
  void myTest() {
    // ...
  }

}
如果您在Spring Boot 3.1或更高版本中使用Testcontainers,则不再需要使用@DynamicPropertySource,因为Spring Boot提供了一种称为Service Connections的抽象机制,用于与主要产品进行交互。

命令行参数的使用。

如果您希望在应用程序运行时更改设置值,可以使用命令行参数。

$ java -jar xxx.jar --app.timezone=Asia/Tokyo
使用@SpringBootTest的args属性可以在测试中指定类似于命令行参数的内容。
// …
@SpringBootTest(args = “–app.timezone=亚洲/东京”)
public class IntegrationTests {
// …
}

JSON应用程序属性

由于环境变量和系统属性存在限制,因此可以通过指定环境变量名为”SPRING_APPLICATION_JSON”或属性名为”spring.application.json”的JSON格式属性来绕过这些限制。

$ export SPRING_APPLICATION_JSON='{"app":{"timezone":"Asia/Tokyo"}}' && java -jar xxx.jar
$ java -Dspring.application.json='{"app":{"timezone":"Asia/Tokyo"}}' -jar xxx.jar

在上述的例子中,它将被视为与”app.timezone=Asia/Tokyo”相同的对待方式。

JVM的系统属性

如果你想在应用程序运行时更改配置值,可以使用JVM的系统属性。与Spring Boot独有的命令行参数不同,系统属性是Java标准的机制。因此,如果想要与Spring Boot管理之外的组件共享配置值,考虑使用系统属性可能是一个不错的选择。

$ java -Dapp.timezone=Asia/Tokyo -jar xxx.jar

操作系统的环境变量

如果要针对每个运行应用程序的操作系统用户更改设置值,则可以使用操作系统的环境变量(用户环境变量)。

$ APP_TIMEZONE=Asia/Tokyo
$ java -jar xxx.jar

在环境变量(以区分符号分隔)中指定的配置值,可以使用以点号(.)或连字符(-)分隔的名称进行引用。例如,在上述例子中,可以通过Spring Boot指定名称为”app.timezone”或”app-timezone”来获取值。

Spring Boot专用的属性文件

Spring Boot采用了一种机制,可以加载专用于Spring Boot应用程序的属性文件/YAML文件(如application.properties或application.yml)。专用于Spring Boot应用程序的属性文件/YAML文件的特点之一是可以为每个配置文件准备不同的属性文件/YAML文件。

如果没有明确指定活动配置文件,则会默认隐式指定为default配置文件,并按以下顺序(优先顺序)应用设置值。

application-default.properties (or .yml)

application.properties (or .yml)

指定配置文件时加载属性文件

另外,如果明确指定了活动配置文件,则属性文件/YAML文件的设置值将按照相反的顺序按照配置文件的指定顺序应用。例如,如果设置为-Dspring.profiles.active=p1,p2,则配置值将按照以下顺序(优先顺序)应用。

application-p2.properties (or .yml)

application-p1.properties (or .yml)

application.properties (or .yml)

在包含配置文件时加载

进一步说,对于Spring Boot专用的属性文件/YAML文件,您可以通过以下方式指定以包含配置文件。

spring.profiles.include=default,p3

如果包含了配置文件,那么被包含的属性文件/YAML文件将被优先使用。例如,假设没有指定活动配置文件的情况下,配置值将按以下顺序应用:

application-p3.properties (or .yml)

application-default.properties (or .yml)

application.properties (or .yml)

重点是,包含的配置文件/YAML文件中的属性文件将优先于包含它的文件。例如,无法覆盖包含源中指定的配置值。

app.timezone=PST
spring.profiles.include=default,p3
app.timezone=Asia/Tokyo

在上述示例中,app.timezone会被应用为PST。
尽管直觉上可能会希望它成为Asia/Tokyo,但在当前版本的Spring Boot中,它不会被覆盖。 (Chinese)

包含属性文件

根据目的,可以提供任意的属性文件,并可以包含它们。例如,可以在application.properties中定义默认设置,并在存在用于自定义的属性文件时,优先使用该文件中指定的值。

app.timezone=Asia/Tokyo
spring.config.import=optional:timezone-custom.properties
app.timezone=Asia/Tokyo

由于属性文件包含机制的存在,在配置文件通过配置文件的切换可以实现,例如 -Dspring.profiles.active=p1,如果存在名为timezone-custom-p1.properties的文件,则其中指定的配置值将生效。

如果想要与基于Cloud环境的执行平台提供的配置信息管理机制(包括秘密信息管理等)进行联动,当想要读取它们提供的yaml文件时,可以使用该功能。

属性文件存储位置

在Spring Boot中,专用的属性文件/YAML文件默认情况下存储在以下位置,并且根据以下顺序应用配置值。

    • アプリケーションの実行ディレクトリ直下の「config」ディレクトリのサブディレクトリ(file:./config/*/)

 

    • アプリケーションの実行ディレクトリ直下の「config」ディレクトリ(file:./config/)

 

    • アプリケーションの実行ディレクトリ(file:./)

 

    • クラスパス直下の「config」ディレクトリ(classpath:/config/)

 

    クラスパス直下(classpath:/)

以下是一个可能的中文翻译:

自定义选项
您可以通过指定专用的外部配置值(spring.config.name、spring.config.location、spring.config.additional-location),从而实现更改或存储目录的基础属性文件名称(默认为application)、添加目标文件(如果要针对任意文件添加)等操作。更详细的信息,请参阅”Spring Boot的参考页面”。

使用YAML文件

在Spring Boot中,可以使用YAML文件代替属性文件。相较于属性形式,YAML格式更适合表示数据结构,因此与Spring Boot提供的“类型安全配置属性”相兼容。

app:
    name: MyApp

本投稿不再详细介绍YAML文件的用法,如果要使用YAML文件,请参考”Spring Boot的参考页面”。

使用多文档

使用多文檔機制,可以在一個屬性文件或YAML文件中指定不同的配置值來設定不同的配置文件或雲平台。通過指定預先確定的關鍵詞來分隔多個文檔,然後指定啟用該設定的條件。

值得一提的是,为了启用设置,有两个属性可供使用。

プロパティキー説明spring.config.activate.on-profile指定されたプロファイルと一致する場合に有効になるspring.config.activate.on-cloud-platform指定されたCloud環境と一致した場合に有効になる

■如果是属性文件的情况

# —或者!—将其作为Spring Boot中的唯一有效表达式使用。

app.timezone=UTC
#---
app.timezone=Asia/Tokyo
spring.config.activate.on-profile=p1

■如果是YAML文件

— 这是被 YAML 支持的表达方式。

app:
  timezone: UTC
---
app:
  timezone: Asia/Tokyo
spring:
  config:
    activate:
      on-profile: "p1"  

Configuration Trees的使用

Configuration Trees是一种支持通过Cloud环境的执行平台提供配置信息的代表性方法,即将配置信息的结构提供为文件系统的目录树。

假设以下这样的目录结构中,存在一个存储配置值的文件被挂载。

etc/
  config/
    app/
      timezone ← 設定値が格納されるファイル
Asia/Tokyo

如果想在Spring Boot应用程序中将其作为名为”app.timezone”的配置值进行处理,可以通过以下指定进行加载来实现。

app.timezone=UTC
spring.config.import=optional:configtree:/etc/config/

使用@PropertySource

在Spring Boot中,您还可以使用Spring Framework提供的@PropertySource来读取任意的属性文件。

@SpringBootApplication
@PropertySource("classpath:security.properties")
public class SpringBootDemoApplication  {
    // ...
}

使用占位符进行属性值的替换

在属性文件或YAML文件中,您可以使用占位符将外部配置值嵌入到属性值中。

app.name=MyApp
app.description=${app.name} is a Spring Boot application
app:
    name: MyApp
    description: ${app.name} is a Spring Boot application

此外,Spring Boot提供了用于嵌入随机数的特殊设置值(以random.开头的设置值)。请查看”Spring Boot的参考页面”以获取更详细的信息。

总结

这次,我介绍了在Spring Boot应用程序中处理外部配置值的方法。配置管理是创建应用程序时绝对不能缺少的技术要素之一。在Spring Boot中,与执行环境相关的配置值应被定义在各个配置文件/ YAML文件中,并通过应用程序启动时的参数(spring.profiles.active)来切换使用的配置文件,这是一种标准的管理方法。此外,还支持与云环境中提供的功能进行集成,以选择适合所使用的云环境的最佳方法。

另外,此篇文章还有一些未提及的内容,如果您想全面了解的话,推荐阅读Spring Boot的参考页面。

    https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.external-config

以下是一个选项的中文释义:

参考网站

    • https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.external-config

 

    • https://docs.spring.io/spring-framework/reference/testing/testcontext-framework/ctx-management/property-sources.html

 

    https://docs.spring.io/spring-framework/reference/core/beans/environment.html#beans-using-propertysource
bannerAds