SpringBoot解析指定Yaml配置文件

Posted 叫我二蛋

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringBoot解析指定Yaml配置文件相关的知识,希望对你有一定的参考价值。


再来个文章目录

文章目录

下面还有投票,帮忙投个票👍

前言

最近在看某个开源项目代码并准备参与其中,代码过了一遍后发现多个自定义的配置文件用来装载业务配置代替数据库查询,直接响应给前端,这里简单记录一下实现过程。

我们通常在SpringBoot项目中用配置文件属性时使用@ConfigurationProperties或@Value默认配置文件的属性值,也就是application.yml或者application.properties文件中的属性值。

但是不能全都往默认配置文件里堆的,本文利用@PropertySource和@ConfigurationProperties注解引用其它配置文件的属性值。

1、自定义配置文件

在resources下创建my.yaml文件,“-”用来表示数组类型,一定要注意空格

my:
  contents:
    - id: 12121
      name: nadasd
    - id: 3333
      name: vfffff

2、配置对象类

创建配置类对象,在类上添加@Component、@PropertySource、@ConfigurationProperties注解。

@Component是将该类交由spring管理,@PropertySource用来指定配置文件及解析Yaml格式,@ConfigurationProperties是将解析后的配置文件属性自动注入该类的属性。

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

@Component
@PropertySource(value = "classpath:my.yaml", factory = YamlPropertiesSourceFactory.class)
@ConfigurationProperties(prefix = "my")
public class MyProperties 

    private List<content> contents = new ArrayList<>();

    public List<content> getContents() 
        return contents;
    

    public void setContents(List<content> contents) 
        this.contents = contents;
    




class content 
    private String id;

    private String name;

    public String getId() 
        return id;
    

    public void setId(String id) 
        this.id = id;
    

    public String getName() 
        return name;
    

    public void setName(String name) 
        this.name = name;
    

@PropertySource注解是Spring用于加载配置文件,@PropertySource属性如下:

  • name:默认为空,不指定Spring自动生成
  • value:配置文件
  • ignoreResourceNotFound:没有找到配置文件是否忽略,默认false,4.0版本加入
  • encoding:配置文件编码格式,默认UTF-8 4.3版本才加入
  • factory:配置文件解析工厂,默认:PropertySourceFactory.class 4.3版本才加入,如果是之前的版本就需要手动注入配置文件解析Bean

Spring Boot 默认不支持@PropertySource读取yaml 文件,需要自定义PropertySourceFactory进行解析。

3、YamlPropertiesSourceFactory

创建YamlPropertiesSourceFactory类用来解析Yaml格式的文件。

import org.springframework.boot.env.YamlPropertySourceLoader;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.core.io.support.PropertySourceFactory;

import java.io.IOException;
import java.util.List;
import java.util.Optional;

public class YamlPropertiesSourceFactory implements PropertySourceFactory 

    @Override
    public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException 
        String resourceName = Optional.ofNullable(name).orElse(resource.getResource().getFilename());
        List<PropertySource<?>> yamlSources = new YamlPropertySourceLoader().load(resourceName, resource.getResource());
        return yamlSources.get(0);
    


Springboot 读取配置文件原理

参考技术A

Springboot 读取配置文件(application.yaml, application.properties)的过程发生在SpringApplication#prepareEnvironment() 阶段,而prepareEnvironment又属于整个Springboot 应用启动的非常前置阶段,因为Environment的准备是后续bean创建的基础。让我们来一探启动是的详细code。除去StopWatch这些code,可以发现prepareEnvironment 发生在SpringApplication#run 这在整个应用启动的多步实质性操作中几乎是第一步。

而prepareEnvironment中最重要的是通过触发listener(EventPublishingRunListener)来通过SimpleApplicationEventMulticaster#multicastEvent发出ApplicationEnvironmentPreparedEvent。

