Customized yml configuration file and external deployment

 

Background: There was a small project that needed a backend, and I took on the title of being a Java geek. I was obsessed with Amway spring boot by a man, so (forced to have no choice) I started to learn and develop at the same time... It's really delicious. There is a lot of information and it is very useful! ! !

The power plant project uses a real-time database developed by the company itself. The backend involves a lot of measurement point information that needs to be stored in configuration files (don’t ask me why it’s not a relational database), and I hope it can be easily modified during deployment. Considering that there is a lot of content, it is not suitable to put it in application-pro.yml, so I added point.yml. It's not that the on-site measurement point information needs to be changed because it changes, but more because I suddenly slap my head and realize that I wrote it wrong because my hands were shaking?

First of all, because I accidentally became a xxx.yml player, I used it well, but I couldn't go back to xxx.properties. It is said that the official does not support using the annotation @PropertySource("classpath:xxx.properties" like loading the xxx.properties configuration file. ) to load the yml configuration file. What I want to talk about here is the method of loading the custom yml file.

Take a look at the official description

For the method of loading custom xxx.properties files, please refer to this article:

Properties configuration analysis in springBoot

Note: When I was looking for information on multi-data source configuration, I was very frustrated because of the difference in spring boot versions corresponding to the information. Please be sure to note that the version I am using is:

spring boot 2.13

2. Load custom yml file

There is a lot of information on spring boot, so much that it is very easy to solve the problem without using your brain. After I calmed down after completing the project, I felt that I should verify it. After all, the slap in the face is to have a good reputation in the future.

2.1. Use @PropertiesSource annotation to read yml configuration file - simple version

According to the official announcement given above, this path is not feasible. Because I don’t see the version number corresponding to the document, let’s try it:

# Configuration file point.yml 
id: 2233 
name: Ellie

