一、使用
1、自定义 YmlPropertySourceFactory
public class YmlPropertySourceFactory implements PropertySourceFactory {
@Override
public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
String sourceName = resource.getResource().getFilename();
if (StringUtils.isNotBlank(sourceName) && (sourceName.endsWith(".yml") || sourceName.endsWith(".yaml"))) {
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
factory.setResources(resource.getResource());
factory.afterPropertiesSet();
Properties properties = factory.getObject();
return new PropertiesPropertySource(name,properties);
}
return null;
}
2、使用@PropertySource
注入外部文件,将factory 设置为 YmlPropertySourceFactory
使用绝对路径用 file:开头
@PropertySource(name = "test", value = "file:C:\\Users\\x\\Desktop\\test.yml", factory = YmlPropertySourceFactory.class)
使用类路径用classpath:开头
@PropertySource(name = "test", value = "classpath:test.yml", factory = YmlPropertySourceFactory.class)
@PropertySource(name = "test", value = "classpath:test.yml", factory = YmlPropertySourceFactory.class)
@Component
@Data
public class TestProperties {
@Value("${test.key1}")
private String key1;
@Value("${test.key2}")
private String key2;
}
3、test.yml
写一些yml格式的参数
test:
key1: value1
key2: value2
二、原理
1、在spring 的 ConfigurationClassParser 类中的 doProcessConfigurationClass( ) 方法,会对配置类进行解析,也会解析@PropertySource 注解
//获取 @PropertySources、@PropertySource
for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), PropertySources.class,
org.springframework.context.annotation.PropertySource.class)) {
if (this.environment instanceof ConfigurableEnvironment) {
//解析注解
processPropertySource(propertySource);
}
else {
logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
"]. Reason: Environment must implement ConfigurableEnvironment");
}
}
2、processPropertySource(propertySource)
`private void processPropertySource(AnnotationAttributes propertySource) throws IOException {
//name 名称
String name = propertySource.getString("name");
if (!StringUtils.hasLength(name)) {
name = null;
}
//encoding 编码格式
String encoding = propertySource.getString("encoding");
if (!StringUtils.hasLength(encoding)) {
encoding = null;
}
//value 配置文件路径
String[] locations = propertySource.getStringArray("value");
Assert.isTrue(locations.length > 0, "At least one @PropertySource(value) location is required");
//ignoreResourceNotFound 配置文件找不到的时候忽略异常
boolean ignoreResourceNotFound = propertySource.getBoolean("ignoreResourceNotFound");
//factory 用于创建 PropertySource
Class<? extends PropertySourceFactory> factoryClass = propertySource.getClass("factory");
PropertySourceFactory factory = (factoryClass == PropertySourceFactory.class ?
DEFAULT_PROPERTY_SOURCE_FACTORY : BeanUtils.instantiateClass(factoryClass));
//遍历文件路径
for (String location : locations) {
try {
//解析占位符 ${}
String resolvedLocation = this.environment.resolveRequiredPlaceholders(location);
//获取文件 Resource
Resource resource = this.resourceLoader.getResource(resolvedLocation);
//创建PropertySource,添加到上下文环境中
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;
}
}
}
}
3、getResource( )
@Override
public Resource getResource(String location) {
if (this.resourceLoader != null) {
return this.resourceLoader.getResource(location);
}
return super.getResource(location);
}
@Override
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
for (ProtocolResolver protocolResolver : getProtocolResolvers()) {
Resource resource = protocolResolver.resolve(location, this);
if (resource != null) {
return resource;
}
}
// 以/开头
if (location.startsWith("/")) {
return getResourceByPath(location);
}
//以classpath:开头
else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
}
else {
try {
// Try to parse the location as a URL...
URL url = new URL(location);
return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
}
catch (MalformedURLException ex) {
// No URL -> resolve as resource path.
return getResourceByPath(location);
}
}
}
5、createPropertySource( )
回调 YmlPropertySourceFactory 的 createPropertySource( ) 方法
6、addPropertySource( )
private void addPropertySource(PropertySource<?> propertySource) {
String name = propertySource.getName();
//获取上下文环境中的 propertySources
MutablePropertySources propertySources = ((ConfigurableEnvironment) this.environment).getPropertySources();
if (this.propertySourceNames.contains(name)) {
// We've already added a version, we need to extend it
//环境中已经存在同名的 propertySource
PropertySource<?> existing = propertySources.get(name);
if (existing != null) {
PropertySource<?> newSource = (propertySource instanceof ResourcePropertySource ?
((ResourcePropertySource) propertySource).withResourceName() : propertySource);
//如果是复合的 CompositePropertySource ,将新的 newSource 直接加入复合的
if (existing instanceof CompositePropertySource) {
((CompositePropertySource) existing).addFirstPropertySource(newSource);
}
else {
//如果是普通的 ResourcePropertySource
if (existing instanceof ResourcePropertySource) {
existing = ((ResourcePropertySource) existing).withResourceName();
}
//创建复合的将 existing 和 newSource 都加入复合的
CompositePropertySource composite = new CompositePropertySource(name);
composite.addPropertySource(newSource);
composite.addPropertySource(existing);
//用复合的 CompositePropertySource 替换掉环境中原来的
propertySources.replace(name, composite);
}
return;
}
}
//加入环境中的 propertySources 中
if (this.propertySourceNames.isEmpty()) {
propertySources.addLast(propertySource);
}
else {
String firstProcessed = this.propertySourceNames.get(this.propertySourceNames.size() - 1);
propertySources.addBefore(firstProcessed, propertySource);
}
this.propertySourceNames.add(name);
}