你会不会5种牛犇的yml文件读取方式?

简介: 我是小假 期待与你的下一次相遇 ~

 前言

除了烂大街的@Value@ConfigurationProperties外,还能够通过哪些方式,来读取yml配置文件的内容?


1、Environment

在Spring中有一个类Environment,它可以被认为是当前应用程序正在运行的环境,它继承了PropertyResolver接口,因此可以作为一个属性解析器使用。先创建一个yml文件,属性如下:

person:
  name: hydra
  gender: male
  age: 18

image.gif

使用起来也非常简单,直接使用@Autowired就可以注入到要使用的类中,然后调用它的getProperty()方法就可以根据属性名称取出对应的值了。

@RestController
public class EnvironmentController {
    @Autowired
    private Environment environment;
    @GetMapping("envTest")
    private void getEnv(){
        System.out.println(environment.getProperty("person.name"));
        System.out.println(environment.getProperty("person.gender"));
        Integer autoClose = environment
            .getProperty("person.age", Integer.class);
        System.out.println(autoClose);
        String defaultValue = environment
            .getProperty("person.other", String.class, "defaultValue");
        System.out.println(defaultValue);
    }
}

image.gif

在上面的例子中可以看到,除了简单的获取外,Environment提供的方法还可以对取出的属性值进行类型转换、以及默认值的设置,调用一下上面的接口,打印结果如下:

hydra
male
18
defaultValue

image.gif

除了获取属性外,还可以用来判断激活的配置文件,先在application.yml中激活pro文件:

spring:
  profiles:
    active: pro

image.gif

可以通过acceptsProfiles方法来检测某一个配置文件是否被激活加载,或者通过getActiveProfiles方法拿到所有被激活的配置文件。测试接口:

@GetMapping("getActiveEnv")
private void getActiveEnv(){
    System.out.println(environment.acceptsProfiles("pro"));
    System.out.println(environment.acceptsProfiles("dev"));
    String[] activeProfiles = environment.getActiveProfiles();
    for (String activeProfile : activeProfiles) {
        System.out.println(activeProfile);
    }
}

image.gif

打印结果:

true
false
pro

image.gif

2、YamlPropertiesFactoryBean

在Spring中还可以使用YamlPropertiesFactoryBean来读取自定义配置的yml文件,而不用再被拘束于application.yml及其激活的其他配置文件。

在使用过程中,只需要通过setResources()方法设置自定义yml配置文件的存储路径,再通过getObject()方法获取Properties对象,后续就可以通过它获取具体的属性,下面看一个例子:

@GetMapping("fcTest")
public void ymlProFctest(){
    YamlPropertiesFactoryBean yamlProFb = new YamlPropertiesFactoryBean();
    yamlProFb.setResources(new ClassPathResource("application2.yml"));
    Properties properties = yamlProFb.getObject();
    System.out.println(properties.get("person2.name"));
    System.out.println(properties.get("person2.gender"));
    System.out.println(properties.toString());
}

image.gif

查看运行结果,可以读取指定的application2.yml的内容:

fcant
female
{person2.age=18, person2.gender=female, person2.name=fcant}

image.gif

但是这样的使用中有一个问题,那就是只有在这个接口的请求中能够取到这个属性的值,如果再写一个接口,不使用YamlPropertiesFactoryBean读取配置文件,即使之前的方法已经读取过这个yml文件一次了,第二个接口取到的仍然还是空值。来对这个过程进行一下测试:

@Value("${person2.name:null}")
private String name;
@Value("${person2.gender:null}")
private String gender;
@GetMapping("fcTest2")
public void ymlProFctest2(){
    System.out.println(name);
    System.out.println(gender);
}

image.gif

先调用一次fcTest接口,再调用fcTest2接口时会打印null值:

null
null

image.gif

想要解决这个问题也很简单,可以配合PropertySourcesPlaceholderConfigurer使用,它实现了BeanFactoryPostProcessor接口,也就是一个bean工厂后置处理器的实现,可以将配置文件的属性值加载到一个Properties文件中。使用方法如下:

