【基础系列】SpringBoot配置篇之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 文件配置信息,最直接的办法当然是直接撸源码(实际上最简单的办法直接借助搜索引擎,看一下有没有哪位大佬有过相关分享,如果不是为了写本文,我可是完全没想开撸,毕竟从提出这个问题到现在回复,也过了两年多了:sob:…)

1. 源码定位

那么这个源码可以怎么定位分析呢,先直接进入这个注解瞅一下

public @interface PropertySource {
// ... 省略无关的属性
trueClass<? extends PropertySourceFactory> factory() default PropertySourceFactory.class;
}

请注意上面的特意留出来的 PropertySourceFactory , 从命名上来看,大致就能感觉这个工厂类与属性有关了,主要就是为了创建 PropertySource 对象

它就比较有意思了,如果没有猜错的话,配置文件加载到Spring容器之后,多半就会与 PropertySource 关联起来了(所以说好的命名可以省很多注释说明)

接下来看一下这个工厂类的默认实现 DefaultPropertySourceFactory ,源码很简单

public class DefaultPropertySourceFactory implements PropertySourceFactory {
true@Override
truepublic PropertySource<?> createPropertySource(@Nullable String name, EncodedResource resource) throws IOException {
truetruereturn (name != null ? new ResourcePropertySource(name, resource) : new ResourcePropertySource(resource));
true}
}

在这里我们打个断点,确认一下会发生什么神器的事情

从上面的截图可以看到,这个 EncodedResource 包含了我们指定的配置文件,直接单步进去,可以看到执行的时候下面这个

// org.springframework.core.io.support.ResourcePropertySource#ResourcePropertySource(org.springframework.core.io.support.EncodedResource)
public ResourcePropertySource(EncodedResource resource) throws IOException {
truetruesuper(getNameForResource(resource.getResource()), PropertiesLoaderUtils.loadProperties(resource));
truetruethis.resourceName = null;
}

请注意,核心代码不是 super() 这个构造方法,而是传参的 PropertiesLoaderUtils.loadProperties(resource)

上面这一行调用,就是实现具体的从配置文件中获取配置信息

下面是具体的实现(摘抄有用的部分逻辑)

// org.springframework.core.io.support.PropertiesLoaderUtils
public static Properties loadProperties(EncodedResource resource) throws IOException {
trueProperties props = new Properties();
truefillProperties(props, resource);
truereturn props;
}
public static void fillProperties(Properties props, EncodedResource resource)
throws IOException {
// 属性填充,注意DefaultPropertiesPersister
truefillProperties(props, resource, new DefaultPropertiesPersister());
}
static void fillProperties(Properties props, EncodedResource resource, PropertiesPersister persister)
throws IOException {
...
truetry {
truetrueString filename = resource.getResource().getFilename();
truetrueif (filename != null && filename.endsWith(XML_FILE_EXTENSION)) {
truetruetruestream = resource.getInputStream();
truetruetrue// 这个是关键
truetruetruepersister.loadFromXml(props, stream);
truetrue}
truetrueelse if (resource.requiresReader()) {
truetruetruereader = resource.getReader();
truetruetrue// 关键调用
truetruetruepersister.load(props, reader);
truetrue}
truetrueelse {
truetruetruestream = resource.getInputStream();
truetruetrue// 关键调用
truetruetruepersister.load(props, stream);
truetrue}
true}
true...
}

配置信息的读取,最终依靠的就是 org.springframework.util.DefaultPropertiesPersister#load() ,到这里我们基本上就找到了从配置文件中读取配置的“幕后黑手”,直接看一下它的实现逻辑就能知道为啥不支持yaml了

public class DefaultPropertiesPersister implements PropertiesPersister {
true@Override
truepublic void load(Properties props, InputStream is) throws IOException {
truetrueprops.load(is);
true}
true@Override
truepublic void load(Properties props, Reader reader) throws IOException {
truetrueprops.load(reader);
true}
}

直接进入看到源码,非常简单直观的实现方式了,直接使用jdk的 java.util.Properties#load(java.io.InputStream) 来读取配置文件,所以真相已经大白了(原来都是jdk的锅:joy:)

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. 项目

项目源码

系列博文

1. 一灰灰Blog

尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激

下面一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛

打赏 如果觉得我的文章对您有帮助,请随意打赏。

一灰灰Blog
我还没有学会写个人说明!
上一篇

kubernetes pod为什么需要pause容器?

你也可能喜欢

评论已经被关闭。

插入图片