在Spring架構中@PropertySource注解是非常常用的一個注解,其主要作用是将外部化配置解析成key-value鍵值對"存入"Spring容器的Environment環境中,以便在Spring應用中可以通過@Value或者占位符${key}的形式來使用這些配置。
使用案列#
// @PropertySource需要和@Configuration配個使用
// @PropertySource加載的配置檔案時需要注意加載的順序,後面加載的配置會覆寫前面加載的配置
// @PropertySource支援重複注解
// value值不僅支援classpath表達式,還支援任意合法的URI表達式
@Configuration
@PropertySource(value = "classpath:/my.properties",encoding = "UTF8")
@PropertySource(value = "classpath:/my2.properties",encoding = "UTF8",ignoreResourceNotFound = true)
public static class PropertyConfig {
}
@Component
public class App {
@Value("${key1:default-val}")
private String value;
@Value("${key2:default-val2}")
private String value2;
}
下面是配置檔案my.properties和my2.properties的具體内容。
# my.properties
key1=自由之路
# my2.properties
key1=程式員
key2=自由之路
Spring容器啟動時,會将my.properties和my2.properties的内容加載到Environment中,并在App類的依賴注入環節,将key1和key2的值注入到對應的屬性。
自定義PropertySource工廠#
閱讀@PropertySource的源代碼,我們發現還有一個factory屬性。從這個屬性的字面意思看,我們不難猜測出這個屬性設定的是用于産生PropertySource的工廠。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(PropertySources.class)
public @interface PropertySource {
String name() default "";
String[] value();
boolean ignoreResourceNotFound() default false;
String encoding() default "";
Class<? extends PropertySourceFactory> factory() default PropertySourceFactory.class;
}
要深入了解PropertySourceFactory,我們先要知道以下的背景知識。
在Spring中,配置的來源有很多。Spring将配置來源統一抽象成 PropertySource 這個抽象類,Spring中内建的常用的 PropertySource 有以下這些
- MapPropertySource
- CommandLinePropertySource
- PropertiesPropertySource
- SystemEnvironmentPropertySource
- ResourcePropertySource
ResourcePropertySource這個類将一系列配置來源統一成ResourcePropertySource,可以說是對 PropertySource 的進一步封裝。
PropertySourceFactory 接口,用于産生PropertySource。Spring中,PropertySourceFactory 預設的實作是DefaultPropertySourceFactory,用于生産 ResourcePropertySource。
經過上面的介紹,我們知道如果沒有配置@PropertySource的factory屬性的話,預設的PropertySourceFactory使用的就是DefaultPropertySourceFactory。當然,我們也可以自定義PropertySourceFactory,用于“生産”我們自定義的PropertySource。下面就示範一個将yaml檔案解析成MapPropertySource的使用案列。
/**
* Spring中内置的解析yaml的處理器
* YamlProcessor
* - YamlMapFactoryBean --> 解析成Map
* - YamlPropertiesFactoryBean --> 解析成Properties
*/
public class YamlMapSourceFactory implements PropertySourceFactory {
@Override
public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
YamlMapFactoryBean yamlMapFactoryBean = new YamlMapFactoryBean();
yamlMapFactoryBean.setResources(resource.getResource());
Map<String, Object> map = yamlMapFactoryBean.getObject();
return new MapPropertySource(name, map);
}
}
// 加了factory屬性,必須加name屬性
// 有了factory機制,我們可以做很多自定一的擴充,比如配置可以從遠端來
@PropertySource(name = "my.yaml",value = "classpath:/my.yaml",encoding = "UTF8",factory = YamlMapSourceFactory.class)
public static class PropertyConfig {
}
原理簡析#
到這邊我們對@PropertySource已經有了一個感性的認識,知道了其主要作用是将各種類型的外部化配置檔案以key-value的形式加載到Spring的Environment中。這個部分我們從源碼的角度來分析下Spring是怎麼處理@PropertySource這個注解的。分析源碼可以加深我們對@PropertySource的認識(看源碼不是目的,是為了加深了解,學習Spring的設計思想)。
@PropertySource注解的處理是在ConfigurationClassPostProcessor中進行觸發的。最終會調用到ConfigurationClassParser的processPropertySource方法。
// ConfigurationClassParser#processPropertySource
private void processPropertySource(AnnotationAttributes propertySource) throws IOException {
String name = propertySource.getString("name");
if (!StringUtils.hasLength(name)) {
name = null;
}
String encoding = propertySource.getString("encoding");
if (!StringUtils.hasLength(encoding)) {
encoding = null;
}
String[] locations = propertySource.getStringArray("value");
Assert.isTrue(locations.length > 0, "At least one @PropertySource(value) location is required");
boolean ignoreResourceNotFound = propertySource.getBoolean("ignoreResourceNotFound");
Class<? extends PropertySourceFactory> factoryClass = propertySource.getClass("factory");
// 如果有自定義工廠就使用自定義工廠,沒有自定義工廠就使用DefaultPropertySourceFactory
PropertySourceFactory factory = (factoryClass == PropertySourceFactory.class ?
DEFAULT_PROPERTY_SOURCE_FACTORY : BeanUtils.instantiateClass(factoryClass));
// 周遊各個location位址
for (String location : locations) {
try {
// location位址支援占位符的形式
String resolvedLocation = this.environment.resolveRequiredPlaceholders(location);
// 擷取Resource
Resource resource = this.resourceLoader.getResource(resolvedLocation);
addPropertySource(factory.createPropertySource(name, new EncodedResource(resource, encoding)));
}
catch (IllegalArgumentException | FileNotFoundException | UnknownHostException | SocketException ex) {
// Placeholders not resolvable or resource not found when trying to open it
if (ignoreResourceNotFound) {
if (logger.isInfoEnabled()) {
logger.info("Properties location [" + location + "] not resolvable: " + ex.getMessage());
}
}
else {
throw ex;
}
}
}
}
總的來說,Spring處理@PropertySource的源代碼非常簡單,這邊就不再過多贅述了。