@Configuration
public class PropertyConfig {
    @Bean
    public static PropertySourcesPlaceholderConfigurer placeholderConfigurer() {
        PropertySourcesPlaceholderConfigurer configurer 
            = new PropertySourcesPlaceholderConfigurer();
        YamlPropertiesFactoryBean yamlProFb 
            = new YamlPropertiesFactoryBean();
        yamlProFb.setResources(new ClassPathResource("application2.yml"));
        configurer.setProperties(yamlProFb.getObject());
        return configurer;
    }
}

image.gif

再次调用之前的接口,结果如下,可以正常的取到application2.yml中的属性:

fcant
female

image.gif

除了使用YamlPropertiesFactoryBean将yml解析成Properties外,其实还可以使用YamlMapFactoryBean解析yml成为Map,使用方法非常类似:

@GetMapping("fcMapTest")
public void ymlMapFctest(){
    YamlMapFactoryBean yamlMapFb = new YamlMapFactoryBean();
    yamlMapFb.setResources(new ClassPathResource("application2.yml"));
    Map<String, Object> map = yamlMapFb.getObject();
    System.out.println(map);
}

image.gif

打印结果:

{person2={name=fcant, gender=female, age=18}}

image.gif

3、监听事件

SpringBoot是通过监听事件的方式来加载和解析的yml文件,那么也可以仿照这个模式,来加载自定义的配置文件。

首先,定义一个类实现ApplicationListener接口,监听的事件类型为ApplicationEnvironmentPreparedEvent,并在构造方法中传入要解析的yml文件名:

public class YmlListener implements 
    ApplicationListener<ApplicationEnvironmentPreparedEvent> {
    private String ymlFilePath;
    public YmlListener(String ymlFilePath){
        this.ymlFilePath = ymlFilePath;
    }
    //...
}

image.gif

自定义的监听器中需要实现接口的onApplicationEvent()方法,当监听到ApplicationEnvironmentPreparedEvent事件时会被触发:

