1. 概述
本教程將演示如何通過 Java 配置和 @PropertySource 來在 Spring 中設置和使用屬性。
我們還將瞭解屬性在 Spring Boot 中的工作原理。
2. 通過註解註冊屬性文件
Spring 3.1 還引入了 @PropertySource 註解">@PropertySource 註解作為一種便捷的機制,用於向環境添加屬性源。
我們可以將此註解與 @Configuration 註解">@Configuration 註解結合使用:
@Configuration
@PropertySource("classpath:foo.properties")
public class PropertiesWithJavaConfig {
//...
}使用佔位符註冊新的屬性文件也是一種非常有效的方法,它允許我們在運行時動態地選擇正確的屬性文件:
@PropertySource({
"classpath:persistence-${envTarget:mysql}.properties"
})
...2.1. 定義多個屬性位置
<em>@PropertySource</em> 註解是可重複使用的,遵循 Java 8 的約定(詳見 Java 8 約定)。因此,如果使用 Java 8 或更高版本,我們可以使用此註解來定義多個屬性位置:
@PropertySource("classpath:foo.properties")
@PropertySource("classpath:bar.properties")
public class PropertiesWithJavaConfig {
//...
}當然,我們也可以使用 @PropertySources 註解並指定一個 @PropertySource 的數組。這在任何支持的Java版本中都有效,不僅僅是Java 8或更高版本:
@PropertySources({
@PropertySource("classpath:foo.properties"),
@PropertySource("classpath:bar.properties")
})
public class PropertiesWithJavaConfig {
//...
}在任何情況下,如果屬性名稱發生衝突,則最後讀取的源數據將優先使用。
3. 使用/注入屬性
使用@Value註解注入屬性非常簡單:
@Value( "${jdbc.url}" )
private String jdbcUrl;我們還可以為該屬性指定一個默認值:
@Value( "${jdbc.url:aDefaultUrl}" )
private String jdbcUrl;新添加的 PropertySourcesPlaceholderConfigurer 在 Spring 3.1 中 可以解析 bean 定義屬性值和 @Value 註解中的 ${…} 佔位符。
最後,我們可以使用 Environment API 獲取屬性值:
@Autowired
private Environment env;
...
dataSource.setUrl(env.getProperty("jdbc.url"));4. 使用 Spring Boot 的屬性
在深入探討 Spring Boot 中更高級的配置選項之前,我們先花一些時間來了解一下新的屬性支持。
總的來説,這種支持與標準 Spring 相比,配置量更少,這當然是 Boot 的主要目標之一。
4.1. application.properties: 默認屬性文件
Boot 採用約定優於配置的策略來處理屬性文件。這意味着我們可以簡單地將 application.properties 文件放在我們的 src/main/resources 目錄下,它會自動被檢測到。然後我們可以像注入普通屬性一樣注入其中加載的任何屬性。
因此,通過使用默認文件,我們無需顯式註冊 PropertySource,甚至不需要提供屬性文件的路徑。
我們還可以使用運行時屬性配置不同的文件,如果需要的話:
java -jar app.jar --spring.config.location=classpath:/another-location.properties自 Spring Boot 2.3 以來,我們也可以指定配置文件的通配符位置。
例如,我們可以將 spring.config.location 屬性設置為 config/*/。
java -jar app.jar --spring.config.location=config/*/這樣一來,Spring Boot 將在我們的 JAR 文件之外查找與 config/*/ 目錄模式匹配的配置文件。這在我們需要多個配置屬性源時非常有用。
自版本 2.4.0 起,Spring Boot 支持使用多文檔屬性文件,類似於 YAML 的設計:
baeldung.customProperty=defaultValue
#---
baeldung.customProperty=overriddenValue請注意,對於屬性文件,三連字符表示法必須以註釋字符(#)開頭。
4.2. 環境特定屬性文件
如果需要針對不同的環境進行配置,Boot 提供了內置機制來實現這一點。
只需在 src/main/resources 目錄下定義一個 application-environment.properties 文件,然後設置一個與環境名稱相同的 Spring 配置文件。
例如,如果定義了一個名為“staging”的環境,則需要定義一個 staging 配置文件,並使用 application-staging.properties 文件。
此 env 文件將被加載,並且 它將優先於默認屬性文件。 請注意,默認文件仍將被加載,但當存在屬性衝突時,環境特定的屬性文件將優先使用。
4.3. 測試特定屬性文件
我們可能需要在使用應用程序進行測試時,使用不同的屬性值。
Spring Boot 通過在測試運行時查找我們的 src/test/resources 目錄來處理這種情況。 再次強調,默認屬性仍然可以正常注入,但如果存在衝突,將被這些屬性覆蓋。
4.4. <em @TestPropertySource</em> 註解
如果需要對測試屬性進行更精細的控制,則可以使用 <em @TestPropertySource</em> 註解。
這允許我們為特定的測試上下文設置測試屬性,這些屬性將優先於默認屬性源。
@RunWith(SpringRunner.class)
@TestPropertySource("/foo.properties")
public class FilePropertyInjectionUnitTest {
@Value("${foo}")
private String foo;
@Test
public void whenFilePropertyProvided_thenProperlyInjected() {
assertThat(foo).isEqualTo("bar");
}
}如果我們不想使用文件,我們可以直接指定名稱和值:
@RunWith(SpringRunner.class)
@TestPropertySource(properties = {"foo=bar"})
public class PropertyInjectionUnitTest {
@Value("${foo}")
private String foo;
@Test
public void whenPropertyProvided_thenProperlyInjected() {
assertThat(foo).isEqualTo("bar");
}
}我們也可以通過使用 properties 參數來達到類似的效果,利用 @SpringBootTest註解:
@RunWith(SpringRunner.class)
@SpringBootTest(
properties = {"foo=bar"}, classes = SpringBootPropertiesTestApplication.class)
public class SpringBootPropertyInjectionIntegrationTest {
@Value("${foo}")
private String foo;
@Test
public void whenSpringBootPropertyProvided_thenProperlyInjected() {
assertThat(foo).isEqualTo("bar");
}
}4.5. 層次屬性
如果我們的屬性被分組在一起,我們可以利用 @ConfigurationProperties 註解,將這些屬性層次結構映射到 Java 對象圖。
讓我們以用於配置數據庫連接的一些屬性為例:
database.url=jdbc:postgresql:/localhost:5432/instance
database.username=foo
database.password=bar然後,我們使用標註將它們映射到數據庫對象:
@ConfigurationProperties(prefix = "database")
public class Database {
String url;
String username;
String password;
// standard getters and setters
}Spring Boot 再次採用約定優於配置的原則,自動將屬性名稱與相應的字段進行映射。我們只需要提供屬性前綴即可。
如果您想更深入地瞭解配置屬性,請查看我們的深入文章。
4.6. 替代方案:YAML 文件
Spring 也支持 YAML 文件。
所有測試特定、環境特定和默認屬性文件的命名規則都適用。唯一區別在於文件擴展名以及依賴於 SnakeYAML 庫在類路徑上的依賴。
YAML 特別適合用於存儲層次化屬性;以下屬性文件:
database.url=jdbc:postgresql:/localhost:5432/instance
database.username=foo
database.password=bar
secret: foo以下是翻譯後的內容:
與以下 YAML 文件相同:
database:
url: jdbc:postgresql:/localhost:5432/instance
username: foo
password: bar
secret: foo此外,還值得一提的是 YAML 文件不支持 @PropertySource 註解,因此如果需要使用該註解,我們只能使用屬性文件。
另一個顯著的特點是,在 2.4.0 版本中,Spring Boot 改變了從多文檔 YAML 文件加載屬性的方式。 以前,它們添加的順序基於激活的 profile 順序。 但是,新版本中,框架遵循我們之前為 .properties 文件指示的相同排序規則:文件中聲明的屬性會覆蓋更高位置的屬性。
此外,在此版本中,profile 無法從特定 profile 文檔中激活,從而使結果更清晰、更可預測。
4.7. 導入額外配置文件
在 2.4.0 版本之前,Spring Boot 允許使用 spring.config.location 和 spring.config.additional-location 屬性包含額外的配置文件,但這些屬性存在一些限制。例如,它們必須在啓動應用程序之前定義(作為環境或系統屬性,或使用命令行參數),因為它們在早期階段被使用。
在上述版本中,我們可以使用 spring.config.import 屬性在 application.properties 或 application.yml 文件中輕鬆包含額外的文件。 此屬性支持一些有用的功能:
- 添加多個文件或目錄
- 文件可以從類路徑或外部目錄加載
- 指示啓動過程是否應在文件未找到時失敗,或者它是否為可選文件
- 導入沒有擴展名的文件
讓我們來看一個有效的示例:
spring.config.import=classpath:additional-application.properties,
classpath:additional-application[.yml],
optional:file:./external.properties,
classpath:additional-application-properties/注意:我們使用換行符格式化了此屬性,僅為增強清晰度。
Spring 會將導入視為立即插入在導入聲明下方的新文檔。
4.8. 從命令行參數傳遞屬性
除了使用文件之外,我們還可以直接通過命令行傳遞屬性:
java -jar app.jar --property="value"我們也可以通過系統屬性來完成,這些屬性在 -jar 命令之前提供,而不是之後提供:
java -Dproperty.name="value" -jar app.jar4.9. 從環境變量中獲取屬性
Spring Boot 也會檢測環境變量,並將它們視為屬性:
export name=value
java -jar app.jar
4.10. 屬性值的隨機化
如果不想使用確定性屬性值,可以使用 <a href="https://docs.spring.io/spring-boot/docs/1.5.7.RELEASE/api/org/springframework/boot/context/config/RandomValuePropertySource.html">RandomValuePropertySource</a> 來隨機化屬性值:
random.number=${random.int}
random.long=${random.long}
random.uuid=${random.uuid}4.11. 額外屬性源類型
Spring Boot 支持多種屬性源,並採用精心設計的排序機制,以允許合理的覆蓋。 建議參考 官方文檔,該文檔的內容比本文檔更全面。
5. 使用原始 Bean 進行配置 — PropertySourcesPlaceholderConfigurer
除了 Spring 提供的便捷方法來獲取屬性外,我們還可以手動定義和註冊屬性配置 Bean。
使用 PropertySourcesPlaceholderConfigurer 能夠完全控制配置,但同時也更冗長,並且在大多數情況下是不必要的。
下面我們將展示如何使用 Java 配置來定義此 Bean:
@Bean
public static PropertySourcesPlaceholderConfigurer properties(){
PropertySourcesPlaceholderConfigurer pspc
= new PropertySourcesPlaceholderConfigurer();
Resource[] resources = new ClassPathResource[ ]
{ new ClassPathResource( "foo.properties" ) };
pspc.setLocations( resources );
pspc.setIgnoreUnresolvablePlaceholders( true );
return pspc;
}6. 屬性在父子上下文中
這個問題再次出現:當我們的 Web 應用程序有父上下文和子上下文時,會發生什麼?父上下文可能具有一些通用的核心功能和 Bean,然後一個(或多個)子上下文,可能包含 Servlet 相關的 Bean。
在這種情況下,如何定義屬性文件並在這些上下文中包含它們?以及如何從 Spring 中檢索這些屬性?
我們將提供一個簡單的分解。
如果文件 定義在父上下文中:
- @Value 在 子上下文 中有效:是
- @Value 在 父上下文 中有效:是
- environment.getProperty 在 子上下文 中有效:是
- environment.getProperty 在 父上下文 中有效:是
如果文件 定義在子上下文中:
- @Value 在 子上下文 中有效:是
- @Value 在 父上下文 中有效:否
- environment.getProperty 在 子上下文 中有效:是
- environment.getProperty 在 父上下文 中有效:否
7. 結論
本文介紹了在 Spring 中使用屬性和屬性文件的一些示例。