【基础系列】SpringBoot配置篇之PropertySource加载Yaml配置文件实例演示

文章目录
  1. I. 项目环境
    1. 1. 基本配置
    2. 2. 实例项目
  2. II. PropertySource原理分析
    1. 1. 源码定位
    2. 2. yaml文件支持
    3. 3. 小结
  3. III. 其他
    1. 0. 项目
    2. 1. 一灰灰Blog

在之前有介绍过借助注解@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

1
2
3
4
5
6
biz.token=mytoken
biz.appKey=asdf
biz.appVersion=1
biz.source=xxx.yyy

biz.uuid=${biz.token}#${biz.appKey}

接下来定义对应的配置类

1
2
3
4
5
6
7
8
9
10
11
@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项目不可获取的启动类

1
2
3
4
5
6
7
8
9
/**
* 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. 源码定位

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

1
2
3
4
5
public @interface PropertySource {
// ... 省略无关的属性

trueClass<? extends PropertySourceFactory> factory() default PropertySourceFactory.class;
}

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

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

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

1
2
3
4
5
6
7
8
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包含了我们指定的配置文件,直接单步进去,可以看到执行的时候下面这个

1
2
3
4
5
// 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)

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 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了

1
2
3
4
5
6
7
8
9
10
11
12
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的锅😂)

2. yaml文件支持

经过上面的一番操作,我们知道@ConfigurationProperties加载配置文件,主要是借助jdk的Properties#load方法来读取配置文件到容器内,那么若我们希望加载yaml配置文件,可以怎么搞呢?

因为SpringBoot是支持yaml配置文件的读取的,所以我们完全可以扩展一下,借助SpringBoot的工具类来实现配置文件加载,所以可以实现自定义的PropertySourceFactory

1
2
3
4
5
6
7
8
9
10
11
12
13
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);
}
}

然后再我们希望使用的地方,利用自定义的工厂类替换默认的即可

1
2
3
4
5
6
7
8
9
10
11
12
@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;
}

对应的配置文件如下

1
2
3
4
5
6
7
biz2:
yml:
type: 1
name: biz.yml.name
ary:
- a: hello
- b: world

最后实例验证一下

1
2
3
4
5
6
7
8
9
10
11
@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


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