而SimpleApplicationEventMulticaster#multicastEvent的实现其实也很简单,找到相关的监听ApplicationEnvironmentPreparedEvent的listener,然后一个个的调用他们的Listener#onApplicationEvent(event)方法,而这其中就包括了处理configuration文件的listener。
在Springboot 2.4.0 之前这个处理configuration 文件的lister是ConfigFileApplicationListener,在2.4.0之后,处理configuration 文件的lister是EnvironmentPostProcessorApplicationListener,并且对configuration文件的加载做了较大的改变,导致一些行为可能出现了变化,这也就是下面要详细讲的内容。

Springboot 2.4.0之后,configuration 文件的load顺序按照优先级是如下顺序(序号大的会被小的覆盖):

和之前版本比较,整体的属性加载顺序并无调整,只有Application properties(14,15)这里有顺序的调整,具体调整为:

如果存在多个active的profiles,例如[Test, Dev], 那么对于同时存在两个profile 配置文件中的配置,后面的profile里的配置(Dev)会覆盖前面profile(Test)里配置的值。

前面讲了这么多,终于要引出Springboot 2.4之后配置文件加载的行为变化了。

考虑这样的情况,如果我想在跑Springboot test的时候指定特定的profile,那么可以在Test class中加入@ActiveProfile("Test")。 如果我的应用中存在ApplicationEnvironmentPreparedEvent的某个自定义listener中,会根据当前environment 设置profile,如env.addActiveProfile("Dev")。
当前就会有两个active profile,由于springboot-test会在调用application#run 前利用DefaultActiveProfilesResolver把@ActiveProfile注解定义的profile(Test)先加入了active的profile,等test run的时候 env.addActiveProfile("Dev") 又会把"Dev"也作为active profile 加入,这时候当前的active profile便为["Test", "Dev"]。

据上面介绍,后面的profile(Dev)对应的configuration 会覆盖前面的(Test)。可Springboot 2.4.0之前的版本为我们做了调整,让Test class中@ActiveProfile内定义的profile所对应的配置文件成为最高优先级。

刚才提到在Springboot 2.4.0 之前这个处理configuration 文件的lister是ConfigFileApplicationListener,我们
来看看ConfigFileApplicationListener的相关code。

查看initializeProfiles(),发现此时对profile的顺序做了调整,将activatedViaProperty (Test) 放在最后add,于是profile的顺序就变成了[Dev, Test]。

在profiles.poll()时原本profile的顺序已经倒了过来,已经变为[Dev, Test], 在load()方法中由于后置的Test profile,application-Test.yaml中的值最终生效了。

可是到了Springboot2.4.0之后,ConfigFileApplicationListener被deprecated了,取而代之的是EnvironmentPostProcessorApplicationListener,EnvironmentPostProcessorApplicationListener通过调用ConfigDataEnvironmentPostProcessor来完成configuration加载。
EnvironmentPostProcessorApplicationListener.java

ConfigDataEnvironmentPostProcessor.java

ConfigDataEnvironmentPostProcessor只是老老实实的set了active profile,并没有调换profile的顺序。最后调用定义在spring.factories中的resource loader class来load 配置文件。

YamlPropertySourceLoader.java

插一句,Springboot为我们提供了很好的yaml文件parse的code,当你需要解析yaml文件时不妨直接参考Springboot的YamlPropertySourceLoader

这样一旦应用升级到Springboot 2.4.0之后相同的test code会使用application-Dev.yaml中配置的值,造成了test结果的改变。
如果要解决这个问题,根据上面介绍的配置文件优先级顺序,可以在@SpringbootTest中设置properties 来作为最终的配置覆盖当前profile对应的配置。

了解一个框架很不容易,一个小小的变化都有可能造成应用的行为变化,唯有刨根问底,不断总结才是framework人解决一切问题的不变的方法论。

以上是关于SpringBoot解析指定Yaml配置文件的主要内容,如果未能解决你的问题,请参考以下文章

SpringBoot03:yaml配置解析

SpringBoot对于yaml的详细学习和三种属性赋值的实战详解

SpringBoot中yaml文件配置属性

Springboot 读取配置文件原理

SpringBoot--注入指定的配置文件

java面试之SpringBoot篇