淺談SpringBoot加載配置文件的實(shí)現(xiàn)方式,通俗易懂!
一、簡介
在實(shí)際的項(xiàng)目開發(fā)過程中,我們經(jīng)常需要將某些變量從代碼里面抽離出來,放在配置文件里面,以便更加統(tǒng)一、靈活的管理服務(wù)配置信息。比如,數(shù)據(jù)庫、eureka、zookeeper、redis、mq、kafka 等服務(wù)組件的連接參數(shù)配置,還有我們自定義的項(xiàng)目參數(shù)配置變量。
當(dāng)然,實(shí)際上根據(jù)當(dāng)前的業(yè)務(wù)需求,我們往往會(huì)自定義參數(shù),然后注入到代碼里面去,以便靈活使用!
今天,我們就一起來聊一聊SpringBoot加載配置文件的幾種玩法!
SpringBoot 項(xiàng)目在啟用時(shí),首先會(huì)默認(rèn)加載bootstrap.properties或者bootstrap.yml這兩個(gè)配置文件(這兩個(gè)優(yōu)先級(jí)最高);接著會(huì)加載application.properties或application.yml;如果何配置了spring.profiles這個(gè)變量,同時(shí)還會(huì)加載對(duì)應(yīng)的application-{profile}.properties或者application-{profile}.yml文件,profile為對(duì)應(yīng)的環(huán)境變量,比如dev,如果沒有配置,則會(huì)加載profile=default的配置文件。
雖然說配置項(xiàng)都寫在同一個(gè)配置文件沒有問題,但是很多時(shí)候我們?nèi)匀幌M芊珠_寫,這樣會(huì)比較清晰,比如zookeeper的配置寫在zookeeper.properties,數(shù)據(jù)庫相關(guān)的配置寫在datasource.properties等等,因此就需要設(shè)置加載外部配置文件!
具體該如何實(shí)現(xiàn)呢,我們一起來看看!
二、代碼實(shí)踐
2.1、通過@value注解實(shí)現(xiàn)參數(shù)加載
當(dāng)我們想要在某個(gè)類里面注入某個(gè)變量,通過@value注解就可以簡單實(shí)現(xiàn)參數(shù)的注入!
例如application.properties文件里,配置一個(gè)config.name的變量key,值為zhangsan;
//參數(shù)定義
config.name=zhangsan
然后在對(duì)應(yīng)的類里面,通過參數(shù)@value注入即可!
@RestController
public class HelloController {
@Value("${config.name}")
private String config;
@GetMapping("config")
public String config(){
return JSON.toJSONString(config);
}
}
使用@value注解注入配置,通常情況下有個(gè)要求就是,注解里面的變量,必須在application.properties文件里面事先定義好,否則啟動(dòng)報(bào)錯(cuò)!
當(dāng)然,如果我們不想讓它抱錯(cuò),我們可以給它一個(gè)缺省值xxx,比如:
@Value("${config.name:xxx}")
private String config;
這樣,SpringBoot 項(xiàng)目在啟用時(shí)不會(huì)報(bào)錯(cuò)!
2.2、通過@ConfigurationProperties注解實(shí)現(xiàn)參數(shù)加載
某些場景下,@value注解并不能滿足我們所有的需求,比如參數(shù)配置的數(shù)據(jù)類型是一個(gè)對(duì)象或者數(shù)組,這個(gè)時(shí)候才用@ConfigurationProperties會(huì)是一個(gè)比較好的選擇!
- 配置一個(gè)對(duì)象類型的參數(shù)
例如在application.properties文件里,當(dāng)我們想配置一個(gè)對(duì)象類型的參數(shù),我們可以這樣操作!
//參數(shù)定義
config2.name=demo_1
config2.value=demo_value_1
然后,創(chuàng)建一個(gè)配置類Config2,用于將定義的變量映射到配置類里面。
@Component
@ConfigurationProperties(prefix = "config2")
public class Config2 {
public String name;
public String value;
//...get、set
}
讀取數(shù)據(jù)的方式,也很簡單,直接注入到對(duì)應(yīng)的類里面就可以了;
@RestController
public class HelloController {
@Autowired
private Config2 config2;
@GetMapping("config2")
public String config2(){
return JSON.toJSONString(config2);
}
}
- 配置一個(gè)Map類型的參數(shù)
例如在application.properties文件里,當(dāng)我們想配置一個(gè) Map 類型的參數(shù),我們可以這樣操作!
//參數(shù)定義
config3.map1.name=demo_id_1_name
config3.map1.value=demo_id_1_value
config3.map2.name=demo_id_2_name
config3.map2.value=demo_id_2_value
然后,創(chuàng)建一個(gè)配置類Config3,用于將定義的變量映射到配置類里面。
@Component
@ConfigurationProperties(prefix = "config3")
public class Config3 {
private Map<String, String> map1 = new HashMap<>();
private Map<String, String> map2 = new HashMap<>();
//...get、set
}
讀取數(shù)據(jù)的方式,與之類似!
@RestController
public class HelloController {
@Autowired
private Config3 config3;
@GetMapping("config3")
public String config3(){
return JSON.toJSONString(config3);
}
}
- 配置一個(gè)List類型的參數(shù)
例如在application.properties文件里,當(dāng)我們想配置一個(gè) List 類型的參數(shù),我們可以這樣操作!
//參數(shù)定義
config4.userList[0].enable=maillist_1_enable
config4.userList[0].name=maillist_1_name
config4.userList[0].value=maillist_1_value
config4.userList[1].enable=maillist_2_enable
config4.userList[1].name=maillist_2_name
config4.userList[1].value=maillist_2_value
config4.userList[2].enable=maillist_3_enable
config4.userList[2].name=maillist_3_name
config4.userList[2].value=maillist_3_value
然后,創(chuàng)建一個(gè)配置類Config4,用于將定義的變量映射到配置類里面。
@Component
@ConfigurationProperties(prefix = "config4")
public class Config4 {
private List<UserEntity> userList;
public List<UserEntity> getUserList() {
return userList;
}
public void setUserList(List<UserEntity> userList) {
this.userList = userList;
}
}
public class UserEntity {
private String enable;
private String name;
private String value;
//...get、set
}
讀取數(shù)據(jù)的方式,與之類似!
@RestController
public class HelloController {
@Autowired
private Config4 config4;
@GetMapping("config4")
public String config4(){
return JSON.toJSONString(config4);
}
}
2.3、通過@PropertySource注解實(shí)現(xiàn)配置文件加載
正如我們最開始所介紹的,很多時(shí)間,我們希望將配置文件分卡寫,比如zookeeper組件對(duì)應(yīng)的服務(wù)配置文件是zookeeper.properties,redis組件對(duì)應(yīng)的服務(wù)配置文件是redis.properties等等。
這種自定義的配置文件,我們應(yīng)該如何加載到Spring容器里面呢?
其實(shí)方法也很簡單,通過@PropertySource就可以實(shí)現(xiàn)!
首先,我們?cè)趓esources資源文件夾下,創(chuàng)建兩個(gè)配置文件test.properties和bussiness.properties,內(nèi)容如下!
test.properties文件內(nèi)容:
aaa.a1=aa1123
aaa.a2=aa2123
aaa.a3=aa3123
aaa.a4=aa4123
bussiness.properties文件內(nèi)容:
bbbb.a1=bb1123
bbbb.a2=bb2123
bbbb.a3=bb3123
bbbb.a4=bb4123
在SpringBoot啟動(dòng)類上加載配置文件即可!
@SpringBootApplication
@PropertySource(value = {"test.properties","bussiness.properties"})
public class PropertyApplication {
public static void main(String[] args) {
SpringApplication.run(PropertyApplication.class, args);
}
}
讀取數(shù)據(jù)的方式,與之類似!
@RestController
public class HelloController {
@Value("${aaa.a2}")
private String a2;
@Value("${bbbb.a1}")
private String bbbbA1;
@GetMapping("a2")
public String a2(){
return JSON.toJSONString(a2);
}
@GetMapping("bbbbA1")
public String bbbbA1(){
return JSON.toJSONString(bbbbA1);
}
}
如果我們只是在業(yè)務(wù)中需要用到自定義配置文件的值,這樣引入并沒有什么問題;但是如果某些自定義的變量,在項(xiàng)目啟動(dòng)的時(shí)候需要用到,這種方式會(huì)存在一些問題,原因如下:
翻譯過來的意思就是說:
雖然在@SpringBootApplication上使用@PropertySource似乎是在環(huán)境中加載自定義資源的一種方便而簡單的方法,但我們不推薦使用它,因?yàn)镾pringBoot在刷新應(yīng)用程序上下文之前就準(zhǔn)備好了環(huán)境。使用@PropertySource定義的任何鍵都加載得太晚,無法對(duì)自動(dòng)配置產(chǎn)生任何影響。
因此,如果某些參數(shù)是啟動(dòng)項(xiàng)變量,建議將其定義在application.properties或application.yml文件里面,這樣就不會(huì)有問題!
或者,采用【自定義環(huán)境處理類】來實(shí)現(xiàn)配置文件的加載!
2.4、通過自定義環(huán)境處理類,實(shí)現(xiàn)配置文件的加載
實(shí)現(xiàn)方法也很簡單,首先,創(chuàng)建一個(gè)實(shí)現(xiàn)自EnvironmentPostProcessor接口的類,然后自行加載配置文件。
public class MyEnvironmentPostProcessor implements EnvironmentPostProcessor {
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
//自定義配置文件
String[] profiles = {
"test.properties",
"bussiness.properties",
"blog.yml"
};
//循環(huán)添加
for (String profile : profiles) {
//從classpath路徑下面查找文件
Resource resource = new ClassPathResource(profile);
//加載成PropertySource對(duì)象,并添加到Environment環(huán)境中
environment.getPropertySources().addLast(loadProfiles(resource));
}
}
//加載單個(gè)配置文件
private PropertySource<?> loadProfiles(Resource resource) {
if (!resource.exists()) {
throw new IllegalArgumentException("資源" + resource + "不存在");
}
if(resource.getFilename().contains(".yml")){
return loadYaml(resource);
} else {
return loadProperty(resource);
}
}
/**
* 加載properties格式的配置文件
* @param resource
* @return
*/
private PropertySource loadProperty(Resource resource){
try {
//從輸入流中加載一個(gè)Properties對(duì)象
Properties properties = new Properties();
properties.load(resource.getInputStream());
return new PropertiesPropertySource(resource.getFilename(), properties);
}catch (Exception ex) {
throw new IllegalStateException("加載配置文件失敗" + resource, ex);
}
}
/**
* 加載yml格式的配置文件
* @param resource
* @return
*/
private PropertySource loadYaml(Resource resource){
try {
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
factory.setResources(resource);
//從輸入流中加載一個(gè)Properties對(duì)象
Properties properties = factory.getObject();
return new PropertiesPropertySource(resource.getFilename(), properties);
}catch (Exception ex) {
throw new IllegalStateException("加載配置文件失敗" + resource, ex);
}
}
}
接著,在resources資源目錄下,我們還需要?jiǎng)?chuàng)建一個(gè)文件META-INF/spring.factories,通過spi方式,將自定義環(huán)境處理類加載到Spring處理器里面,當(dāng)項(xiàng)目啟動(dòng)時(shí),會(huì)自動(dòng)調(diào)用這個(gè)類!
#啟用我們的自定義環(huán)境處理類
org.springframework.boot.env.EnvironmentPostProcessor=com.example.property.env.MyEnvironmentPostProcessor
這種自定義環(huán)境處理類方式,相對(duì)會(huì)更佳靈活,首先編寫一個(gè)通用的配置文件解析類,支持properties和yml文件的讀取,然后將其注入到Spring容器里面,基本上可以做到一勞永逸!
2.5、最后,我們來介紹一下yml文件讀取
在上文中,我們大部分都是以properties為案例進(jìn)行介紹,可能有的人已經(jīng)踩過坑了,在項(xiàng)目中使用@PropertySource注解來加載yml文件,結(jié)果啟動(dòng)直接報(bào)錯(cuò),原因是@PropertySource不支持直接解析yml文件,只能解析properties文件。
那如果,我想單獨(dú)解析yml文件,也不想弄一個(gè)【自定義環(huán)境處理類】這種方式來讀取文件,應(yīng)該如何處理呢?
操作方式也很簡單,以自定義的blog.yml文件為例!
blog.yml文件內(nèi)容:
pzblog:
name: helloWorld
然后,創(chuàng)建一個(gè)讀取yml文件的配置類
@Configuration
public class ConfigYaml {
/**
* 加載YML格式自定義配置文件
* @return
*/
@Bean
public static PropertySourcesPlaceholderConfigurer properties() {
PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer();
YamlPropertiesFactoryBean yaml = new YamlPropertiesFactoryBean();
yaml.setResources(new ClassPathResource("blog.yml"));
configurer.setProperties(yaml.getObject());
return configurer;
}
}
讀取數(shù)據(jù)的方式,與之類似!
@RestController
public class HelloController {
@Value("${pzblog.name}")
private String pzblogName;
@GetMapping("pzblogName")
public String pzblogName(){
return JSON.toJSONString(pzblogName);
}
}
三、小結(jié)
本文主要圍繞 SpringBoot 加載配置文件的幾種實(shí)現(xiàn)方式,做了一次內(nèi)容總結(jié),如果有遺漏的地方,歡迎網(wǎng)友批評(píng)指出!
四、參考
1、springBoot 官方文檔