《【源碼解析】憑什麼?spring boot 一個 jar 就能開發 web 項目》 中有讀者反應:html
部署後運維很不方便,比較修改一個 IP 配置,須要從新打包。java
這一點我是深有體會,17 年自學,並很大膽的直接在生產環境用的時候,我都是讓產品經理(此時他充當咱們的運維,嘿嘿)用壓縮軟件打開 jar,而後複製出配置,修改完以後再替換回去。爲何我這麼大膽,由於當時才入行一年,並且以爲有架構師兜底,我就奔放了。你是不知道,當時負責這個項目的開發(c#開發)一開始不想用 SpringBoot 的。git
不過現在看到這個問題,我有點震驚,都 9102 年了,居然還擔憂這樣的問題。我想說,哥們,這真的不是事兒。SpringBoot 早就提供了方法來解決這個問題。web
SpringBoot 有不少生產特性,能夠在生產環境中使用時更加方便。其中外部化配置基本都會用到。spring
Spring Boot 容許外部化配置,以便相同的應用在不一樣的環境中工做。
屬性值能夠在 Spring 環境中使用 @Value 或 @ConfigurationProperties 使用。json
這次參考的版本是 SpringBoot-2.2.0.RELEASE
c#
外部化配置的優先級順序以下:數組
$HOME/.config/spring-boot
@TestPropertySource
properties
屬性:在 @SpringBootTest 和 用來測試特定片斷的測試註解SPRING_APPLICATION_JSON
中的屬性:內嵌在環境變量或系統屬性中的 JSONServletConfig
初始化參數ServletContext
初始化參數java:comp/env
中的 JNDI 屬性System.getProperties()
RandomValuePropertySource
):random.*
屬性application-{profile}.properties
application-{profile}.properties
application.properties
application.properties
@PropertySource
註解:用於 @Configuration
類上SpringApplication.setDefaultProperties
指定注意:以上用 properties
文件的地方也可用 yml
文件安全
my.uuid=${random.uuid}
java -jar -Ddemo=vm demo.jar --demo=arg
System#getProperty
獲取Environment#getProperty
獲取,若經過此方法獲取不到,會獲取 vm 同名參數public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(ArgApplication.class, args); LOGGER.info("----------------"); /* 打印 arg 參數 */ Arrays.stream(args) .forEach( arg -> { LOGGER.info("arg:{}", arg); }); /* 命令行傳參 demo */ LOGGER.info("System#getProperty:{}", System.getProperty("demo")); LOGGER.info("Environment#getProperty:{}", context.getEnvironment().getProperty("demo")); }
輸入命令springboot
java -jar -Ddemo=vm arg-0.0.1-SNAPSHOT.jar aaa bbb ccc --demo=arg
效果以下:
---------------- arg:aaa arg:bbb arg:ccc arg:--demo=arg System#getProperty:vm Environment#getProperty:arg
而若是執行命令是:
java -jar -Ddemo=vm arg-0.0.1-SNAPSHOT.jar aaa bbb ccc
結果以下:
arg:aaa arg:bbb arg:ccc System#getProperty:vm Environment#getProperty:vm
若是執行命令是:
java -jar arg-0.0.1-SNAPSHOT.jar aaa bbb ccc --demo=arg
結果以下:
arg:aaa arg:bbb arg:ccc arg:--demo=arg System#getProperty:null Environment#getProperty:arg
優先級:
若是定義了 spring.config.location
,如:classpath:/custom-config/,file:./customr-config/
,優先級以下:
若是指定了 spring.config.additional-location
,會先加載 additional 配置 如:spring.config.additional-location=classpath:/custom-config/,file:./customr-config/
,優先級以下:
默認的 profile 是 default
,當沒有指定spring.profiles.active
屬性時,默認會加載application-default.properties
文件。指定 profiles 文件的加載順序與上述不指定 profiles 文件的加載一致。指定 profile 文件的屬性始終覆蓋未指定文件的屬性
。如:spring.profiles.active=dev
,則 application-dev.properties
文件內的屬性會覆蓋 application.properties
內的同名屬性。
注意:若是在
spring.config.location
屬性中指定了文件
,則此文件對應的特定 profiles 類文件不起做用。若是想要起做用,在spring.config.location
中使用文件夾
。
配置文件中能夠引用以前定義的值,以下:
app.name=MyApp app.description=${app.name} is a Spring Boot application.
能夠用此特性建立一些已存在的 Spring Boot 配置的較短、易於使用的變量。以下:
# nacos 配置示例 spring: cloud: nacos: config: server-addr: 127.0.0.1:8848 namespace: d9a39d78-xxxxxxxx-ea4f282e9d99 discovery: server-addr: 127.0.0.1:8848 namespace: d9a39d78-xxxxxxxx-ea4f282e9d99 # Discovery 配置示例 nacos: plugin: namespace: d9a39d78-xxxxxxxx-ea4f282e9d99
可改成以下配置
spring: cloud: nacos: config: server-addr: ${app.server-addr} namespace: ${app.namespace} discovery: server-addr: ${app.server-addr} namespace: ${app.namespace} # Discovery 配置示例 nacos: plugin: namespace: ${app.namespace} app: server-addr: 127.0.0.1:8848 namespace: d9a39d78-xxxxxxxx-ea4f282e9d99
而後在命令行能夠直接經過 -Dapp.namespace
或 --app.namespace
來傳參,會方便不少。特別是在多個地方用到同一個屬性的時候。
Spring Boot 不支持屬性加密,但提供鉤子節點修改配置屬性。EnvironmentPostProcessor
接口容許在應用啓動前操做 Environment
。
yaml 文件使用的時候很是直觀、方便。並且在 Spring Boot 中作了處理,獲取 yaml 和 properties 文件中的屬性基本是同樣的操做。
經過 spring.profiles
指示什麼時候使用對應的配置,使用 ---
進行配置分隔
# application.yml server: address: 192.168.1.100 --- spring: profiles: development server: address: 127.0.0.1 --- spring: profiles: production & eu-central server: address: 192.168.1.120
用 @PropertySource
不能加載 yaml 文件,這種狀況下只能使用 properties 文件。
在特定 profile 的 yaml 文件中使用多 profile 配置,會有意料以外的狀況:
# application-dev.yml server: port: 8000 --- spring: profiles: "!test" security: user: password: "secret"
當運行時指定 --spring.profiles.active=dev
,啓用 dev profile,其它的 profile 會忽略。也就是此例中 spring.security.user.password
屬性會失效。
所以,不要在指定 profile 的 yaml 文件中使用多種 profile 配置。
經過 @ConfigurationProperties
註解將屬性(properties、yml 文件、環境變量等)綁定到類對象中。與自動配置類相似。
@ConfigurationProperties("acme") public class AcmeProperties{ private boolean enabled; private InetAddress remoteAddress; private final Security security = new Security(); // getter and setter public static class Security{ private String username; private String password; private List<String> roles = new ArrayList<>(Collections.singleton("USER")); // getter and setter } }
這種安排依賴於默認的無參構造器,getter 和 setter 一般是必需的,由於綁定就像 Spring MVC 同樣是經過標準的 Java Beans 屬性描述符進行的。在下列狀況下,可省略 setter:
- Maps:只要被初始化後,getter 必須而 setter 沒必要須,binder 能夠對它們進行修改
- Collections 和 數組:能夠經過索引或逗號分隔的值來設定屬性。後者必須有 setter 方法。建議對於這種狀況一直加上 setter。若是初始化了一個 Collection,確保它不是不可變類型。
- 若是初始化了嵌套的 POJO 屬性(如上例中的 Security),setter 不是必須的。若是須要 binder 經過其默認構造器動態建立實例,則須要 setter
注意:若是使用 Lombok 生成 getter 和 setter,確保不會生成任何特定的構造器,否則容器會自動使用它來實例化對象。
最後,只有標準 Java Bean 屬性能夠這樣綁定屬性,靜態屬性不支持。
上述示例能夠改爲以下:
@ConstructorBinding @ConfigurationProperties("acme") public class AcmeProperties{ private final boolean enabled; private final InetAddress remoteAddress; private final Security security; public AcmeProperties(boolean enabled, InetAddress remoteAddress, Security security){ this.enabled = enabled; this.remoteAddress = remoteAddress; this.security = security; } // getter and setter public static class Security{ private final String username; private final String password; private final List<String> roles; public Security(String username, String password, @DefaultValue("USER") List<String> roles){ this.username = username; this.password = password; this.roles = roles; } // getter and setter } }
@ConstructorBinding
註解表示使用構造函數綁定屬性值。這意味着 binder
將指望找到一個包含待綁定參數的構造器。
@ConstructorBinding
類的嵌套成員也將經過構造函數綁定屬性值。
可使用 @DefaultValue
指定默認值,轉換服務將字符串值強轉爲缺乏屬性的目標類型。
要使用構造綁定,類必須容許使用
@EnableConfigurationProperties
或 配置屬性掃描方式。不能對由常規 Spring 機制建立的 bean 使用構造函數綁定。如:@Component Bean、經過@Bean 方法建立的 Bean 或使用@Import 加載的 Bean
若是類中有多個構造器,能夠直接將
@ConstructorBinding
註解使用在要綁定的構造器上。
@ConfigurationProperties
註解類型Spring Boot 提供了一個基礎設施來綁定這些類型並將它們自動註冊爲 bean。
若是應用程序中使用@SpringBootsApplication
,用@ConfigurationProperties
註解的類將被自動掃描並註冊爲 bean。默認狀況下,將從聲明此註解的類的包中進行掃描。若是要掃描特定的包,能夠對 ·@SpringBootsApplication
註解的類顯式使用@ConfigurationPropertiescan
註解,以下例所示:
@SpringBootApplication @ConfigurationPropertiesScan({ "com.example.app", "org.acme.another" }) public class MyApplication { }
有時,用
@ConfigurationProperties
註釋的類可能不適合掃描,例如,若是正在開發本身的自動配置,在這些狀況下,能夠在任何@Configuration 類上指定要處理的類型列表,以下例所示:
@Configuration(proxyBeanMethods = false) @EnableConfigurationProperties(AcmeProperties.class) public class MyConfiguration { }
注意:當使用配置屬性掃描或經過@EnableConfigurationProperties 註冊@ConfigurationProperties bean 時,bean 有一個常規名稱:
<prefix>-<fqn>
,其中<prefix>
是@ConfigurationProperties
註解中指定的環境 key 前綴,<fqn>
是 bean 的徹底限定名。若是註解沒有提供任何前綴,則只使用 bean 的徹底限定名。
上例中 bean name 是acme-com.example.AcmeProperties
。
@ConfigurationProperties
註解類型這種類型的配置在 SpringApplication 外部 YAML 配置中特別適用,以下例所示:
# application.yml acme: remote-address: 192.168.1.1 security: username: admin roles: - USER - ADMIN
@ConfigurationProperties
bean 能夠像其它 bean 同樣注入使用。以下:
@Service public class MyService{ private final AcmeProperties properties; @Autowired public MyService(AcmeProperties properties){ this.properties = properties; } // ... }
使用
@ConfigurationProperties
還能夠生成元數據文件,IDE 可使用這些文件提供代碼自動完成功能。
除了能夠在 類
上使用 @ConfigurationProperties
註解,還能夠在 public @Bean 方法
上使用它。若是要將屬性綁定到不在控制範圍內的第三方組件,那麼這樣作特別有用。
要從 Environment
屬性配置 bean,將 @ConfigurationProperties
添加到其 bean 註冊中,以下例所示:
@ConfigurationProperties(prefix = "another") @Bean public AnotherComponent anotherComponent() { //... }
用
another
前綴定義的任何 JavaBean 屬性都映射到AnotherComponent
bean 上,映射方式相似於前面的 AcmeProperties 示例。
Spring Boot 使用一些寬鬆的規則將
Environment
屬性綁定到@ConfigurationProperties
bean,所以環境屬性名和 bean 屬性名之間不須要徹底匹配。常見的包括短劃線分隔的環境屬性(例如,context-path
綁定到contextPath
)和大寫的環境屬性(例如,PORT
綁定到port
)。
@ConfigurationProperties(prefix="acme.my-project.person") public class OwnerProperties { private String firstName; public String getFirstName() { return this.firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } }
對於以上 Java Bean,可使用如下屬性
注意:註解的前綴值必須是短橫線 (小寫,用-分隔,如:acme.my-project.person)。
建議:若是可能的話,將屬性存儲爲小寫的短橫線格式,例如:my.property-name=acme。
在綁定到 Map
屬性時,若是 key
包含除小寫字母-數字字符或 -
以外的任何內容,則須要使用括號符號,以便保留原始值。若是 key
沒有被[]
包圍,則刪除任何不是字母數字或 -
的字符。
acme: map: "[/key1]": value1 "[/key2]": value2 /key3: value3
上面的屬性將綁定到 Map
的這些 key
中:/key1
、/key2
、key3
當在多個位置配置 list 時,經過替換(而非添加)整個 list 來覆蓋。
@ConfigurationProperties("acme") public class AcmeProperties { private final List<MyPojo> list = new ArrayList<>(); public List<MyPojo> getList() { return this.list; } }
acme: list: - name: my name description: my description --- spring: profiles: dev acme: list: - name: my another name
當啓用 dev
配置時,AcmeProperties.list
中值包含一個 MyPojo
對象(name 爲my another name
),不是添加操做,而是覆蓋操做。
當一個
List
在多個 profiles 中定義時,最高優先級的被使用。
對於 Map
屬性,能夠使用從多個屬性源獲取屬性值進行綁定。可是,對於多個源中的同一屬性,將使用優先級最高的屬性。
@ConfigurationProperties("acme") public class AcmeProperties { private final Map<String, MyPojo> map = new HashMap<>(); public Map<String, MyPojo> getMap() { return this.map; } }
acme: map: key1: name: my name 1 description: my description 1 --- spring: profiles: dev acme: map: key1: name: dev name 1 key2: name: dev name 2 description: dev description 2
當 dev 配置啓用時,AcmeProperties.map
中包含兩個鍵值對。key1
中 pojo name 爲 dev name 1,description 爲 my description 1;key2
中 pojo name 爲 dev name 2,description 爲 dev description 2。
不一樣屬性源的配置進行了合併
以上合併規則適用於全部的屬性源
Spring Boot 試圖在綁定到
@ConfigurationProperties
bean 時將外部應用程序屬性強轉爲正確的類型。若是須要自定義類型轉換,能夠提供ConversionService
bean(帶有名爲ConversionService
的 bean)或自定義屬性編輯器(經過CustomEditorConfigurer
bean)或自定義Converters
(使用 bean 定義註解@ConfigurationPropertiesBinding
)。
注意:因爲此 bean 在應用程序生命週期的早期被請求,請確保限制
ConversionService
正在使用的依賴項。一般,須要的任何依賴項在建立時均可能未徹底初始化。若是自定義的ConversionService
不須要配置 keys 強轉,而且僅依賴於使用@ConfigurationPropertiesBinding
限定的自定義轉換器,則可能須要將它重命名。
SpringBoot 對錶示持續時間有專門的支持。若是暴露 java.time.Duration
屬性,則能夠用如下格式:
long
表示(除非指定了 @DurationUnit
,不然使用毫秒做爲默認單位)java.time.Duration
使用的標準 ISO-8601 格式@ConfigurationProperties("app.system") public class AppSystemProperties { @DurationUnit(ChronoUnit.SECONDS) private Duration sessionTimeout = Duration.ofSeconds(30); private Duration readTimeout = Duration.ofMillis(1000); public Duration getSessionTimeout() { return this.sessionTimeout; } public void setSessionTimeout(Duration sessionTimeout) { this.sessionTimeout = sessionTimeout; } public Duration getReadTimeout() { return this.readTimeout; } public void setReadTimeout(Duration readTimeout) { this.readTimeout = readTimeout; } }
要指定 30 秒的 sessionTimeout,30、PT30S 和 30s 都是等效的。500ms 的 readTimeout 能夠用如下任何形式指定:500、PT0.5S 和 500ms。
也可使用如下任何支持的單位:
ns
:納秒us
:微妙ms
:毫秒s
:秒m
:分h
:時d
:天默認的單位是毫秒,可使用
@DurationUnit
指定
Spring 框架有一個 DataSize
類型,以字節表示大小。若是暴露一個 DataSize
屬性,則能夠用如下格式:
long
表示(除非指定了 @DataSizeUnit
,不然使用字節做爲默認單位)java.time.Duration
使用的標準 ISO-8601 格式10MB
表示 10 兆字節)。@ConfigurationProperties("app.io") public class AppIoProperties { @DataSizeUnit(DataUnit.MEGABYTES) private DataSize bufferSize = DataSize.ofMegabytes(2); private DataSize sizeThreshold = DataSize.ofBytes(512); public DataSize getBufferSize() { return this.bufferSize; } public void setBufferSize(DataSize bufferSize) { this.bufferSize = bufferSize; } public DataSize getSizeThreshold() { return this.sizeThreshold; } public void setSizeThreshold(DataSize sizeThreshold) { this.sizeThreshold = sizeThreshold; } }
要指定 10 兆字節的 bufferSize
,10
和 10MB
是等效的。256 字節的 sizeThreshold
能夠指定爲 256
或 256B
。
也可使用如下任何支持的單位:
B
:字節
KB
:千字節
MB
:兆字節
GB
:千兆字節
TB
:兆兆字節
默認的單位是字節,可使用
@DataSizeUnit
指定
每當對 @ConfigurationProperties
類使用 Spring 的@Validated
註解時,Spring Boot 就會驗證它們。能夠直接在配置類上使用 JSR-303 javax.validation
約束註解。必須確保類路徑上有一個兼容的 JSR-303 實現(如:hibernate-validator),而後將約束註解添加到字段中。
@ConfigurationProperties(prefix="acme") @Validated public class AcmeProperties { @NotNull private InetAddress remoteAddress; // ... getters and setters }
注意:還能夠經過註解@Bean 方法來觸發驗證,該方法使用@Validated 建立配置屬性。
儘管嵌套屬性在綁定時也將被驗證,但最好對關聯字段使用
@Valid
。這確保即便找不到嵌套屬性,也會觸發驗證。
@ConfigurationProperties(prefix="acme") @Validated public class AcmeProperties { @NotNull private InetAddress remoteAddress; @Valid private final Security security = new Security(); // ... getters and setters public static class Security { @NotEmpty public String username; // ... getters and setters } }
還能夠經過建立
ConfigurationPropertiesValidator
bean 來添加自定義 SpringValidator
。@Bean
方法應該聲明爲static
。配置屬性驗證器是在應用程序生命週期的早期建立的,將@Bean 方法聲明爲 static 能夠建立 Bean,而無需實例化@configuration 類。這樣作能夠避免任何可能由早期實例化引發的問題。
注意:
spring-boot-actuator
模塊包含一個端點,該端點暴露全部@ConfigurationProperties
bean。訪問/actuator/configprops
可得到相關信息。
@Value
註解是一個核心容器特性,它不提供與 @ConfigurationProperties
相同的特性。
若是須要爲組件定義了一組配置鍵,建議將它們配置到一個
@ConfigurationProperties
註解的 POJO 中。因爲@Value
不支持鬆綁定,若是須要使用環境變量提供值,則它不是一個好的選項。
雖然能夠在@Value
中編寫SpEL
表達式,但此類表達式不會從 properties 文件中處理。
若是項目比較大的話,分紅了好幾個 SpringBoot 工程,可使用某些 SpringCloud 組件,好比:配置中心。配置中心支持一個地方管理全部的配置,有些還能夠支持修改配置實時生效而不用重啓應用,真的是很棒棒呢。推薦使用 nacos
。若是項目比較小,你用 git
或者指定文件夾
來做爲配置存放的地方也能夠。
怎麼樣?有了這些用法的支持,你還會以爲 Springboot 打成一個 jar
會在部署的時候很不方便嗎?
官方文檔
公衆號:逸飛兮(專一於 Java 領域知識的深刻學習,從源碼到原理,系統有序的學習)