文章作者:Tyan
博客:noahsnail.com ?|? CSDN ?|? 簡書
24. 外部配置
Spring Boot允許你進行外部化配置,因此可以將同樣的應(yīng)用代碼在不同的環(huán)境中運行。你可以使用屬性文件,YAML文件,環(huán)境變量和命令行參數(shù)來進行外部化配置。屬性值可以使用@Value
注解直接注入到你的beans中,通過Spring的Environment
抽象或通過@ConfigurationProperties
綁定到結(jié)構(gòu)化對象上來訪問。
Spring Boot使用非常特別的PropertySource
順序,這個順序的設(shè)計是為了允許值的合理重寫。屬性被認為是按照以下順序:
- 根目錄下的開發(fā)工具全局設(shè)置屬性(當開發(fā)工具激活時為
~/.spring-boot-devtools.properties
)。 - 測試中的
@TestPropertySource
注解。 - 測試中的
@SpringBootTest#properties
注解特性。 - 命令行參數(shù)。
-
SPRING_APPLICATION_JSON
中的屬性(環(huán)境變量或系統(tǒng)屬性中的內(nèi)聯(lián)JSON嵌入)。 -
ServletConfig
初始化參數(shù)。 -
ServletContext
初始化參數(shù)。 -
java:comp/env
的JNDI特性。 - Java系統(tǒng)屬性 (
System.getProperties()
)。 - 操作系統(tǒng)環(huán)境變量。
-
RandomValuePropertySource
只在random.*
中有屬性。 - jar包之外的指定配置文件的應(yīng)用屬性(
application-{profile}.properties
和YAML變量)。 - jar包之內(nèi)的指定配置文件的應(yīng)用屬性(
application-{profile}.properties
和YAML變量)。 - jar包之外的應(yīng)用屬性 (
application.properties
和YAML變量)。 - jar包之內(nèi)的應(yīng)用屬性 (
application.properties
和YAML變量)。 -
@Configuration
類中的@PropertySource
注解 。 - 默認屬性(通過
SpringApplication.setDefaultProperties
指定).
為了提供一個具體的例子,假設(shè)你開發(fā)了一個使用名字屬性的@Component
:
import org.springframework.stereotype.*
import org.springframework.beans.factory.annotation.*
@Component
public class MyBean {
@Value("${name}")
private String name;
// ...
}
在你的應(yīng)用路徑中(例如在你的jar內(nèi)部),你可以使用application.properties
為name
提供一個合理的默認屬性值。當在新環(huán)境運行時,application.properties
可以在jar外部提供來重寫name
;對于一次性測試,你可以通過指定的命令行切換來啟動(例如java -jar app.jar --name="Spring"
)。
SPRING_APPLICATION_JSON
可以在命令行中通過環(huán)境變量提供。例如在UNIX shell中:
$ SPRING_APPLICATION_JSON='{"foo":{"bar":"spam"}}' java -jar myapp.jar
在這個例子中,Spring的
Environment
中會有foo.bar=spam
。你也可以在系統(tǒng)變量中提供JSON作為spring.application.json
。
$ java -Dspring.application.json='{"foo":"bar"}' -jar myapp.jar
或命令行參數(shù):
$ java -jar myapp.jar --spring.application.json='{"foo":"bar"}'
或JNDI變量
java:comp/env/spring.application.json
24.1 配置隨機值
當注入隨機值時,RandomValuePropertySource
是很有用的(例如,注入秘密或測試用例)。它可以產(chǎn)生integers
,longs
,uuids
或strings
,例如:
my.secret=${random.value}
my.number=${random.int}
my.bignumber=${random.long}
my.uuid=${random.uuid}
my.number.less.than.ten=${random.int(10)}
my.number.in.range=${random.int[1024,65536]}
random.int*
語法OPEN value (,max) CLOSE
,OPEN,CLOSE
可以是任何字符,value,max
是整數(shù)。如果提供了max
,則value
是最小值,max
是最大值(不包含)。
24.2 訪問命令行屬性
默認情況下,SpringApplication
會將任何命令行參數(shù)(以--
開頭,例如--server.port=9000
)轉(zhuǎn)換成property
并將其添加到Spring的Environment
中。正如前面提到的那樣,命令行屬性總是優(yōu)先于其它的配置源。
如果你想命令行屬性添加到Environment
中,你可以使用SpringApplication.setAddCommandLineProperties(false)
禁用它。
24.3 應(yīng)用屬性文件
SpringApplication
會從以下位置的application.properties
文件中加載屬性并將它們添加到Spring的Environment
中:
當前目錄的子目錄
/config
當前目錄
classpath中的
/config
包classpath的根目錄
這個列表是按優(yōu)先級排序的(在更高位置的屬性會重寫定義在更低位置的屬性)。
你也可以使用YAML(
.yml
)文件來代替.properties
文件。
如果你不喜歡用application.properties
作為配置文件的名字,你可以通過指定spring.config.name
環(huán)境屬性來來改變配置文件的名字。你也可以使用spring.config.location
環(huán)境屬性來引用一個顯式的位置(目錄位置或文件路徑以逗號分隔)。
$ java -jar myproject.jar --spring.config.name=myproject
或
$ java -jar myproject.jar --spring.config.location=classpath:/default.properties,classpath:/override.properties
spring.config.name
和spring.config.location
可以在早期用來決定加載哪一個文件,因此必須被定義為環(huán)境屬性(通常是操作系統(tǒng)環(huán)境,系統(tǒng)屬性或命令行參數(shù))。
如果spring.config.location
包含目錄(相對于文件而言),它們應(yīng)該以/
結(jié)尾(在加載之前,在后面添加上從spring.config.name
中產(chǎn)生的名字,包括指定配置文件的名字)。在spring.config.location
中指定的文件按原樣使用,不支持指定配置文件變量,將會被任何指定配置文件的屬性覆蓋。
默認搜索路徑一直用classpath:,classpath:/config,file:,file:config/
,不管spring.config.location
中的值。搜索路徑按從低到高排序(file:config/
最高)。如果你指定了自己的位置,它們優(yōu)先于所有的默認位置并使用同樣的從低到高的順序。這樣你可以在application.properties
中為你的應(yīng)用設(shè)置默認值(或你可以選擇spring.config.name
的其它生成文件基本名),在運行時用其它的文件覆蓋它,同時保留默認值。
如果你使用環(huán)境變量而不是系統(tǒng)屬性,大多數(shù)操作系統(tǒng)不允許句號分隔的關(guān)鍵字,但你可以用下劃線代替(例如,
SPRING_CONFIG_NAME
代替spring.config.name
)。
如果你在容器中運行,那么JNDI屬性(在
java:comp/env
中)或servlet上下文初始化參數(shù)也可以用來代替環(huán)境變量或系統(tǒng)屬性。
24.4 特定的profile屬性
除了application.properties
文件之外,特定的profile屬性也可以使用命名規(guī)范application-{profile}.properties
來定義。Environment
有一系列默認配置文件(默認為[default]
),如果沒有設(shè)置激活的配置文件,會使用默認的配置文件(例如,如果沒有激活顯式的配置文件,則會加載application-default.properties
中的屬性)。
特定的profile屬性會從相同位置加載application.properties
,特定的profile文件總是覆蓋非特定的配置文件,無論特定profile文件在你打包的jar內(nèi)部還是外部。
如果指定了幾個配置文件,將會應(yīng)用最后一個。例如,spring.profiles.active
屬性指定的配置文件在那些配置的文件之后通過SpringApplication
API添加,因此優(yōu)先級更高。
如果你在
spring.config.location
中指定了任何文件,那些文件的特定profile版本將不會被考慮。如果你也想使用特定的profile屬性,在spring.config.location
中使用目錄。
24.5 屬性中的占位符
當使用application.properties
中的值時,會通過現(xiàn)有的Environment
進行過濾,因此你可以參考前面定義的值(例如從系統(tǒng)屬性中)。
app.name=MyApp
app.description=${app.name} is a Spring Boot application
你也可以使用這個技術(shù)來創(chuàng)建現(xiàn)有的Spring Boot屬性的
short
版本。怎樣使用的更多細節(jié)請看70.4小節(jié),“Use ‘short’ command line arguments”。
24.6 使用YAML代替Properties
YAML是JSON的超集,它可以用一種非常方便的形式來指定分層配置數(shù)據(jù)。當你的類路徑有SnakeYAML庫時,SpringApplication
類自動支持YAML作為properties的一個替代品。
如果你使用‘Starters’,SnakeYAML將由
spring-boot-starter
自動提供。
24.6.1 加載YAML
Spring框架提供了兩個類用來方便的加載YAML文檔。YamlPropertiesFactoryBean
將加載YAML作為Properties
,YamlMapFactoryBean
將加載YAML作為Map
。
例如,下面的YAML文檔:
environments:
dev:
url: http://dev.bar.com
name: Developer Setup
prod:
url: http://foo.bar.com
name: My Cool App
將被轉(zhuǎn)換成這些屬性:
environments.dev.url=http://dev.bar.com
environments.dev.name=Developer Setup
environments.prod.url=http://foo.bar.com
environments.prod.name=My Cool App
YAML列表通過[index]
解引用表示為屬性的key,例如這個YAML:
my:
servers:
- dev.bar.com
- foo.bar.com
將被轉(zhuǎn)換成這些屬性:
my.servers[0]=dev.bar.com
my.servers[1]=foo.bar.com
為了像使用Spring的DataBinder
一樣(@ConfigurationProperties
的功能)綁定這些屬性,你需要在類型為java.util.List
(或Set
)的目標bean中有屬性,你需要提供一個setter
或用一個可變的值來對它初始化,例如,綁定上面的屬性值:
@ConfigurationProperties(prefix="my")
public class Config {
private List<String> servers = new ArrayList<String>();
public List<String> getServers() {
return this.servers;
}
}
24.6.2 在Spring Environment中公開YAML為屬性
YamlPropertySourceLoader
類可以在Spring的Environment
中將YAML作為PropertySource
。這允許你使用熟悉的@Value
注解和占位符語法來訪問YAML屬性。
24.6.3 多profile的YAML文檔
你可以在單個文件中指定多個特定profile的YAML文檔,當應(yīng)用文檔時,通過spring.profiles
關(guān)鍵字來表明使用哪個文檔。例如:
server:
address: 192.168.1.100
---
spring:
profiles: development
server:
address: 127.0.0.1
---
spring:
profiles: production
server:
address: 192.168.1.120
在上面的例子中,如果development
profile被激活,server.address
的值為127.0.0.1
。如果development
和production
profile不可用,server.address
的值為192.168.1.100
。
當應(yīng)用上下文啟動時,如果沒有顯式的激活profile,則激活默認的profile。因此在這個YAML文件中我們僅在"default" profile中設(shè)置了security.user.password
。
server:
port: 8000
---
spring:
profiles: default
security:
user:
password: weak
在這個例子中,密碼總是設(shè)置的,因為它沒有添加到如何profile中,必要時我們必須在其它的profile中顯式的對它重新設(shè)置:
server:
port: 8000
security:
user:
password: weak
Spring profiles被設(shè)計為使用"spring.profiles"元素可以選擇性的用!
字符進行否定。如果否定的和非否定的profile指向一個單獨的文檔,必須至少匹配一個非否定的profile,可能沒有否定的profile進行匹配。
24.6.4 YAML缺點
YAML文件不能通過@PropertySource
注解進行加載。因此在這種情況下如果你需要加載值,你需要使用屬性文件。
24.6.5 合并YAML列表
正如我們上面看到的,任何YAML內(nèi)容最終都要轉(zhuǎn)換成屬性。當通過profile重寫“l(fā)ist“屬性時,這個過程可能有違直覺。
例如,假設(shè)MyPojo
對象的name
和description
屬性默認情況下為空。從FooProperties
使用一個MyPojo
列表:
@ConfigurationProperties("foo")
public class FooProperties {
private final List<MyPojo> list = new ArrayList<>();
public List<MyPojo> getList() {
return this.list;
}
}
考慮下面的配置:
foo:
list:
- name: my name
description: my description
---
spring:
profiles: dev
foo:
list:
- name: my another name
如果dev
profile沒有激活,FooProperties.list
將包含一個上面定義的MyPojo
輸入。然而如果dev
profile可用,lists
仍只包含一個輸入(name為“my another name”,description為空)。這個配置將不能添加第二個MyPojo
到list
中,并且它將不能合并這些項。
當在多個profiles中指定一個集合時,將會使用最高優(yōu)先級的那個集合(唯一的哪個):
foo:
list:
- name: my name
description: my description
- name: another name
description: another description
---
spring:
profiles: dev
foo:
list:
- name: my another name
在上面的例子中,假設(shè)dev
profile被激活,FooProperties.list
將包含一個MyPojo
輸入(name為“my another name”,description為空)。
24.7 類型安全的配置屬性
Boot提供了一種處理屬性的可替代方法,允許強類型的beans管理和驗證你的應(yīng)用的配置。例如:
@ConfigurationProperties(prefix="connection")
public class ConnectionProperties {
private String username;
private InetAddress remoteAddress;
// ... getters and setters
}
建議添加getters和setters,綁定是通過標準的Java Beans屬性描述符,像在Spring MVC中一樣。對于不可變類型或那些從
String
中可直接強制轉(zhuǎn)換的類型,它們是強制性的。只要它們被初始化,maps,集合或數(shù)組需要getter方法,但不需要setter方法因為通過綁定起它們可以直接變化。如果有setter,可以創(chuàng)建Maps,集合和數(shù)組。Maps和集合可以通過getter擴展,數(shù)組擴展需要setter。如果它們有默認的構(gòu)造函數(shù),或構(gòu)造函數(shù)接收可以從String
類型強制轉(zhuǎn)換的值,嵌入的POJO屬性也可以創(chuàng)建(因此setter不是強制性的)。一些人使用Lombok項目來自動添加getter和setter。
?
請看
@Value
和@ConfigurationProperties
之間的不同。
你也需要在@EnableConfigurationProperties
注解中列出要注冊的屬性類:
@Configuration
@EnableConfigurationProperties(ConnectionProperties.class)
public class MyConfiguration {
}
當
@ConfigurationProperties
以那種方式注冊時,這個bean將有一個常規(guī)的名字:<prefix>-<fqn>
,<prefix>
是在@ConfigurationProperties
注解中指定的環(huán)境關(guān)鍵字的前綴,<fqn>
是bean的完整合格的名字。如果注解沒有提供任何前綴,則只用bean的完整合格的名字。
在上面的例子中bean名字是
connection-com.example.ConnectionProperties
,假設(shè)ConnectionProperties
在com.example
包中。
即使上面的配置會為ConnectionProperties
創(chuàng)建一個正規(guī)的bean,我們建議@ConfigurationProperties
只處理環(huán)境,特別是不從上下文中注入其它的beans。已經(jīng)說過,為了任何帶有@ConfigurationProperties
注解的bean可以根據(jù)Environment
屬性進行配置,@EnableConfigurationProperties
注解也自動應(yīng)用到你的工程中。確保ConnectionProperties
已經(jīng)是一個bean,你可以簡寫上面的MyConfiguration
:
@Component
@ConfigurationProperties(prefix="connection")
public class ConnectionProperties {
// ... getters and setters
}
這種風(fēng)格的配置在SpringApplication
的外部化YAML配置中工作的尤其好:
# application.yml
connection:
username: admin
remoteAddress: 192.168.1.1
# additional configuration as required
為了同@ConfigurationProperties
beans一起工作,你可以像任何其它bean一樣以相同的方式注入它們:
@Service
public class MyService {
private final ConnectionProperties connection;
@Autowired
public MyService(ConnectionProperties connection) {
this.connection = connection;
}
//...
@PostConstruct
public void openConnection() {
Server server = new Server();
this.connection.configure(server);
}
}
使用
@ConfigurationProperties
也允許你生成IDEs可以使用的元數(shù)據(jù)文件。更多細節(jié)請看附錄B,配置元數(shù)據(jù)附錄。
24.7.1 第三方配置
也可以使用@ConfigurationProperties
來注解一個類,你也可以在公有的@Bean
方法上使用它。當你想綁定屬性到你控制之外的第三方組件上時尤其有用。
@ConfigurationProperties(prefix = "foo")
@Bean
public FooComponent fooComponent() {
...
}
任何定義的帶有foo
前綴的屬性都將以類似于上面的ConnectionProperties
例子中的方式映射到FooComponent
bean中。
24.7.2 松散綁定
Spring Boot使用一些松散的規(guī)則將Environment
屬性綁定到@ConfigurationProperties
beans上,因此不需要在Environment
屬性名和bean屬性名之間進行確切的匹配。常見的有用例子包括破折號分隔(例如,context-path綁定到contextPath),大小寫(例如PORT
綁定到port
,)環(huán)境屬性。
例如,給定下面的@ConfigurationProperties
類:
@ConfigurationProperties(prefix="person")
public class OwnerProperties {
private String firstName;
public String getFirstName() {
return this.firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
}
下面的屬性名都可以使用:
表24.1. 松散綁定
Property | Note |
---|---|
person.firstName | 標準的駝峰寫法 |
person.first-name | 破折號注解,建議在.properties 和.yml 文件中使用 |
person.first_name | 下劃線注解,.properties 和.yml 文件中的可替代寫法 |
PERSON_FIRST_NAME | 大寫形式。當使用系統(tǒng)變量時推薦 |
24.7.3 屬性轉(zhuǎn)換
當Spring綁定屬性到@ConfigurationProperties
beans時,它將試圖將外部的應(yīng)用屬性強制轉(zhuǎn)換成正確的類型。如果你需要定制類型轉(zhuǎn)換你可以提供一個ConversionService
bean(bean id為conversionService
),或定制屬性編輯器(通過CustomEditorConfigurer
bean),或定制Converters
(帶有@ConfigurationPropertiesBinding
注解的bean定義)。
bean要求在應(yīng)用生命周期中的早期,要確保限制
ConversionService
使用的依賴。通常,任何你要求的依賴可能在創(chuàng)建時不是完整初始化的。如果你定制的ConversionService
不要求配置關(guān)鍵字強制轉(zhuǎn)換,你可能想重新命名你定制的ConversionService
,并且只依賴滿足@ConfigurationPropertiesBindings
的定制轉(zhuǎn)換器。
24.7.4 @ConfigurationProperties驗證
Spring Boot會試圖驗證外部化配置,默認使用JSR-303(如果它在classpath中)。你可以簡單的添加JSR-303 javax.validation
約束注解到你的@ConfigurationProperties
類中:
@ConfigurationProperties(prefix="connection")
public class ConnectionProperties {
@NotNull
private InetAddress remoteAddress;
// ... getters and setters
}
為了驗證嵌入的屬性值,你必須注解相關(guān)的字段作為@Valid
來觸發(fā)它的校驗。例如,在上面的ConnectionProperties
例子上構(gòu)建:
@ConfigurationProperties(prefix="connection")
public class ConnectionProperties {
@NotNull
@Valid
private RemoteAddress remoteAddress;
// ... getters and setters
public static class RemoteAddress {
@NotEmpty
public String hostname;
// ... getters and setters
}
}
通過創(chuàng)建一個稱為configurationPropertiesValidator
的bean定義,你也可以添加定制的Spring Validator
。@Bean
方法應(yīng)該聲明靜態(tài)的。配置屬性驗證器在應(yīng)用生命周期的早期創(chuàng)建,聲明@Bean
方法為靜態(tài)方法,允許不必實例化@Configuration
類就創(chuàng)建bean。這避免了任何早期實例化可能引起的問題。這兒有一個屬性驗證的例子因此你可以看一下怎樣設(shè)置它
spring-boot-actuator
模塊包含一個端點,這個端點將公開所有的@ConfigurationProperties
beans。簡單的將你的web瀏覽器指向/configprops
或用等價的JMX端點。更多細節(jié)請看產(chǎn)品級功能
24.7.5 @ConfigurationProperties和@Value
@Value
是一種核心的容器功能,它不能作為類型安全配置屬性提供同樣的功能。下面的表中總結(jié)了@ConfigurationProperties
和@Value
支持的功能:
功能 | @ConfigurationProperties |
@Value |
---|---|---|
松散綁定 | Yes | No |
元數(shù)據(jù)支持 | Yes | No |
SpEL評估 | No | Yes |
如果你為自己的組件定義了一些配置關(guān)鍵字,我們建議你將它們分組到帶有@ConfigurationProperties
注解的POJO中。也請注意@Value
不支持松散綁定,如果你需要用環(huán)境變量提供值,它不是一個好的選擇。
最后,雖然你可以在@Value
中寫表達式,但這種表達式不能從應(yīng)用屬性文件中處理。