自定義的配置檔案,如何注入到SpringBoot?

語言: CN / TW / HK

一、簡介

在實際的專案開發過程中,我們經常需要將某些變數從程式碼裡面抽離出來,放在配置檔案裡面,以便更加統一、靈活的管理服務配置資訊。比如,資料庫、eureka、zookeeper、redis、mq、kafka 等服務元件的連線引數配置,還有我們自定義的專案引數配置變數。

當然,實際上根據當前的業務需求,我們往往會自定義引數,然後注入到程式碼裡面去,以便靈活使用!

SpringBoot 專案在啟用時,首先會預設載入bootstrap.properties或者bootstrap.yml這兩個配置檔案(這兩個優先順序最高);接著會載入application.properties或application.yml;如果何配置了spring.profiles這個變數,同時還會載入對應的application-{profile}.properties或者application-{profile}.yml檔案,profile為對應的環境變數,比如dev,如果沒有配置,則會載入profile=default的配置檔案。

雖然說配置項都寫在同一個配置檔案沒有問題,但是很多時候我們仍然希望能分開寫,這樣會比較清晰,比如zookeeper的配置寫在zookeeper.properties,資料庫相關的配置寫在datasource.properties等等,因此就需要設定載入外部配置檔案!

具體該如何實現呢,我們一起來看看!

二、程式碼實踐

2.1、通過@value註解實現引數載入

當我們想要在某個類裡面注入某個變數,通過@value註解就可以簡單實現引數的注入!

例如application.properties檔案裡,配置一個config.name的變數key,值為zhangsan

//引數定義
config.name=zhangsan

然後在對應的類裡面,通過引數@value注入即可!

@RestController
public class HelloController {
    
    @Value("${config.name}")
    private String config;

    @GetMapping("config")
    public String config(){
        return JSON.toJSONString(config);
    }
}

使用@value註解注入配置,通常情況下有個要求就是,註解裡面的變數,必須在application.properties檔案裡面事先定義好,否則啟動報錯!

當然,如果我們不想讓它抱錯,我們可以給它一個預設值xxx,比如:

@Value("${config.name:xxx}")
private String config;

這樣,SpringBoot 專案在啟用時不會報錯!

2.2、通過@ConfigurationProperties註解實現引數載入

某些場景下,@value註解並不能滿足我們所有的需求,比如引數配置的資料型別是一個物件或者陣列,這個時候才用@ConfigurationProperties會是一個比較好的選擇!

配置一個物件型別的引數

例如在application.properties檔案裡,當我們想配置一個物件型別的引數,我們可以這樣操作!

//引數定義
config2.name=demo_1
config2.value=demo_value_1

然後,建立一個配置類Config2,用於將定義的變數對映到配置類裡面。

@Component
@ConfigurationProperties(prefix = "config2")
public class Config2 {

    public String name;

    public String value;

    
}

讀取資料的方式,也很簡單,直接注入到對應的類裡面就可以了

@RestController
public class HelloController {
    
    @Autowired
    private Config2 config2;

    @GetMapping("config2")
    public String config2(){
        return JSON.toJSONString(config2);
    }
}

配置一個Map型別的引數

例如在application.properties檔案裡,當我們想配置一個 Map 型別的引數,我們可以這樣操作!

//引數定義
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

然後,建立一個配置類Config3,用於將定義的變數對映到配置類裡面。

@Component
@ConfigurationProperties(prefix = "config3")
public class Config3 {

    private Map<String, String> map1 = new HashMap<>();

    private Map<String, String> map2 = new HashMap<>();

    
}

讀取資料的方式,與之類似!

@RestController
public class HelloController {
    
    @Autowired
    private Config3 config3;

    @GetMapping("config3")
    public String config3(){
        return JSON.toJSONString(config3);
    }
}

配置一個List型別的引數

例如在application.properties檔案裡,當我們想配置一個 List 型別的引數,我們可以這樣操作!

//引數定義
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

然後,建立一個配置類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;

    
}

讀取資料的方式,與之類似!

@RestController
public class HelloController {
    
    @Autowired
    private Config4 config4;

    @GetMapping("config4")
    public String config4(){
        return JSON.toJSONString(config4);
    }
}

2.3、通過@PropertySource註解實現配置檔案載入

正如我們最開始所介紹的,很多時間,我們希望將配置檔案分卡寫,比如zookeeper元件對應的服務配置檔案是zookeeper.properties,redis元件對應的服務配置檔案是redis.properties等等。

這種自定義的配置檔案,我們應該如何載入到Spring容器裡面呢?

其實方法也很簡單,通過@PropertySource就可以實現!

首先,我們在resources資原始檔夾下,建立兩個配置檔案test.properties和bussiness.properties,內容如下!

test.properties檔案內容:

