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.