记一则对SpringBoot@PropertySource不能解
scio
起因
在项目中,有一个文件core.yml,里面存储了core模块的一些基础配置,该文件里面的写法有properties的写法也有yaml的写法。例如:
···
scio.core.api.secrets: test,123456
scio.core.sms.appKey=1d5123123165db5
···
在eclipse编辑器中,经常会提示错误,也就是说yml文件中的语法有误。确实,scio.core.sms.appKey=1d5123123165db5
这段的写法是properties的写法。但是启动程序,没有任何错误报出,看着不爽,就想把错误取消,所以把scio.core.sms.appKey=1d5123123165db5
修改成scio.core.sms.appKey: "1d5123123165db5"
。启动项目,junit测试报错了,提示appKey不存在。
发现
发现junit测试错误,根据问题找到校验appKey的业务逻辑,发现${scio.core.sms.appKey}
获取到的appKey居然是带有引号的"1d5123123165db5"
。那就奇怪了,按照yaml的写法,双引号和单引号只是区分是否转义字符串中的特殊符号,不会把双引号到值里面去的。跟踪发现,core.yml的引用是使用了@PropertySource({"classpath:core.yml"})
进行解析的,通过查看@PropertySource
注解的解释,如下:Given a file app.properties containing the key/value pair testbean.name=myTestBean, the following @Configuration classuses @PropertySource to contribute app.properties to the Environment's set of PropertySources.
,由此可见,默认他是解析的properties文件,猜想,他并没有按照yaml的格式解析,若properties的解析也支持冒号分隔,那么把冒号后面的数据解析带有引号,也就能解释通了。
解析
-
@PropertySource 的注解中,有一个
factory
属性,可指定一个自定义的PropertySourceFactory
接口实现,用于解析指定的文件。默认的实现是DefaultPropertySourceFactory
,继续跟进,使用了PropertiesLoaderUtils.loadProperties
进行文件解析,所以默认就是使用Properties进行解析的。
扩展
-
CompositePropertySourceFactory 查看了
DefaultPropertySourceFactory
的解析方法后,发现其支持properties文件的解析,跟进properties的load方法发现在解析时,分隔符是=
或者:
,虽说可以解析简单的yml格式内容,但是无法支持真正的yaml语法,可以对DefaultPropertySourceFactory
进行扩展,支持两种格式混合解析。以下是主要代码
package com.scio.cloud.cloudconfig;
import java.io.IOException;
import java.util.Optional;
import java.util.Properties;
import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.support.DefaultPropertySourceFactory;
import org.springframework.core.io.support.EncodedResource;
/**
* CompositePropertySourceFactory support properties and yaml file
*
* @author Wang.ch
* @date 2019-03-22 09:30:15
*/
public class CompositePropertySourceFactory extends DefaultPropertySourceFactory {
@Override
public PropertySource<?> createPropertySource(String name, EncodedResource resource)
throws IOException {
String sourceName = Optional.ofNullable(name).orElse(resource.getResource().getFilename());
if (!resource.getResource().exists()) {
// return an empty Properties
return new PropertiesPropertySource(sourceName, new Properties());
} else if (sourceName.endsWith(".yml") || sourceName.endsWith(".yaml")) {
Properties propertiesFromYaml = loadYaml(resource);
return new PropertiesPropertySource(sourceName, propertiesFromYaml);
} else {
return super.createPropertySource(name, resource);
}
}
/**
* load yaml file to properties
*
* @param resource
* @return
* @throws IOException
*/
private Properties loadYaml(EncodedResource resource) throws IOException {
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
factory.setResources(resource.getResource());
factory.afterPropertiesSet();
return factory.getObject();
}
}
- @PropertySource
@PropertySource(
value = {"core.yml", "key.properties"},
factory = CompositePropertySourceFactory.class)