7.PropertySource加载Yaml配置文件实例演示
在之前有介绍过借助注解@PropertySource
来引入自定义的配置文件,在当时遇到抛出了一个问题,通过这个注解可以正确获取到.properties
文件的配置信息,但是yaml
文件却读取不到,最近又碰到这个问题,正好把之前挖的坑填上;本文将主要定位一下,为啥yml文件读取不了,又可以如何处理
如对之前博文有兴趣的小伙伴,可以查看: 180921-SpringBoot基础篇配置信息之自定义配置指定与配置内引用
I. 项目环境
1. 基本配置
本文后续的源码定位以及实例演示都是基于SpringBoot 2.2.1.RELEASE
进行,如需复现本文中的case,请确保环境一致
- IDEA
- MAVEN
- SpringBoot 2.2.1.RELEASE
- JDK1.8
2. 实例项目
创建一个SpringBoot项目,用于后续的演示,首先创建一个配置文件biz.properties
biz.token=mytoken
biz.appKey=asdf
biz.appVersion=1
biz.source=xxx.yyy
biz.uuid=${biz.token}#${biz.appKey}
接下来定义对应的配置类
@Data
@Configuration
@PropertySource({"classpath:biz.properties"})
@ConfigurationProperties(prefix = "biz")
public class OtherProperBean {
private String token;
private String appKey;
private Integer appVersion;
private String source;
private String uuid;
}
最后补上SpringBoot项目不可获取的启动类
/**
* Created by @author yihui in 14:08 18/9/19.
*/
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
}
II. PropertySource原理分析
想要定位为啥@PropertySource
注解只会获取到properties
文件的配置,而不能获取yaml
文件配置信息,最直接的办法当然是直接撸源码(实际上最简单的办法直接借助搜索引擎,看一下有没有哪位大佬有过相关分享,如果不是为了写本文,我可是完全没想开撸,毕竟从提出这个问题到现在回复,也过了两年多了😭...)
1. 源码定位
那么这个源码可以怎么定位分析呢,先直接进入这个注解瞅一下
public @interface PropertySource {
// ... 省略无关的属性
Class<? extends PropertySourceFactory> factory() default PropertySourceFactory.class;
}
请注意上面的特意留出来的PropertySourceFactory
, 从命名上来看,大致就能感觉这个工厂类与属性有关了,主要就是为了创建PropertySource
对象
它就比较有意思了,如果没有猜错的话,配置文件加载到Spring容器之后,多半就会与PropertySource
关联起来了(所以说好的命名可以省很多注释说明)
接下来看一下这个工厂类的默认实现DefaultPropertySourceFactory
,源码很简单
public class DefaultPropertySourceFactory implements PropertySourceFactory {
@Override
public PropertySource<?> createPropertySource(@Nullable String name, EncodedResource resource) throws IOException {
return (name != null ? new ResourcePropertySource(name, resource) : new ResourcePropertySource(resource));
}
}
在这里我们打个断点,确认一下会发生什么神器的事情
从上面的截图可以看到,这个EncodedResource
包含了我们指定的配置文件,直接单步进去,可以看到执行的时候下面这个
// org.springframework.core.io.support.ResourcePropertySource#ResourcePropertySource(org.springframework.core.io.support.EncodedResource)
public ResourcePropertySource(EncodedResource resource) throws IOException {
super(getNameForResource(resource.getResource()), PropertiesLoaderUtils.loadProperties(resource));
this.resourceName = null;
}
请注意,核心代码不是super()
这个构造方法,而是传参的PropertiesLoaderUtils.loadProperties(resource)
上面这一行调用,就是实现具体的从配置文件中获取配置信息
下面是具体的实现(摘抄有用的部分逻辑)
// org.springframework.core.io.support.PropertiesLoaderUtils
public static Properties loadProperties(EncodedResource resource) throws IOException {
Properties props = new Properties();
fillProperties(props, resource);
return props;
}
public static void fillProperties(Properties props, EncodedResource resource)
throws IOException {
// 属性填充,注意DefaultPropertiesPersister
fillProperties(props, resource, new DefaultPropertiesPersister());
}
static void fillProperties(Properties props, EncodedResource resource, PropertiesPersister persister)
throws IOException {
...
try {
String filename = resource.getResource().getFilename();
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);
}
}
...
}
配置信息的读取,最终依靠的就是org.springframework.util.DefaultPropertiesPersister#load()
,到这里我们基本上就找到了从配置文件中读取配置的“幕后黑手”,直接看一下它的实现逻辑就能知道为啥不支持yaml了
public class DefaultPropertiesPersister implements PropertiesPersister {
@Override
public void load(Properties props, InputStream is) throws IOException {
props.load(is);
}
@Override
public void load(Properties props, Reader reader) throws IOException {
props.load(reader);
}
}
直接进入看到源码,非常简单直观的实现方式了,直接使用jdk的java.util.Properties#load(java.io.InputStream)
来读取配置文件,所以真相已经大白了(原来都是jdk的锅😂)
2. yaml文件支持
经过上面的一番操作,我们知道@ConfigurationProperties
加载配置文件,主要是借助jdk的Properties#load
方法来读取配置文件到容器内,那么若我们希望加载yaml配置文件,可以怎么搞呢?
因为SpringBoot是支持yaml配置文件的读取的,所以我们完全可以扩展一下,借助SpringBoot的工具类来实现配置文件加载,所以可以实现自定义的PropertySourceFactory
public class YamlSourceFactory extends DefaultPropertySourceFactory {
@Override
public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
if (resource == null) {
return super.createPropertySource(name, resource);
}
// 这里使用Yaml配置加载类来读取yml文件信息
List<PropertySource<?>> sources = new YamlPropertySourceLoader().load(resource.getResource().getFilename(), resource.getResource());
return sources.get(0);
}
}
然后再我们希望使用的地方,利用自定义的工厂类替换默认的即可
@Data
@Configuration
@PropertySource(value = {"classpath:biz2.yml"}, factory = YamlSourceFactory.class)
@ConfigurationProperties(prefix = "biz2.yml")
public class YmlProperties {
private Integer type;
private String name;
private List<Map<String, String>> ary;
}
对应的配置文件如下
biz2:
yml:
type: 1
name: biz.yml.name
ary:
- a: hello
- b: world
最后实例验证一下
@SpringBootApplication
public class Application {
public Application(YmlProperties ymlProperties) {
System.out.println(ymlProperties);
}
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
}
3. 小结
当我们希望加载自定义的配置文件时,@PropertySource
注解是一个非常好的选择(当然也可以借助多环境配置方案,指定spring.profiles.active
的值,实现加载前缀为application-
的配置文件,有兴趣的小伙伴可以查看我之前的博文)
请注意@PropertySource
引入的配置文件不支持yaml
文件,如需支持,可以参考本文中的实现方式,自定义一个yaml文件的PropertySourceFactory
最后提一句,遇到问题千万不要放过,尽量迅速解决,不要留待以后,不然拖延症发作的话,这个时间可能就一直悬着了...
III. 其他
0. 项目
项目源码
- 工程: https://github.com/liuyueyi/spring-boot-demo
- 源码: https://github.com/liuyueyi/spring-boot-demo/tree/master/spring-boot/000-properties
系列博文