aaa.a1=aa1123
aaa.a2=aa2123
aaa.a3=aa3123
aaa.a4=aa4123

bussiness.properties檔案內容:

bbbb.a1=bb1123
bbbb.a2=bb2123
bbbb.a3=bb3123
bbbb.a4=bb4123

在SpringBoot啟動類上載入配置檔案即可!

@SpringBootApplication
@PropertySource(value = {"test.properties","bussiness.properties"})
public class PropertyApplication {

    public static void main(String[] args) {
        SpringApplication.run(PropertyApplication.class, args);
    }
}

讀取資料的方式,與之類似!

@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);
    }
}

如果我們只是在業務中需要用到自定義配置檔案的值,這樣引入並沒有什麼問題;但是如果某些自定義的變數,在專案啟動的時候需要用到,這種方式會存在一些問題,原因如下:

翻譯過來的意思就是說:

雖然在@SpringBootApplication上使用@PropertySource似乎是在環境中載入自定義資源的一種方便而簡單的方法,但我們不推薦使用它,因為SpringBoot在重新整理應用程式上下文之前就準備好了環境。使用@PropertySource定義的任何鍵都載入得太晚,無法對自動配置產生任何影響。

因此,如果某些引數是啟動項變數,建議將其定義在application.properties或application.yml檔案裡面,這樣就不會有問題!

或者,採用【自定義環境處理類】來實現配置檔案的載入!

2.4、通過自定義環境處理類,實現配置檔案的載入

實現方法也很簡單,首先,建立一個實現自EnvironmentPostProcessor介面的類,然後自行載入配置檔案。

public class MyEnvironmentPostProcessor implements EnvironmentPostProcessor {


    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
        
        String[] profiles = {
                "test.properties",
                "bussiness.properties",
                "blog.yml"
        };

        
        for (String profile : profiles) {
            
            Resource resource = new ClassPathResource(profile);
            
            environment.getPropertySources().addLast(loadProfiles(resource));
        }
    }

    
    private PropertySource<?> loadProfiles(Resource resource) {
        if (!resource.exists()) {
            throw new IllegalArgumentException("資源" + resource + "不存在");
        }
        if(resource.getFilename().contains(".yml")){
            return loadYaml(resource);
        } else {
            return loadProperty(resource);
        }
    }

    
    private PropertySource loadProperty(Resource resource){
        try {
            
            Properties properties = new Properties();
            properties.load(resource.getInputStream());
            return new PropertiesPropertySource(resource.getFilename(), properties);
        }catch (Exception ex) {
            throw new IllegalStateException("載入配置檔案失敗" + resource, ex);
        }
    }

    
    private PropertySource loadYaml(Resource resource){
        try {
            YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
            factory.setResources(resource);
            
            Properties properties = factory.getObject();
            return new PropertiesPropertySource(resource.getFilename(), properties);
        }catch (Exception ex) {
            throw new IllegalStateException("載入配置檔案失敗" + resource, ex);
        }
    }
}

接著,在resources資源目錄下,我們還需要建立一個檔案META-INF/spring.factories,通過spi方式,將自定義環境處理類載入到Spring處理器裡面,當專案啟動時,會自動呼叫這個類!

#啟用我們的自定義環境處理類
org.springframework.boot.env.EnvironmentPostProcessor=com.example.property.env.MyEnvironmentPostProcessor

這種自定義環境處理類方式,相對會更佳靈活,首先編寫一個通用的配置檔案解析類,支援properties和yml檔案的讀取,然後將其注入到Spring容器裡面,基本上可以做到一勞永逸!

2.5、最後,我們來介紹一下yml檔案讀取

在上文中,我們大部分都是以properties為案例進行介紹,可能有的人已經踩過坑了,在專案中使用@PropertySource註解來載入yml檔案,結果啟動直接報錯,原因是@PropertySource不支援直接解析yml檔案,只能解析properties檔案。

那如果,我想單獨解析yml檔案,也不想弄一個【自定義環境處理類】這種方式來讀取檔案,應該如何處理呢?

操作方式也很簡單,以自定義的blog.yml檔案為例!

blog.yml檔案內容:

pzblog:
  name: helloWorld

然後,建立一個讀取yml檔案的配置類

@Configuration
public class ConfigYaml {

    
    @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;
    }
}

讀取資料的方式,與之類似!

@RestController
public class HelloController {
    
    @Value("${pzblog.name}")
    private String pzblogName;

    @GetMapping("pzblogName")
    public String pzblogName(){
        return JSON.toJSONString(pzblogName);
    }
}

三、小結

本文主要圍繞 SpringBoot 載入配置檔案的幾種實現方式,做了一次內容總結,如果有遺漏的地方,歡迎網友批評指出!

四、參考

1、springBoot 官方文件