@PropertySource
註解加載yml
文件背景: 業務須要建立多個配置類,基於 yml
文件擁有簡潔的層次結構,遂配置文件選擇 yml
類型。 但在實際的開發中遇到使用 @PropertySource
註解沒法加載 yml
配置文件問題。spring
首先咱們先來分析一下 @PropertySource
註解的源碼:bash
public @interface PropertySource {
/** 加載資源的名稱 */
String name() default "";
/**
* 加載資源的路徑,可以使用classpath,如:
* "classpath:/config/test.yml"
* 若有多個文件路徑放在{}中,使用','號隔開,如:
* {"classpath:/config/test1.yml","classpath:/config/test2.yml"}
* 除使用classpath外,還可以使用文件的地址,如:
* "file:/rest/application.properties"
*/
String[] value();
/** 此屬性爲根據資源路徑找不到文件後是否報錯, 默認爲是 false */
boolean ignoreResourceNotFound() default false;
/** 此爲讀取文件的編碼, 若配置中有中文建議使用 'utf-8' */
String encoding() default "";
/**
* 關鍵:此爲讀取資源文件的工程類, 默認爲:
* 'PropertySourceFactory.class'
*/
Class<? extends PropertySourceFactory> factory() default PropertySourceFactory.class;
}
複製代碼
從源碼能夠看出,讀取資源文件 PropertySourceFactory
接口是關鍵,加下來打開 PropertySourceFactory
接口的源碼:app
public interface PropertySourceFactory {
PropertySource<?> createPropertySource(@Nullable String var1, EncodedResource var2) throws IOException;
}
複製代碼
發現其中只有一個建立屬性資源接口的方法,接下來咱們找到實現這個方法的類:ide
public class DefaultPropertySourceFactory implements PropertySourceFactory {
public DefaultPropertySourceFactory() {
}
public PropertySource<?> createPropertySource(@Nullable String name, EncodedResource resource) throws IOException {
return name != null ? new ResourcePropertySource(name, resource) : new ResourcePropertySource(resource);
}
}
複製代碼
在這個類中咱們發現其返回了一個對象 ResourcePropertySource
,找到 DefaultPropertySourceFactory
類使用的兩個 ResourcePropertySource
類的構造方法:測試
public ResourcePropertySource(String name, EncodedResource resource) throws IOException {
super(name, PropertiesLoaderUtils.loadProperties(resource));
this.resourceName = getNameForResource(resource.getResource());
}
public ResourcePropertySource(EncodedResource resource) throws IOException {
super(getNameForResource(resource.getResource()), PropertiesLoaderUtils.loadProperties(resource));
this.resourceName = null;
}
複製代碼
在上面代碼中,兩個構造方法都使用了 PropertiesLoaderUtils.loadProperties()
這個屬性的方法, 一直點下去, 會發現這麼一段代碼:ui
static void fillProperties(Properties props, EncodedResource resource, PropertiesPersister persister)
throws IOException {
InputStream stream = null;
Reader reader = null;
try {
String filename = resource.getResource().getFilename();
// private static final String XML_FILE_EXTENSION = ".xml";
if (filename != null && filename.endsWith(XML_FILE_EXTENSION)) {
stream = resource.getInputStream();
persister.loadFromXml(props, stream);
}
else if (resource.requiresReader()) {
reader = resource.getReader();
persister.load(props, reader);
}
else {
stream = resource.getInputStream();
persister.load(props, stream);
}
}
finally {
if (stream != null) {
stream.close();
}
if (reader != null) {
reader.close();
}
}
}
複製代碼
由上可知,@PropertySource
註解也能夠用來加載 xml
文件,接下來根據 persister.load(props, stream)
方法一直點下去會找到下面一段代碼:this
private void load0 (LineReader lr) throws IOException {
char[] convtBuf = new char[1024];
int limit;
int keyLen;
int valueStart;
char c;
boolean hasSep;
boolean precedingBackslash;
/**
* 每次讀取一行
*/
while ((limit = lr.readLine()) >= 0) {
c = 0;
keyLen = 0;
valueStart = limit;
hasSep = false;
//System.out.println("line=<" + new String(lineBuf, 0, limit) + ">");
precedingBackslash = false;
/**
* 遍歷一行字的每個字符
* 若字符中出現 '='、':'、' '、'\t'、'\f' 則跳出循環
*/
while (keyLen < limit) {
c = lr.lineBuf[keyLen];
//need check if escaped.
// 若是當前遍歷字符爲 '=' 或 ':' 則跳出循環
if ((c == '=' || c == ':') && !precedingBackslash) {
valueStart = keyLen + 1;
hasSep = true;
break;
}
// 若是當前遍歷字符爲 ' ' 或 '\t' 或 '\f' 跳出循環,
// 但在接下來的循環中還須要繼續遍歷知道找到 '=' 或 ':'
else if ((c == ' ' || c == '\t' || c == '\f') && !precedingBackslash) {
valueStart = keyLen + 1;
break;
}
// 檢查是否轉義
if (c == '\\') {
precedingBackslash = !precedingBackslash;
} else {
precedingBackslash = false;
}
// 每次循環,keyLen + 1
keyLen++;
}
/**
* 判斷valueStart(值的開始下標)是否小於讀取行的長度,若小於,則進入循環
*/
while (valueStart < limit) {
c = lr.lineBuf[valueStart];
// 判斷當前字符是否等於空格、製表符、換頁符。都不等於則進入循環
if (c != ' ' && c != '\t' && c != '\f') {
// 當 hasSep 爲false時表明上個 while (keyLen < limit) 循環跳出時c爲 空格或製表符或換頁符
// 這裏繼續循環直到找到'='或':'號爲止
// 因而可知 在配置文件中'=' 或 ':' 號前可有空格、製表符、換頁符
if (!hasSep && (c == '=' || c == ':')) {
hasSep = true;
} else {
break;
}
}
// 每次循環,valueStart + 1
valueStart++;
}
// 獲取配置文件中的key,value並保存
String key = loadConvert(lr.lineBuf, 0, keyLen, convtBuf);
String value = loadConvert(lr.lineBuf, valueStart, limit - valueStart, convtBuf);
put(key, value);
}
}
複製代碼
上面 load0
方法每次讀取一行,而後根據 '='
或 ':'
來獲取 key
和 value
,而 yml
具備鮮明層次結構的特色則不能由此方法讀取。編碼
@PropertySource
註解讀取屬性文件的關鍵在於 PropertySourceFactory
接口中的 createPropertySource
方法,因此咱們想要實現 @PropertySource
註解讀取 yml
文件就須要實現 createPropertySource
方法,在 @PropertySource
註解其是經過 DefaultPropertySourceFactory
類來實現這個方法,咱們只須要繼承此類,並重寫其 createPropertySource
方法便可,實現代碼以下:@Override
public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
if (resource == null){
return super.createPropertySource(name, resource);
}
List<PropertySource<?>> sources = new YamlPropertySourceLoader().load(resource.getResource().getFilename(), resource.getResource());
return sources.get(0);
}
複製代碼
注: spring boot
中 yml
、yaml
對應的加載類爲 YamlPropertySourceLoader
。spa
@Component
@PropertySource(value = "test.yml", encoding = "utf-8", factory = TestFactory.class)
@ConfigurationProperties(prefix = "com.test")
public class IdCardServerConfig {
private String serverCode;
...
}
複製代碼