首頁 > 軟體

使用@PropertySource讀取組態檔通過@Value進行引數注入

2022-03-25 16:00:31

@PropertySource讀取組態檔通過@Value引數注入

有引數檔案如下test.properties

project.author=wpfc
project.create_time=2018/3/29

在系統中讀取對應的資料,並注入到屬性中

@Configuration
@ComponentScan("cn.edu.ntu")
@PropertySource("classpath:test.properties")
public class ElConfig {
 
    @Value("#{systemProperties['os.name']}")
    private String osName;
    
    //要想使用@Value 用${}預留位置注入屬性,這個bean是必須的(PropertySourcesPlaceholderConfigurer),
    //這個就是佔位bean,
    //另一種方式是不用value直接用Envirment變數直接getProperty('key')  
    @Value("${project.author}")
    public String author;
    
    @Autowired
    private Environment environment;
    
    //You need this if you use @PropertySource + @Value
    @Bean
    public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
       return new PropertySourcesPlaceholderConfigurer();
    }
    
    public void printProperties(){
        System.out.println("os name : " + osName);
        System.out.println("author  : " + author);
        System.out.println("env     : " + environment.getProperty("project.create_time"));
    }    
}

測試方法:

public class MainApplication {
    public static void main(String[] args){
        AnnotationConfigApplicationContext context = null;
        context = new AnnotationConfigApplicationContext(ElConfig.class);
        ElConfig bean = context.getBean(ElConfig.class);
        bean.printProperties();
    }    
}

測試結果:

os name : Windows 7
author  : wpfc
env     : 2018/3/29

  • @Import引入javaConfig設定的設定類
  • @ImportResource引入xml對應的組態檔

Spring讀取設定@Value、@PropertySource、@ConfigurationProperties使用

Spring (Boot)獲取引數的方式有很多,其中最被我們熟知的為@Value了,它不可謂不強大。

今天就針對我們平時最長使用的@Value,以及可能很少人使用的@PropertySource、@ConfigurationProperties等相關注解進行一個詳細的掃盲,希望能夠幫助到到家,使用起來更加順暢

@Value

@Value註解的注入非常強大,可以藉助組態檔的注入、也可以直接注入

注入普通字串

    @Value("normal")
    private String normal; // normal (顯然這種注入的意義不大)

注入作業系統屬性

@Value("#{systemProperties['os.name']}")
    private String systemPropertiesName; 
//效果等同於  是因為spring模版把系統變數否放進了Enviroment
@Value("${os.name}")
    private String systemPropertiesName;

注入表示式結果

@Value("#{ T(java.lang.Math).random() * 100.0 }")
    private double randomNumber; //41.29185128620939

注入其它Bean的屬性:Person類的name屬性

    @Bean
    public Person person() {
        Person person = new Person();
        person.setName("fangshixiang");
        return person;
    }
 
//注入屬性
    @Value("#{person.name}")
    private String personName;
 
    @Test
    public void contextLoads() {
        System.out.println(personName); //fangshixiang
    }

注入檔案資源

在resources下放置一個jdbc.properties組態檔。然後可以直接注入

    @Value("classpath:jdbc.properties")
    private Resource resourceFile; // 注入檔案資源
 
    @Test
    public void contextLoads() throws IOException {
        System.out.println(resourceFile); //class path resource [jdbc.properties]
        String s = FileUtils.readFileToString(resourceFile.getFile(), StandardCharsets.UTF_8);
        System.out.println(s);
        //輸出:
        //db.username=fangshixiang
        //db.password=fang
        //db.url=jdbc:mysql://localhost:3306/mytest
        //db.driver-class-name=com.mysql.jdbc.Driver
    }

注入Url資源

    @Value("http://www.baidu.com")
    private Resource testUrl; // 注入URL資源 
 
    @Test
    public void contextLoads() {
        System.out.println(testUrl); //URL [http://www.baidu.com]
    }

@Value中$和#的區別

語法:

