SpringBoot系列之@PropertySource支持yaml文件讀取html
最近在作實驗,想經過@PropertySource註解讀取配置文件的屬性,進行映射,習慣上用properties都是測試沒問題的,偶然換成yaml文件,發現都讀取不到屬性值java
由於yaml語法很簡潔,比較喜歡寫yaml配置文件,很顯然,@PropertySource默認不支持yaml讀取,咱們改爲@Value註解也是能夠讀取的,不過屬性一堆的話,一個一個讀取也是很繁瑣的,經過網上找資料和本身實驗驗證,發現是能夠實現對yaml支持git
而後,爲何@PropertySource註解默認不支持?能夠簡單跟一下源碼github
@PropertySource源碼:
根據註釋,默認使用DefaultPropertySourceFactory類做爲資源文件加載類
裏面仍是調用Spring框架底層的PropertiesLoaderUtils工具類進行讀取的
PropertiesLoaderUtils.loadProperties
從源碼能夠看出也是支持xml文件讀取的,能支持reader就獲取reader對象,不然出件inputStream
spring
load0方法是關鍵,這裏加了同步鎖
很重要的load0 方法抓取出來:json
private void load0 (LineReader lr) throws IOException { char[] convtBuf = new char[1024]; int limit; // 當前key所在位置 int keyLen; // 當前value所在位置 int valueStart; char c;//讀取的字符 boolean hasSep; boolean precedingBackslash;//是否轉義字符,eg:/n etc. // 一行一行地讀取 while ((limit = lr.readLine()) >= 0) { c = 0; keyLen = 0; valueStart = limit; hasSep = false; //System.out.println("line=<" + new String(lineBuf, 0, limit) + ">"); precedingBackslash = false; //key的長度小於總的字符長度,那麼就進入循環 while (keyLen < limit) { c = lr.lineBuf[keyLen]; //need check if escaped. if ((c == '=' || c == ':') && !precedingBackslash) { valueStart = keyLen + 1; hasSep = true; break; } else if ((c == ' ' || c == '\t' || c == '\f') && !precedingBackslash) { valueStart = keyLen + 1; break; } if (c == '\\') { precedingBackslash = !precedingBackslash; } else { precedingBackslash = false; } keyLen++; } //value的起始位置小於總的字符長度,那麼就進入該循環 while (valueStart < limit) { c = lr.lineBuf[valueStart]; //當前字符是否非空格類字符 if (c != ' ' && c != '\t' && c != '\f') { if (!hasSep && (c == '=' || c == ':')) { hasSep = true; } else { break; } } valueStart++; } //讀取key String key = loadConvert(lr.lineBuf, 0, keyLen, convtBuf); //讀取value String value = loadConvert(lr.lineBuf, valueStart, limit - valueStart, convtBuf); put(key, value); } }
ok,從源碼能夠看出,這個方法是一行一行地讀取,而後根據冒號、等於號、空格等進行校驗,通過一系列遍歷以後獲取key和value,而yaml語法是以縮進來辨別的,通過本身調試,這個方法也是不支持yaml文件的讀取的,properties源碼是比較多的,具體的Properties源碼實現的能夠參考博客:https://www.cnblogs.com/liuming1992/p/4360310.html,這篇博客寫的比較詳細api
ok,而後給個例子來實現對yaml配置文件的讀取springboot
# 測試ConfigurationProperties user: userName: root isAdmin: true regTime: 2019/11/01 isOnline: 1 maps: {k1 : v1,k2: v2} lists: - list1 - list2 address: tel: 15899988899 name: 上海市
模仿DefaultPropertySourceFactory寫一個yaml資源文件讀取的工廠類:框架
package com.example.springboot.properties.core.propertyResouceFactory; import org.springframework.boot.env.YamlPropertySourceLoader; import org.springframework.core.env.PropertiesPropertySource; import org.springframework.core.env.PropertySource; import org.springframework.core.io.support.EncodedResource; import org.springframework.core.io.support.PropertySourceFactory; import org.springframework.lang.Nullable; import java.io.IOException; import java.util.List; import java.util.Optional; import java.util.Properties; /** * <pre> * YAML配置文件讀取工廠類 * </pre> * <p> * <pre> * @author nicky.ma * 修改記錄 * 修改後版本: 修改人: 修改日期: 2019/11/13 15:44 修改內容: * </pre> */ public class YamlPropertyResourceFactory implements PropertySourceFactory { /** * Create a {@link PropertySource} that wraps the given resource. * * @param name the name of the property source * @param encodedResource the resource (potentially encoded) to wrap * @return the new {@link PropertySource} (never {@code null}) * @throws IOException if resource resolution failed */ @Override public PropertySource<?> createPropertySource(@Nullable String name, EncodedResource encodedResource) throws IOException { String resourceName = Optional.ofNullable(name).orElse(encodedResource.getResource().getFilename()); if (resourceName.endsWith(".yml") || resourceName.endsWith(".yaml")) {//yaml資源文件 List<PropertySource<?>> yamlSources = new YamlPropertySourceLoader().load(resourceName, encodedResource.getResource()); return yamlSources.get(0); } else {//返回空的Properties return new PropertiesPropertySource(resourceName, new Properties()); } } }
寫個bean類進行屬性映射,注意換一下factory參數,factory = YamlPropertyResourceFactory.class
ide
package com.example.springboot.properties.bean; import com.example.springboot.properties.core.propertyResouceFactory.CommPropertyResourceFactory; import com.example.springboot.properties.core.propertyResouceFactory.YamlPropertyResourceFactory; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.PropertySource; import org.springframework.core.io.support.DefaultPropertySourceFactory; import org.springframework.stereotype.Component; import java.util.Date; import java.util.List; import java.util.Map; /** * <pre> * * </pre> * * @author nicky * <pre> * 修改記錄 * 修改後版本: 修改人: 修改日期: 2019年11月03日 修改內容: * </pre> */ @Component @PropertySource(value = "classpath:user.yml",encoding = "utf-8",factory = YamlPropertyResourceFactory.class) @ConfigurationProperties(prefix = "user") public class User { private String userName; private boolean isAdmin; private Date regTime; private Long isOnline; private Map<String,Object> maps; private List<Object> lists; private Address address; @Override public String toString() { return "User{" + "userName='" + userName + '\'' + ", isAdmin=" + isAdmin + ", regTime=" + regTime + ", isOnline=" + isOnline + ", maps=" + maps + ", lists=" + lists + ", address=" + address + '}'; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public boolean isAdmin() { return isAdmin; } public void setAdmin(boolean admin) { isAdmin = admin; } public Date getRegTime() { return regTime; } public void setRegTime(Date regTime) { this.regTime = regTime; } public Long getIsOnline() { return isOnline; } public void setIsOnline(Long isOnline) { this.isOnline = isOnline; } public Map<String, Object> getMaps() { return maps; } public void setMaps(Map<String, Object> maps) { this.maps = maps; } public List<Object> getLists() { return lists; } public void setLists(List<Object> lists) { this.lists = lists; } public Address getAddress() { return address; } public void setAddress(Address address) { this.address = address; } }
package com.example.springboot.properties.bean; /** * <pre> * * </pre> * * @author nicky * <pre> * 修改記錄 * 修改後版本: 修改人: 修改日期: 2019年11月03日 修改內容: * </pre> */ public class Address { private String tel; private String name; @Override public String toString() { return "Address{" + "tel='" + tel + '\'' + ", name='" + name + '\'' + '}'; } public String getTel() { return tel; } public void setTel(String tel) { this.tel = tel; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
junit測試類代碼:
package com.example.springboot.properties; import com.example.springboot.properties.bean.User; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest class SpringbootPropertiesConfigApplicationTests { @Autowired User user; @Test public void testConfigurationProperties(){ System.out.println(user); } }
User{userName='root(15899988899)', isAdmin=false, regTime=Fri Nov 01 00:00:00 SGT 2019, isOnline=1, maps={k2=v2, k1=-30363940}, lists=[1f90e323-8a9c-4194-a31c-be9abbe9ce38, a869f68947faa92964d2a36ce86ee980], address=Address{tel='15899988899', name='上海浦東區'}}
若是既要支持原來的yaml,又要支持properties,就能夠將propertyResourceFactory類進行改寫一下:
package com.example.springboot.properties.core.propertyResouceFactory; import org.springframework.boot.env.YamlPropertySourceLoader; import org.springframework.core.env.PropertySource; import org.springframework.core.io.support.DefaultPropertySourceFactory; import org.springframework.core.io.support.EncodedResource; import org.springframework.core.io.support.PropertySourceFactory; import org.springframework.lang.Nullable; import java.io.IOException; import java.util.List; import java.util.Optional; /** * <pre> * 通用的資源文件讀取工廠類 * </pre> * <p> * <pre> * @author mazq * 修改記錄 * 修改後版本: 修改人: 修改日期: 2019/11/25 10:35 修改內容: * </pre> */ public class CommPropertyResourceFactory implements PropertySourceFactory { /** * Create a {@link PropertySource} that wraps the given resource. * * @param name the name of the property source * @param resource the resource (potentially encoded) to wrap * @return the new {@link PropertySource} (never {@code null}) * @throws IOException if resource resolution failed */ @Override public PropertySource<?> createPropertySource(@Nullable String name, EncodedResource resource) throws IOException { String resourceName = Optional.ofNullable(name).orElse(resource.getResource().getFilename()); if (resourceName.endsWith(".yml") || resourceName.endsWith(".yaml")) { List<PropertySource<?>> yamlSources = new YamlPropertySourceLoader().load(resourceName, resource.getResource()); return yamlSources.get(0); } else { return new DefaultPropertySourceFactory().createPropertySource(name, resource); } } }
調用的時候,要改一下factory參數
@PropertySource(value = "classpath:user.yml",encoding = "utf-8",factory = CommPropertyResourceFactory.class)
這個類就能夠支持原來的properties文件,也能夠支持yaml文件
User{userName='root(15899988899)', isAdmin=false, regTime=Fri Nov 01 00:00:00 SGT 2019, isOnline=1, maps={k2=v2, k1=-30363940}, lists=[1f90e323-8a9c-4194-a31c-be9abbe9ce38, a869f68947faa92964d2a36ce86ee980], address=Address{tel='15899988899', name='上海浦東區'}}
代碼下載:github下載連接