@Override
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
    ConfigurableEnvironment environment = event.getEnvironment();
    ResourceLoader loader = new DefaultResourceLoader();
    YamlPropertySourceLoader ymlLoader = new YamlPropertySourceLoader();
    try {
        List<PropertySource<?>> sourceList = ymlLoader
            .load(ymlFilePath, loader.getResource(ymlFilePath));
        for (PropertySource<?> propertySource : sourceList) {
            environment.getPropertySources().addLast(propertySource);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

image.gif

上面的代码中,主要实现了:

  • 获取当前环境Environment,当ApplicationEnvironmentPreparedEvent事件被触发时,已经完成了Environment的装载,并且能够通过event事件获取
  • 通过YamlPropertySourceLoader加载、解析配置文件
  • 将解析完成后的OriginTrackedMapPropertySource添加到Environment

修改启动类,在启动类中加入这个监听器:

public static void main(String[] args) {
    SpringApplication application = new SpringApplication(MyApplication.class);
    application.addListeners(new YmlListener("classpath:/application2.yml"));
    application.run(args);
}

image.gif

在向environment中添加propertySource前加一个断点,查看环境的变化。

执行完成后,可以看到配置文件源已经被添加到了环境中。

启动完成后再调用一下接口,查看结果:

Fcant
female

image.gif

能够正确的取到配置文件中的值,说明自定义的监听器已经生效。

4、SnakeYml

前面介绍的几种方式,在Spring环境下无需引入其他依赖就可以完成的,接下来要介绍的SnakeYml在使用前需要引入依赖,但是同时也可以脱离Spring环境单独使用。先引入依赖坐标:

<dependency>
    <groupId>org.yaml</groupId>
    <artifactId>snakeyaml</artifactId>
    <version>1.23</version>
</dependency>

image.gif

准备一个yml配置文件:

person1:
  name: hydra
  gender: male
person2:
  name: fcant
  gender: female

image.gif

在使用SnakeYml解析yml时,最常使用的就是loadloadlAllloadAs方法,这三个方法可以加载yml文件或字符串,最后返回解析后的对象。先从基础的load方法开始演示:

public void test1(){
    Yaml yaml=new Yaml();
    Map<String, Object> map =
            yaml.load(getClass().getClassLoader()
                    .getResourceAsStream("snake1.yml"));
    System.out.println(map);
}

image.gif

运行上面的代码,打印Map中的内容:

{person1={name=hydra, gender=male}, person2={name=fcant, gender=female}}

image.gif

接下来看一下loadAll方法,它可以用来加载yml中使用—-连接符连接的多个文档,将上面的yml文件进行修改:

person1:
  name: hydra
  gender: male
---
person2:
  name: fcant
  gender: female

image.gif

在添加了连接符后,尝试再使用load方法进行解析,报错显示发现了另一段yml文档从而无法正常解析。

这时候修改上面的代码,使用loadAll方法:

public void test2(){
    Yaml yaml=new Yaml();
    Iterable<Object> objects = 
        yaml.loadAll(getClass().getClassLoader()
            .getResourceAsStream("snake2.yml"));
    for (Object object : objects) {
        System.out.println(object);
    }
}

image.gif

执行结果如下:

{person1={name=hydra, gender=male}}
{person2={name=fcant, gender=female}}

image.gif

可以看到,loadAll方法返回的是一个对象的迭代,里面的每个对象对应yml中的一段文档,修改后的yml文件就被解析成了两个独立的Map。

接下来再来看一下loadAs方法,它可以在yml解析过程中指定类型,直接封装成一个对象。直接复用上面的snake1.yml,在解析前先创建两个实体类对象用于接收:

@Data
public class Person {
    SinglePerson person1;
    SinglePerson person2;
}
@Data
public class SinglePerson {
    String name;
    String gender;
}

image.gif

下面使用loadAs方法加载yml,注意方法的第二个参数,就是用于封装yml的实体类型。

public void test3(){
    Yaml yaml=new Yaml();
    Person person = 
        yaml.loadAs(getClass().getClassLoader().
            getResourceAsStream("snake1.yml"), Person.class);
    System.out.println(person.toString());
}

image.gif

查看执行结果:

Person(person1=SinglePerson(name=hydra, gender=male), person2=SinglePerson(name=fcant, gender=female))

image.gif

实际上,如果想要将yml封装成实体对象,也可以使用另一种方法。在创建Yaml对象的时候,传入一个指定实体类的构造器对象,然后直接调用load方法就可以实现:

public void test4(){
    Yaml yaml=new Yaml(new Constructor(Person.class));
    Person person = yaml.load(getClass().getClassLoader().
            getResourceAsStream("snake1.yml"));
    System.out.println(person.toString());
}

image.gif

执行结果与上面相同:

Person(person1=SinglePerson(name=hydra, gender=male), person2=SinglePerson(name=fcant, gender=female))

image.gif

SnakeYml其实实现了非常多的功能,这里就不一一列举了,有兴趣的小伙伴可以自己查看一下文档。其实在SpringBoot的底层,也是借助了SnakeYml来进行的yml的解析操作。

5、jackson-dataformat-yaml

相比大家平常用jackson比较多的场景是用它来处理json,其实它也可以用来处理yml,使用前需要引入依赖:

<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-yaml</artifactId>
    <version>2.12.3</version>
</dependency>

image.gif

使用jackson读取yml也非常简单,这里用到了常用的ObjectMapper,在创建ObjectMapper对象时指定使用YAML工厂,之后就可以简单的将yml映射到实体:

public void read() throws IOException {
    ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory());
    InputStream input =
        new FileInputStream("F:\\Work\\yml\\src\\main\\resources\\snake1.yml");
    Person person = objectMapper.readValue(input, Person.class);
    System.out.println(person.toString());
}

image.gif

运行结果:

Person(person1=SinglePerson(name=hydra, gender=male), person2=SinglePerson(name=fcant, gender=female))

image.gif

如果想要生成yml文件的话,可以调用ObjectMapperwriteValue方法实现:

public void write() throws IOException {
    Map<String,Object> map=new HashMap<>();
    SinglePerson person1 = new SinglePerson("Trunks", "male");
    SinglePerson person2 = new SinglePerson("Goten", "male");
    Person person=new Person(person1,person2);
    map.put("person",person);
    ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory());
    objectMapper
            .writeValue(new File("F:\\Work\\yml\\src\\main\\resources\\jackson-gen.yml"),map);
}

