7

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

 3 years ago
source link: http://spring.hhui.top/spring-blog/2020/12/26/201226-SpringBoot系列PropertySource加载Yaml配置文件实例演示/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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

}

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

63YbqmU.jpg!mobile

从上面的截图可以看到,这个 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);
    }
}

JZrINnr.jpg!mobile

3. 小结

当我们希望加载自定义的配置文件时, @PropertySource 注解是一个非常好的选择(当然也可以借助多环境配置方案,指定 spring.profiles.active 的值,实现加载前缀为 application- 的配置文件,有兴趣的小伙伴可以查看我之前的博文)

请注意 @PropertySource 引入的配置文件不支持 yaml 文件,如需支持,可以参考本文中的实现方式,自定义一个yaml文件的 PropertySourceFactory

最后提一句,遇到问题千万不要放过,尽量迅速解决,不要留待以后,不然拖延症发作的话,这个时间可能就一直悬着了…

III. 其他

0. 项目

项目源码

系列博文

1. 一灰灰Blog

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

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

ri6Vj2M.png!mobile

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK