在前面的課程中,咱們給你們分享過SpringBoot精妙的啓動配置,主要闡述的是spring的IoC容器在SpringBoot中的加載過程,並與傳統項目中Spring的IoC容器加載過程進行了一個對比.咱們在開發的過程當中,除了IoC容器的配置以外,固然還有許多其餘的配置,諸如數據庫的連接信息,端口,以及項目的內部使用的一些個性化信息等.那SpringBoot是如何管理這些配置呢?我今天呢,就從如下這三個方面來給你們分享一下SpringBoot是如何管理配置信息的.html
配置文件和屬性獲取前端
配置文件的名字、目錄和優先級
java
傳統的properties與YAML
mysql
1.1 傳統配置文件的值獲取與SpringBoot中的值獲取web
在傳統的項目裏,咱們的配置信息通常都寫在配置文件中,而後關於spring須要的信息,咱們就在spring的xml文件裏引用,大略以下所示:spring
classpath下的config裏建立一個jdbc.propertiessql
jdbc.driverClassName=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://127.0.0.1:3306/testdb?serverTimezone=UTC&characterEncoding=utf-8&useSSL=false&allowPublicKeyRetrieval=true jdbc.username=develop jdbc.password=&dT$BvYdOlH4*m9G
而後在咱們的application.xml裏引入咱們須要的這個數據源:數據庫
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <context:property-placeholder location="classpath:config/jdbc.properties" /> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${jdbc.driverClassName}" /> <property name="url" value="${jdbc.url}" /> <property name="username" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> </bean> ... ...其餘配置 </beans>
固然除了spring的數據源信息以外,咱們每每也會有一些其餘的信息,好比項目返回給前端的一些報錯信息等,此時咱們一般的作法是使用java的原生方法去加載。如如下所示:apache
在classpath的config下有個webreslt.properties緩存
0=SUCCESS 100000=參數有誤 100001=AppKey不能爲空 100002=Signature不能爲空 100003=參數列表(paramMap)不能爲空 100004=secret不能包含在參數列表中 100005=參數簽名不合法 100006=手機號不能爲空 100007=驗證碼不能爲空 100008=郵件內容不能爲空 100009=收件人不能爲空 100010=郵件主題不能爲空 200000=應用無權限 200001=應用未註冊 300000=Api異常 300001=短信發送失敗 300002=短信驗證失敗 300003=郵件發送失敗 300004=短信發送超過最大條數限制
獲取值的方法以下:
package com.ailu.paas.common.utils; import org.apache.commons.lang3.StringUtils; import java.util.Locale; import java.util.ResourceBundle; public class PropertyFileReader { public static String getItem(String key) { return getItem(key, ""); } public static String getItem(String key, String defaultValue) { ResourceBundle rb = ResourceBundle.getBundle("config/webresult"); String value = ""; try { value = new String(rb.getString(key).getBytes("ISO-8859-1"), "UTF-8"); } catch (Exception e) { e.printStackTrace(); } if (StringUtils.isEmpty(value)) { value = defaultValue; } return value.trim(); } }
而後在其餘的地方,咱們就能夠直接使用如下這樣的方式去獲取屬性文件中的值了.
String value=PropertyFileReader.getItem(key);
而在SpringBoot中呢,咱們則可使用@Component+@Value這兩個組合,來快速的讀取配置文件中的值,
仍然是在classpath下,咱們在配置文件裏寫上以下配置:
name="Lianmengtu"
而後咱們建立一個java類,並加上@Component和@Value,以下所示:
package top.lianmengtu.testprofile.common; import lombok.Getter; import lombok.Setter; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @Component @Getter @Setter public class TestProperty { @Value("${name}") private String name; }
其中@Component是爲了把TestProperty做爲組件放入到Spring的IoC容器中,而@Value("${name}")則是爲了從配置文件中取值,${name}中的name便是配置文件中的key.而後咱們就能夠在其餘地方經過注入直接使用了:
@Autowired private TestProperty testProperty; public void test(){ System.out.println(testProperty.getName()); }
1.2 隨機值的綁定
在某些場景下,咱們可能須要在項目的配置中添加一些隨機值,而且這些值在咱們項目啓動後就自動的初始化,而SpringBoot就考慮到了這種狀況,因而給咱們準備了一些工具,方便咱們的使用.使用狀況以下:
# 隨機字符串 secret=${random.value} #隨機數 setup=${random.int} #0-10之間的隨機數 range-int=${random.int[0,10]} #生成uuid uuid=${random.uuid}
獲取方式和其餘的屬性相同,以下所示:
package top.lianmengtu.testprofile.common; import lombok.Getter; import lombok.Setter; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @Component @Getter @Setter public class TestProperty { @Value("${secret}") private String secret; @Value("${setup}") private Integer setup; @Value("${range-int}") private Integer rangeInt; @Value("${uuid}") private String uuid; }
1.3 變量的引用與佔位符
有些時候咱們會在配置項中引用另外一項的值,固然,是以變量的形式進行引用.以下所示:
protocal=http:// domain=${protocal}ask.lianmengtu.top
這裏咱們看到,在springBoot中使用的變量佔位符是${key}.這時候就有一個問題,由於咱們如今大多數的開發環境都是maven,咱們都知道maven也是支持變量佔位的,咱們能夠在打包不一樣環境的時候能夠激活不一樣的profile,而後對變量值進行替換,而咱們往常在使用Maven的時候,用的變量佔位符正好是${key},那此時,咱們該怎麼辦呢?SpringBoot是否是不支持Maven的變量佔位呢?咱們必需要二選其一嗎?
固然不是.SpringBoot也考慮到了這個問題,所以給maven佔位提供了另一個符號即@key@,以下所示:
protocal=http:// domain=${protocal}ask.lianmengtu.top username=@username@ password=@password@
而後咱們就能夠在咱們的pom裏這麼寫了,而當咱們激活某一個profile的時候,相應的maven變量就會被替換了
<profiles> <profile> <id>dev</id> <properties> <username>dev</username> <password>dev123</password> </properties> </profile> <profile> <id>test</id> <properties> <username>test</username> <password>test123</password> </properties> </profile> </profiles>
SpringBoot是除了支持properties這種方式以外,它也支持YAML這種方式,而且由於yml結構清晰,又能夠繼承,因此使用yml這種方式的人也愈來愈多了.而我就是這麼對yml路轉粉的.
2.1 yml結構化
yml的第一個好處是結構化,這與properties是明顯的差異.這裏放上兩份一樣的配置文件來個視覺對比,首先是properties
environments.dev.url= environments.dev.name=Developer Setup environments.prod.url= environments.prod.name=My Cool App my.servers[0]=dev.example.com my.servers[1]=another.example.com
對比yml
environments: dev: url: http://dev.example.com name: Developer Setup prod: url: http://another.example.com name: My Cool App my: servers: - dev.example.com - another.example.com
這裏只是舉個例子,因此可能這兩三行你們看起來沒什麼感受,但要知道在實際的項目中,咱們的配置可能包含各類各樣的信息,數據庫的,緩存的,第三方平臺的等等,那時候,若是咱們還用properties,那麼看着將會很是頭大.
2.2 yml的繼承
在咱們使用配置文件的時候,尤爲是分環境使用的時候,經常會碰到這麼一個問題: 大多數的項目配置都同樣,只有少數的不同,此時,若是是使用properties,那麼咱們就只能每一個文件各寫一份,而在yml裏,咱們就不用,咱們只須要將通用的那部分寫到application-common.yml裏,而後少許不一樣的,咱們在分環境進行描述,application-dev.yml,application-prod.yml裏,這樣咱們只須要激活一份文件,剩下的就會自動的進行繼承和使用了,具體方式以下:
application.yml
app: name: "lianmengtu" country: "China" username: "melon" password: "melon123"
application-dev.yml:
app: username: "jacobdev" password: "dev123456"
application-prod.yml
app: username: "LMTprod" password: "prod456"
這樣當咱們在啓動時,激活不一樣的配置時,username和password會不一樣,但name和country則是從默認的yml中繼承過來的.
2.3 指定配置文件的名字和地址
剛剛咱們提到過多種環境,配置文件之因此要區分環境,就是由於有些信息是須要保密的,沒法公開.而SpringBoot則容許從外部,經過命令行的形式,對配置文件進行指定,固然也能夠指定變量.
2.3.1 指定配置文件的名字
咱們可使用--spring.config.name=xxx 這樣的參數形式指定配置文件的名字:
$ java -jar myproject.jar --spring.config.name=myproject
2.3.2 配置指定目錄下的配置文件
咱們可使用--spring.config.location=xxxxx這樣的參數形式來配置指定目錄下的配置文件,以下文則指定了classpath下的config目錄下的test.yml
java -jar myproject.jar --spring.config.location=classpath:/config/test.yml
固然從1.x轉過來的人可能更喜歡指定目錄,這裏要特別說明一下,若是--spring.config.location是以目錄結尾的,則必須加/ 以下所示:
java -jar myproject.jar --spring.config.location=classpath:/config/
2.3.3 配置的優先級
在咱們沒有明確指定文件名字的時候,springBoot會按着如下順序進行考慮
當前目錄下的config目錄裏的application.properties
當前目錄下的application.properties
classpath下的config目錄下的application.properties
classpath下的application.properties
固然除了這些以外,命令行也是能夠傳參數的,而且命令行參數的優先級是最高的.
使用@Value()來進行屬性注入有些時候會顯得比較笨重,尤爲是使用多個配置或者咱們的配置項是呈垂直結構化的數據時,更是這樣.SpringBoot提供了另一種方法來處理這類比較複雜的數據.這就是咱們要說的@ConfigurationProperties
.
首先咱們有這樣一個配置文件:
app: name: "lianmengtu" enabled: false security: username: "jacob" password: "jacob123" roles: - USER - ADMIN
咱們看到在這個配置文件裏,app下有name屬性,有enabled屬性,其中較爲特殊的是security,由於他還包含了些其餘的屬性,包括username,包括password,還有一個類型爲String的roles集合,那麼此時,咱們能夠對應的寫成下面這個屬性類
package top.lianmengtu.testprofile.common; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.Collections; import java.util.List; @ConfigurationProperties("app") public class ComplexProperty { private String name; private boolean enabled; private final Security security=new Security(); public String getName() { return name; } public void setName(String name) { this.name = name; } public boolean isEnabled() { return enabled; } public void setEnabled(boolean enabled) { this.enabled = enabled; } public Security getSecurity() { return security; } public static class Security { private String username; private String password; private List<String> roles =new ArrayList<>(Collections.singleton("USER")); public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public List<String> getRoles() { return roles; } public void setRoles(List<String> roles) { this.roles = roles; } } }
以後,咱們能夠在咱們的service層引用它:
package top.lianmengtu.testprofile.service.impl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import top.lianmengtu.testprofile.common.ComplexProperty; import java.util.List; @Service public class PropertyService { private final ComplexProperty complexProperty; @Autowired public PropertyService(ComplexProperty complexProperty){ this.complexProperty=complexProperty; } public String name(){ return complexProperty.getName(); } public boolean enabled(){ return complexProperty.isEnabled(); } public String userName(){ return complexProperty.getSecurity().getUsername(); } public String password(){ return complexProperty.getSecurity().getPassword(); } public String roles(){ StringBuffer roles=new StringBuffer(); List<String> roleArray=complexProperty.getSecurity().getRoles(); roleArray.forEach(role->{ roles.append(role).append("----"); }); return roles.toString(); } }
這裏的構造函數注入,咱們也能夠換成相應的@Autowried注入.爲了使這個配置生效,咱們須要在Application上加上@EnableConfigurationProperties(ComplexProperty.class)
package top.lianmengtu.testprofile; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.properties.EnableConfigurationProperties; import top.lianmengtu.testprofile.common.ComplexProperty; @SpringBootApplication @EnableConfigurationProperties(ComplexProperty.class) public class Application { public static void main(String[] args) { SpringApplication.run(Application.class,args); } }
剛剛舉的那種類型能夠說是比較複雜的一個類型了,除了那種內嵌的形式以外,若是說咱們只想獲得內嵌類裏面的屬性或者說只有內嵌類裏面有屬性,則咱們能夠寫成如下這種形式,其餘的地方都不用變.這種形式咱們稱之爲relaxed binding
@ConfigurationProperties("app.security") public class ComplexProperty { private String username; private String password; private List<String> roles; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public List<String> getRoles() { return roles; } public void setRoles(List<String> roles) { this.roles = roles; } }
而且也支持map類型,配置文件以下所示,其中key能夠有兩種指定方式,一種是"[/key]",一種是/key:
app: name: "lianmengtu" enabled: false security: username: "jacob" password: "jacob123" roles: - USER - ADMIN work: "[/position]": "CEO" "[/company]": "bat" /address: "BeiJing"
而咱們的配置類則以下所示:
package top.lianmengtu.testprofile.common; import org.springframework.boot.context.properties.ConfigurationProperties; import java.util.List; import java.util.Map; @ConfigurationProperties("app.security") public class ComplexProperty { private String username; private String password; private List<String> roles; private Map<String,String> work; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public List<String> getRoles() { return roles; } public void setRoles(List<String> roles) { this.roles = roles; } public Map<String, String> getWork() { return work; } public void setWork(Map<String, String> work) { this.work = work; } }
這兩種方式都是Ok的,
今天的內容,其實到這裏就已經結束了,在使用配置文件的過程當中,咱們還碰到了一些問題,首先,咱們在使用這種配置方式,不管是@Component+@Value仍是後來的@ConfigurationProperties這兩種方式,他都是在進入spring的時候進行初始化的,這也就意味着,若是咱們沒有從Spring的容器中去取咱們的屬性容器的話,那麼咱們的屬性值是沒有辦法注入的,這一點但願你們可以注意,其次,今天只是講了主要的幾種方式,還有一些像複雜類型的屬性合併,以及屬性驗證,這些但願你們能夠研究一下,若是有不明白的,歡迎你們在論壇上提出來,咱們能夠一塊兒探討.如下是@ConfigurationProperties和@Value的一些對比:
Feature | @ConfigurationProperties |
@Value |
---|---|---|
Yes |
No |
|
Yes |
No |
|
|
No |
Yes |