image.gif

查看生成的yml文件,可以看到jackson对字符串类型严格的添加了引号,还在文档的开头添加了yml的链接符。至于其他jackson读写yml的复杂功能,可以在工作中自己去探索使用。

总结

这里介绍了5种读取yml配置文件的方式,前3种依赖于Spring环境,而SnakeYmlJackson则可以脱离环境独立使用,可以说它们是对@Value@ConfigurationProperties注解使用的补充。这几种方法的使用场景不同,也各有各的有优点,各自具备一些特殊的用法,而在工作中更多情况下,要根据具体的用途进行一种方案的选取或多种的搭配使用。

如果小假的内容对你有帮助,请点赞评论收藏。创作不易,大家的支持就是我坚持下去的动力!

image.gif 编辑

相关文章
|
9月前
|
人工智能 Java 测试技术
Spring Boot 集成 JUnit 单元测试
本文介绍了在Spring Boot中使用JUnit 5进行单元测试的常用方法与技巧,包括添加依赖、编写测试类、使用@SpringBootTest参数、自动装配测试模块(如JSON、MVC、WebFlux、JDBC等),以及@MockBean和@SpyBean的应用。内容实用,适合Java开发者参考学习。
983 0
|
SQL 关系型数据库 数据库
学习分布式事务Seata看这一篇就够了,建议收藏
学习分布式事务Seata看这一篇就够了,建议收藏
22038 2
|
9月前
|
JavaScript Java API
简单来说一说 @Value 注解
我是小假 期待与你的下一次相遇 ~
592 2
|
10月前
|
人工智能 Java API
Spring AI 实战|Spring AI入门之DeepSeek调用
本文介绍了Spring AI框架如何帮助Java开发者轻松集成和使用大模型API。文章从Spring AI的初探开始,探讨了其核心能力及应用场景,包括手动与自动发起请求、流式响应实现打字机效果,以及兼容不同AI服务(如DeepSeek、通义千问)的方法。同时,还详细讲解了如何在生产环境中添加监控以优化性能和成本管理。通过Spring AI,开发者可以简化大模型调用流程,降低复杂度,为企业智能应用开发提供强大支持。最后,文章展望了Spring AI在未来AI时代的重要作用,鼓励开发者积极拥抱这一技术变革。
3353 71
Spring AI 实战|Spring AI入门之DeepSeek调用
|
9月前
|
Prometheus 监控 Cloud Native
|
10月前
|
人工智能 弹性计算 JSON
MCP进阶:一键批量搞定MCP工具部署
本文介绍了一种基于阿里云计算巢的一站式MCP工具解决方案,解决了传统MCP工具集成中的效率低下、调用方式割裂和动态管理困难等问题。方案通过标准化协议实现多MCP工具批量部署,提高云资源利用率,并支持OpenAPI与MCP双通道调用,使主流AI助手如Dify、Cherry Studio等无缝接入。内容涵盖背景、原理剖析、部署使用实战及问题排查,最后强调MCP协议作为“通用语言”连接数字与物理世界的重要性。
1871 62
MCP进阶:一键批量搞定MCP工具部署
|
9月前
|
JSON 安全 Java
说一说 Java 反序列化漏洞
我是小假 期待与你的下一次相遇 ~
240 1
说一说 Java 反序列化漏洞
|
9月前
|
消息中间件 缓存 固态存储
说一说 Java 中的内存映射(mmap)
我是小假 期待与你的下一次相遇 ~
287 1
说一说 Java 中的内存映射(mmap)
|
9月前
|
存储 Java Linux
详细地说一说零拷贝
我是小假 期待与你的下一次相遇 ~
502 1
详细地说一说零拷贝
|
9月前
|
JSON Java 数据库连接
IDEA的插件大总汇 (让你的工作效率大大提高!)
我是小假 期待与你的下一次相遇 ~
2792 5