(Uh, why is this kind of information called point!

// 配置对应的config类
@Data
@Configuration
@PropertySource(value = {"classpath:point.yml"})
@ConfigurationProperties()
public class TestPoint {
    private int id;
    private String name;
    @Override
    public String toString() {
        return "TestPoint{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}

I just made a controller for testing.

@RestController
public class TestConfigController {
    @Resource
    TestPoint testPoint;
    @ApiOperation("测试 配置文件")
    @RequestMapping(value = "/config")
    public ResultBean<String> testConfig() {
        return ResultBeanUtil.makeOkResp(testPoint.toString());
    }
}

Get started with postman

All good!

So if you just want to read such simple information, you can directly use the annotation @PropertiesSource. I don’t know what the official uncertainty impact is.

2.2. Use @PropertiesSource annotation to read yml configuration file - not a simple version?

Add a list <basic type> and take a look.

# point.yml
id: 2233
name: Ellie
cards:
  - XD02101263
  - ZY8965
  - GX0009
 
 // 配置类
 @Data
@Configuration
@PropertySource(value = {"classpath:point.yml"})
@ConfigurationProperties()
public class TestPoint {
    private int id;
    private String name;
    private List<String> cards;
    @Override
    public String toString() {
        return "TestPoint{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", cards=" + cards +
                '}';
    }
}

Failed to show off, no more. It is not possible to use the @Value("id") annotation, because this annotation is used to match the situation where the variable name is inconsistent with the configuration file.

According to what other blogs have said (I have only been coding for a month and haven’t looked at the principles in depth, so I have to say: whatever the boss says is what it says), it’s because the @PropertySource annotation can only load the yml configuration file, but cannot expose its configuration information. For spring environment, it needs to be exposed manually. The method is to load the following beans when the application starts.

@Bean
	public static PropertySourcesPlaceholderConfigurer properties() {
		PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer();
		YamlPropertiesFactoryBean yaml = new YamlPropertiesFactoryBean();
		yaml.setResources(new ClassPathResource("point.yml"));
		configurer.setProperties(yaml.getObject());
		return configurer;
	}

Being lazy, I threw it directly into the .java file where the main function is located.

2.3. Add prefix feasible version

After all, I am so clever (brainless analysis!), so I added a prefix. The name of the prefix can be chosen as you like, as long as it corresponds to the configuration class. I was just lazy and called it prefix.

# point.yml
prefix:
  id: 2233
  name: Ellie
  cards:
    - XD02101263
    - ZY8965
    - GX0009
 
 // config类
 @Data
@Configuration
@PropertySource(value = {"classpath:point.yml"})
@ConfigurationProperties(prefix = "prefix") 
public class TestPoint {
    private int id;
    private String name;
    private List<String> cards;
    @Override
    public String toString() {
        return "TestPoint{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", cards=" + cards +
                '}';
    }
}

It all ends well? Help:

I have no idea why this is possible. If anyone passes by, please help answer it! ! ! Kneel down and thank orz

By the way, out of curiosity, I tried the prefix plus yml separator---matching method mentioned in some blog posts. It felt like serious nonsense, and I couldn't actually read it. Reading List<class> is also possible.

# point.yml
prefix:
  id: 2233
  name: Ellie
  cards:
    - name: XD
      code: XD02101263
    - name: ZY
      code: ZY8965
    - name: GX
      code: GX0009
      
// config 类
@Data
@Configuration
@PropertySource(value = {"classpath:point.yml"})
@ConfigurationProperties(prefix = "prefix")
public class TestPoint {
    private int id;
    private String name;
    private List<Card> cards;
    @Override
    public String toString() {
        return "TestPoint{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", cards=" + cards + 
                '}'; 
    } 
} 
// There is no need to have any unique card class 
@Data 
public class Card { 
    private String name; 
    private String code; 
    @Override 
    public String toString() { 
        return "Card{" + 
                "name='" + name + '\'' + 
                ", code='" + code + '\'' + 
                '}'; 
    } 
}

In the process of searching for information, I also saw a unique way of writing, which is to solve the problem of reading and writing multi-layered nested yml. It has not been verified, because if I have a choice, I am not willing to write like this, but the way of writing is indeed very unique, hahaha! https:

3. External department

In fact, the configuration file is deployed outside the jar package to facilitate modification without repackaging.

3.1. External loading of spring boot core configuration file

If you want to load custom configuration files externally, you need to first understand spring's default file loading method.

The spring program will load the application.properties configuration file from the following paths according to priority:

  • /config directory in the current directory
  • Current directory
  • /config directory in classpath
  • classpath root directory

In idea, the classpath under the source code corresponds to src/main/resources very clearly. I don’t know where the packaged classpath is, so I decompressed the packaged jar package and looked under BOOT-INF\classes. Come to application.yml and point.yml. So if I want to overwrite the configuration file, I created a config folder in the same directory as the jar package, and modified the contents of the configuration file to see if the overwriting took effect.

Specific operations:

  • When packaging, application.yml and point.yml are packaged into jar (classpath) by default.
  • When deploying, create a config folder in the same directory as the jar package, modify the port number and point.yml content in application.yml, and see if the modification takes effect.

The modified point.yml file is as follows:

prefix:
  id: 2233
  name: FakeEllie
  cards:
    - name: NONE
      code: 00000001

Test results: The modification of the port number takes effect (the modification of application.yml takes effect), but the modified point.yml does not take effect.

After all, the custom configuration file was wishful thinking and hoped that spring boot would load point.yml according to the core file loading method. It was expected that it did not take effect, but the road was not blocked.

3.2. Add path in @PropertySource

When checking the information, I noticed that there is also this way of writing:

@Data
@Configuration
@PropertySource(value = {"file:config/point.yml"})
@ConfigurationProperties(prefix = "prefix")
public class TestPoint {
    private int id;
    private String name;
    private List<Card> cards;
    @Override
    public String toString() {
        return "TestPoint{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", cards=" + cards +
                '}';
    }
}

The file path is specified through file. Previously, classpath was used to specify the relative path of the resource. Miraculously, no error is reported in this method, but the read content is point.yml under classpath, not point.yml under config.

It seems that the prefix specified by @ConfigurationProperties(prefix = "prefix") is matched on the classpath. It probably has nothing to do with @PropertySource(value = {"file:config/point.yml"}). Wangzai milk is really delicious.

3.3. Add path through YamlPropertiesFactoryBean

Recall the above description, YamlPropertiesFactoryBean exposes the configuration file to the spring environment, you can consider using it to specify the file path.

Modify the bean and add new FileSystemResource("config/point.yml") to specify the configuration under the config folder.

@Bean
	public static PropertySourcesPlaceholderConfigurer properties() {
		PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer();
		YamlPropertiesFactoryBean yaml = new YamlPropertiesFactoryBean();
		yaml.setResources(new ClassPathResource("point.yml"), new FileSystemResource("config/point.yml"));
		configurer.setProperties(yaml.getObject());
		return configurer;
	}

Success? But it seems funny. However, the order in which configuration files are read is also explained. The one under the config folder has the final say.

In order to be more intuitive, I easily modified the point.yml configuration file in the config folder in the same directory of the jar package to ensure that the number of list elements is the same:

prefix:
  id: 2233
  name: FakeEllie
  cards:
    - name: NONE
      code: 00000001
    - name: NONE
      code: 00000002
    - name: NONE
      code: 00000003

Not funny anymore.

However, after changing to use @PropertySource(value = {"classpath:point.yml"}) on the configuration class, the return does not change. So YamlPropertiesFactoryBean exposes the configuration file to the spring environment. It should be added to the spring classpath, reading the default one first, and then reading the newly added one.

However, there is no way to use the default classpath configuration file without external configuration.

In addition, when adding configuration files through YamlPropertiesFactoryBean, you need to ensure that config/point.yml must exist. If you want to achieve the goal of reading point.yml in the default classpath when not performing external configuration, and reading config/ when performing external configuration. point.yml. Then you have to act like a hooligan.

@Data
@Configuration
@PropertySource(value = {"file:config/point.yml", "classpath:point.yml"}, ignoreResourceNotFound = true)
@ConfigurationProperties(prefix = "prefix")
public class TestPoint {
    private int id;
    private String name;
    private List<Card> cards;
    @Override
    public String toString() {
        return "TestPoint{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", cards=" + cards +
                '}';
    }
}

Point: Then! When no external configuration is performed, the content of config/point.yml is empty, or simply consistent with the content of point.yml under the classpath.

Children only do multiple choice questions, I want them all

Although it looks like an accident, I am so concerned about it, and I can’t contain my curiosity! It’s the return value that was put together just now.

Wanting to see if application.yml overwrites the list, the same thing happens, so I moved the corresponding content of the configuration class to application.yml. as follows:

// 配置类
@Data
@Configuration
@ConfigurationProperties(prefix = "prefix")
public class TestPoint {
    private int id;
    private String name;
    private List<Card> cards;
    @Override
    public String toString() {
        return "TestPoint{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", cards=" + cards +
                '}';
    }
}

The configuration class reads application.yml by default.

# classpath:application.yml
prefix:
  id: 2233
  name: Ellie
  cards:
    - name: XD
      code: XD02101263
    - name: ZY
      code: ZY8965
    - name: GX
      code: GX0009
#config/application.yml
prefix:
  id: 2233
  name: FakeEllie
  cards:
    - name: NONE
      code: 00000001

There is no splicing done! ! !

When looking at the impact of changing the order, the order of adding sources to YamlPropertiesFactoryBean was modified, and the returned results changed.

@Bean
	public static PropertySourcesPlaceholderConfigurer properties() {
		PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer();
		YamlPropertiesFactoryBean yaml = new YamlPropertiesFactoryBean();
		yaml.setResources(new FileSystemResource("config/point.yml"), new ClassPathResource("point.yml"));
		configurer.setProperties(yaml.getObject());
        configurer.setIgnoreResourceNotFound(true);
		return configurer;
	}

There is a simple way to specify the configuration file through command parameters at run time, which has a similar effect.

java -jar demo.jar --Dspring.config.location=point.yml

My principle is that if the code can solve it, don't leave it to people to solve it.

Although it didn't solve any problem, I learned by the way that the order of reading is the order of setResources. die

So the current conclusion is that this method is not suitable for configurations with lists and the number changes.

3.4. Customize yaml file resource loading class

In the annotation @PropertySource, there is an attribute factory mainly used to declare the class that parses the configuration file. This class must be an implementation of the PropertySourceFactory interface. Start here.

References:

Spring Boot custom loading yml implementation, with source code interpretation

DefaultPropertySourceFactory, the implementation of PropertySourceFactory, is called by default, so you can customize the factory to implement the PropertySourceFactory interface, or you can extend the DefaultPropertySourceFactory class. The effect of the two writing methods is the same, listed below.

Directly implement the PropertySourceFactory interface

public class YamlPropertyLoaderFactory implements PropertySourceFactory {
    @Override
    public PropertySource<?> createPropertySource(String name, EncodedResource encodedResource) throws IOException {
        List<PropertySource<?>> sources = name != null ? new YamlPropertySourceLoader().load(name, encodedResource.getResource()) : new YamlPropertySourceLoader().load(
                getNameForResource(encodedResource.getResource()), encodedResource.getResource());
        if (sources.size() == 0) {
            return null;
        }
        return sources.get(0);
    }
    private static String getNameForResource(Resource resource) {
        String name = resource.getDescription();
        if (!StringUtils.hasText(name)) {
            name = resource.getClass().getSimpleName() + "@" + System.identityHashCode(resource);
        }
        return name;
    }
}

ExtendDefaultPropertySourceFactory

 public class YamlPropertyLoaderFactory extends DefaultPropertySourceFactory {
    @Override
    public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
        if (resource == null) {
            return super.createPropertySource(name, resource);
        }
        List<PropertySource<?>> sources = new YamlPropertySourceLoader().load(resource.getResource().getFilename(), resource.getResource());
        if (sources.size() == 0) {
            return super.createPropertySource(name, resource);
        }
        return sources.get(0);
    }
}

It is recommended to use the second method.

If you use the factory method to implement it, there is no need for the weird operation of adding a prefix for normal reading.

How to use it:

@Data
@Configuration
@PropertySource(value = {"classpath:point.yml", "file:config/point.yml"}, factory = YamlPropertyLoaderFactory.class, ignoreResourceNotFound = true)
@ConfigurationProperties
public class TestPoint{
    private int id;
    private String name;
    private List<Card> cards;
    @Override
    public String toString() {
        return "TestPoint{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", cards=" + cards +
                '}';
    }
}
# config/point.yml
id: 2233
name: FakeEllie
cards:
  - name: NONE
    code: 00000001

When customizing the factory, when reading configuration files from multiple paths, there is also a sequence, which is the order specified by the value attribute in @PropertySource. This is different from using YamlPropertiesFactoryBean to expose resources to the spring environment. This will not happen before. The "splicing" effect appears, awesome~

Looking at the same problem with the goal of solving the problem and the goal of writing a clear article are really different paths of exploration. The number of words and the writing of the article to keep the flag are far beyond my original expectations, which is really good. I love it!

The above is my personal experience, I hope it can give you a reference, and I hope you can support me a lot.

Reprinting the custom yml configuration file and external deployment (IT technology) 1.
Background: There is a small project that requires a backend, and I took on the title of Java geek. I was attracted by the man's crazy Amway spring boot, so (I was forced to have no choice) ) started the road of learning and developing at the same time... It's really delicious, there are a lot of information, and it's super easy to use! ! !
Power plant
icon-default.png?t=MBR7http://www.qb5200.com/article/484770.html

Guess you like

Origin blog.csdn.net/heni6560/article/details/128465676