Do you know the five ways to read yml files?

Posted by bundyxc on Fri, 21 Jan 2022 13:12:16 +0100

Original: WeChat official account, welcome to share, reprint, please keep the source.

In the previous article, we analyzed the whole process of SpringBoot parsing yml configuration files from the perspective of source code. Today, let's talk about the actual combat and summarize the ways to read the contents of yml configuration files in addition to @ Value and @ ConfigurationProperties.

1,Environment

There is a class Environment in Spring, which can be considered as the running Environment of the current application. It inherits the PropertyResolver interface, so it can be used as a property parser. First create a yml file with the following attributes:

person:
  name: hydra
  gender: male
  age: 18

It is also very simple to use. It can be injected directly into the class to be used directly with @Autowired, and then its getProperty() method can be used to extract the corresponding value according to the property name.

@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);
    }
}

As can be seen from the above example, in addition to simple acquisition, the methods provided by the Environment can also type convert the retrieved attribute values and set the default values. Call the above interface, and the print results are as follows:

hydra
male
18
defaultValue

In addition to obtaining properties, it can also be used to judge the activated configuration file. Let's start with application Activate pro file in YML:

spring:
  profiles:
    active: pro

You can use the acceptsProfiles method to detect whether a configuration file is activated and loaded, or get all activated configuration files through the getActiveProfiles method. Test interface:

@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);
    }
}

Print results:

true
false
pro

2,YamlPropertiesFactoryBean

In Spring, you can also use YamlPropertiesFactoryBean to read the yml file of custom configuration, instead of being bound to application yml and other profiles it activates.

During use, you only need to set the storage path of the custom yml configuration file through the setResources() method, and then obtain the Properties object through the getObject() method. Later, you can obtain specific Properties through it. See an example below:

@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());
}

Viewing the running results, you can read the specified application 2 YML content:

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

However, there is a problem in such use, that is, the value of this property can only be obtained in the request of this interface. If another interface is written, the configuration file is not read by YamlPropertiesFactoryBean. Even if the previous method has read the yml file once, the value obtained by the second interface is still null. To test this process:

@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);
}

When the fcTest interface is called first and then the fcTest2 interface is called, the null value will be printed:

null
null

It is also very simple to solve this problem. It can be used in conjunction with PropertySourcesPlaceholderConfigurer, which implements the beanfactoryprocessor interface, that is, the implementation of a bean factory postprocessor, and can load the property values of the configuration file into a Properties file. The method of use is as follows:

@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;
    }
}

Call the previous interface again. The results are as follows. You can get application2 normally Attributes in YML:

susan
female

In addition to using YamlPropertiesFactoryBean to parse yml into Properties, we can also use YamlMapFactoryBean to parse yml into Map in a very similar way:

@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);
}

Print results:

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

3. Listening events

In the previous article on the principle, we know that SpringBoot loads and parses yml files by listening to events, so we can also follow this pattern to load custom configuration files.

First, define a class to implement the ApplicationListener interface, listen to the event type ApplicationEnvironmentPreparedEvent, and pass in the yml file name to be resolved in the constructor:

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

The onApplicationEvent() method of the interface needs to be implemented in the custom listener. It will be triggered when the ApplicationEnvironmentPreparedEvent event is heard:

@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();
    }
}

The above code mainly implements:

  • Obtain the current Environment. When the ApplicationEnvironmentPreparedEvent event is triggered, the Environment has been loaded and can be obtained through the event event
  • Load and parse configuration files through YamlPropertySourceLoader
  • Add the origintrackedmapppropertysource after parsing to the Environment

Modify the startup class and add this listener to the startup class:

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

Add a breakpoint before adding propertySource to the environment to check the changes of the environment:

After execution, you can see that the configuration file source has been added to the environment:

After startup, call the interface again to view the results:

susan
female

The value in the configuration file can be obtained correctly, indicating that the customized listener has taken effect.

4,SnakeYml

The above methods can be completed without introducing other dependencies in the Spring environment. The SnakeYml to be introduced next needs to introduce dependencies before use, but it can also be used separately from the Spring environment. Import dependent coordinates first:

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

Prepare a yml profile:

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

When using snake yml to parse yml, the most commonly used methods are load, LOADALL and loadAs. These three methods can load yml files or strings, and finally return the parsed objects. Let's start with the basic load method:

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

Run the above code to print the contents in the Map:

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

Next, let's take a look at the loadAll method, which can be used to load multiple documents connected with --- connectors in yml and modify the above yml files:

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

After adding the connector, try to use the load method for parsing. The error is as follows: another yml document is found and cannot be parsed normally:

At this time, modify the above code and use the loadAll method:

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);
    }
}

The results are as follows:

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

You can see that the loadAll method returns an iteration of an object. Each object in the object corresponds to a document in the yml. The modified yml file is parsed into two independent maps.

Next, let's take a look at the loadAs method, which can specify the type in the yml parsing process and directly encapsulate it into an object. We directly reuse the above snake 1 yml, before parsing, create two entity class objects to receive:

@Data
public class Person {
    SinglePerson person1;
    SinglePerson person2;
}

@Data
public class SinglePerson {
    String name;
    String gender;
}

Next, use the loadAs method to load the yml. Note that the second parameter of the method is the entity type used to encapsulate the yml.

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

View execution results:

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

In fact, if you want to encapsulate yml as an entity object, you can use another method. When creating a Yaml object, pass in a constructor object of the specified entity class, and then directly call the load method to achieve:

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());
}

The execution result is the same as above:

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

SnakeYml actually realizes many functions. I won't list them one by one here. Interested partners can check the documents themselves. If you read the source code after reading the last article, you will find that in fact, at the bottom of SpringBoot, you also use SnakeYml to parse yml.

5,jackson-dataformat-yaml

jackson is used to handle json more often than others. In fact, it can also be used to handle yml. Before using it, you need to introduce dependencies:

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

It is also very easy to read yml using jackson. The common ObjectMapper is used here. Specify YAML factory when creating ObjectMapper object, and then simply map yml to entity:

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());
}

Operation results:

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

If you want to generate yml files, you can call the writeValue method of ObjectMapper to implement:

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);
}

Looking at the generated yml file, you can see that jackson strictly added quotation marks to the string type and added yml link characters at the beginning of the document. As for other complex functions of jackson reading and writing yml, you can explore and use them yourself in your work.

summary

This article introduces five ways to read yml configuration files. The first three depend on the Spring environment, while snake yml and Jackson can be used independently of the environment. It can be said that they are complementary to the use of @ Value and @ ConfigurationProperties annotations. The usage scenarios of these methods are different, and each has its own advantages, and each has some special usage. In our work, we should select one scheme or use multiple combinations according to the specific purpose.

Well, I hope this actual battle can help you. I'm Hydra. See you next time.

The author introduces a public official account for code sharing, which is interesting, deep and direct, and talks with you about technology. Personal wechat DrHydra9, welcome to add friends for further communication.

Topics: Java Spring Spring Boot Back-end