${ properties }和#{ SpEL }的語法區別

${ property : default_value }

#{ obj.property? : default_value } 表示SpEl表示式通常用來獲取bean的屬性,或者呼叫bean的某個方法。當然還有可以表示常數

正常使用的情況,這裡不做過多的介紹了,現在介紹一些異常情況

${ properties }`:這種比較簡單,如果key找不到,啟動會失敗。如果找不到的時候也希望正常啟動,可以採用冒號+預設值的方式

#{ obj.property? : default_value }

    @Value("#{person}")
    private Person value;
 
    @Test
    public void contextLoads() {
        System.out.println(value); //Person(name=fangshixiang, age=null, addr=null, hobby=null)
    }

我們發現這個很強大,可以直接把容器的裡的一個物件直接注入進來。只是我們可能一般不這麼做。

如果改成person1,在容器裡找不到這個bean,也是會啟動報錯的。@Value("#{person1?:null}")這樣也是不行的,因為person1找不到就會報錯  

    @Value("#{person.name}")
    private String personName;
 
    @Value("#{person.age}")
    private String perAge;
 
    //注入預設值
    @Value("#{person.age?:20}")
    private String perAgeDefault;
 
    //如果age22這個key根本就不存在,啟動肯定會報錯的
    //@Value("#{person.age22?:20}")
    //private String perAgeDefault22;
 
    @Test
    public void contextLoads() {
        System.out.println(personName); //fangshixiang
        System.out.println(perAge); //null
        System.out.println(perAgeDefault); //20
    }

獲取級聯屬性,下面兩種方法都是ok的:

 
    @Value("#{person.parent.name}")
    private String parentName1;
 
    @Value("#{person['parent.name']}")
    private String parentName2;
 
    @Test
    public void contextLoads() {
        System.out.println(parentName1); //fangshixiang
        System.out.println(parentName2); //fangshixiang
    }

二者結合使用:#{ ‘${}’ }

注意結合使用的語法和單引號,不能倒過來。

兩者結合使用,可以利用SpEL的特性,寫出一些較為複雜的表示式,如:

    @Value("#{'${os.name}' + '_' +  person.name}")
    private String age; 
 
    @Test
    public void contextLoads() {
        System.out.println(age); //Windows 10_fangshixiang
    }

@PropertySource:載入設定屬性源

此註解也是非常非常的強大,用好了,可以很好的實現組態檔的分離關注,大大提高開發的效率,實現集中化管理

最簡單的應用,結合@Value注入屬性值(也是最常見的應用)

通過@PropertySource把組態檔載入進來,然後使用@Value獲取

@Configuration
@PropertySource("classpath:jdbc.properties")
public class PropertySourceConfig {
 
    @Value("${db.url}")
    private String dbUrl;
 
    @PostConstruct
    public void postConstruct() {
        System.out.println(dbUrl); //jdbc:mysql://localhost:3306/mytest
    }
}

@PropertySource各屬性介紹

  • value:陣列。指定組態檔的位置。支援classpath:和file:等字首 Spring發現是classpath開頭的,因此最終使用的是Resource的子類ClassPathResource。如果是file開頭的,則最終使用的類是FileSystemResource
  • ignoreResourceNotFound:預設值false。表示如果沒有找到檔案就報錯,若改為true就不報錯。建議保留false
  • encoding:載入進來的編碼。一般不用設定,可以設定為UTF-8等等
  • factory:預設的值為DefaultPropertySourceFactory.class。
	@Override
	public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
		return (name != null ? new ResourcePropertySource(name, resource) : new ResourcePropertySource(resource));
	}

原始碼其實也沒什麼特別的。其重難點在於:

1、DefaultPropertySourceFactory什麼時候被Spring載入呢?

2、name和resource都是什麼時候被賦值進來的?

本文丟擲這兩個問題,具體原因會在後續分析原始碼的相關文章中有所體現。

需要注意的是PropertySourceFactory的載入時機早於Spring Beans容器,因此實現上不能依賴於Spring的IOC。

@PropertySource多環境設定以及表示式使用(spring.profiles.active)

方法一:可以這麼設定

@PropertySource(「classpath:jdbc-${spring.profiles.active}.properties」)

程式設計師在開發時不需要關心生產環境資料庫的地址、賬號等資訊,一次構建即可在不同環境中執行

@ConfigurationProperties

注意:上面其實都是Spring Framwork提供的功能。而@ConfigurationProperties是Spring Boot提供的。包括@EnableConfigurationProperties也是Spring Boot才有的。它在自動化設定中起到了非常關鍵的作用

ConfigurationPropertiesBindingPostProcessor會對標註@ConfigurationProperties註解的Bean進行屬性值的設定。

有時候有這樣子的情景,我們想把組態檔的資訊,讀取並自動封裝成實體類,這樣子,我們在程式碼裡面使用就輕鬆方便多了,這時候,我們就可以使用@ConfigurationProperties,它可以把同類的設定資訊自動封裝成實體類

該註解在Spring Boot的自動化設定中得到了大量的使用

如SpringMVC的自動化設定:

@ConfigurationProperties(prefix = "spring.mvc")
public class WebMvcProperties {}
 
//載入方式
	@Configuration
	@Conditional(DefaultDispatcherServletCondition.class)
	@ConditionalOnClass(ServletRegistration.class)
	// 此處採用這個註解,可議把WebMvcProperties這個Bean載入到容器裡面去~~~
	// WebMvcProperties裡面使用了`@ConfigurationProperties(prefix = "spring.mvc")`
	@EnableConfigurationProperties(WebMvcProperties.class) //載入MVC的組態檔
	protected static class DispatcherServletConfiguration {}

似乎我們能看出來一些該註解的使用方式。

說明:這裡說的兩種,只是說的最常用的。其實只要能往容器注入Bean,都是一種方式,比如上面的@EnableConfigurationProperties方式也是ok的

關於@EnableConfigurationProperties的解釋,在註解驅動的Spring相關博文裡會有體現

加在類上,需要和@Component註解,結合使用.程式碼如下

com.example.demo.name=${aaa:hi}
com.example.demo.age=11
com.example.demo.address[0]=北京  # 注意陣列 List的表示方式 Map/Obj的方式各位可以自行嘗試
com.example.demo.address[1]=上海
com.example.demo.address[2]=廣州
com.example.demo.phone.number=1111111

java程式碼:

@Component
@ConfigurationProperties(prefix = "com.example.demo")
public class People {
 
    private String name;
    private Integer age;
    private List<String> address;
    private Phone phone;
}   

通過@Bean的方式進行宣告,這裡我們加在啟動類即可,程式碼如下

   @Bean
    @ConfigurationProperties(prefix = "com.example.demo")
    public People people() {
        return new People();
    }

此些方式並不需要使用@EnableConfigurationProperties去開啟它。

細節:Bean的欄位必須有get/set方法,請注意~~~

另外還有一種結合@PropertySource使用的方式,可謂完美搭配

@Component
@PropertySource("classpath:config/object.properties")
@ConfigurationProperties(prefix = "obj")
public class ObjectProperties {}

其餘屬性見名之意,這裡一筆帶過:

  • ignoreInvalidFields
  • ignoreNestedProperties
  • ignoreUnknownFields

簡單理解:

  • @ConfigurationProperties 是將application組態檔的某類名下所有的屬性值,自動封裝到實體類中。
  • @Value 是將application組態檔中,所需要的某個屬性值,封裝到java程式碼中以供使用。

應用場景不同:

如果只是某個業務中需要獲取組態檔中的某項值或者設定具體值,可以使用@Value;

如果一個JavaBean中大量屬性值要和組態檔進行對映,可以使用@ConfigurationProperties;

以上為個人經驗,希望能給大家一個參考,也希望大家多多支援it145.com。


IT145.com E-mail:sddin#qq.com