Statement: if there are any mistakes, please point out!
Make a note of some of the problems encountered when using spring boot configuration, although this problem is caused by lack of knowledge.
Make a note to avoid forgetting again after a period of time, just in case.
If you can help the comrades who meet the same problems in your blog, it's excellent!
The external configuration of SpringBoot mainly refers to some configuration files that are usually used. Because these configurations are not hard coded, they are placed in configuration files, so they are relatively external configurations
The online document of the official external configuration of SpringBoot Externalized Configuration
Primary usage
Spring boot provides great convenience for Configuration. Just write a Yaml file or Properties file, write our Configuration information according to its specified format, and then write a corresponding Java class. Use the annotation @ ConfigurationProperties and @ Configuration, or @ Configuration and @ Value to map the Configuration Value to We configure classes or JavaBean s.
There are the following Java configuration classes
@Configuration @ConfigurationProperties(prefix="spring.server") public class AppConfig{ private String name; private String port; public void setName(String name){ this.name = name; } public void setPort(String port){ this.port = port; } }
The following configuration file,
Yaml format profile
# application.yml spring: server: name: spring-app port: 9527
Properties format profile
# application.properties spring.server.name=spring-app spring.server.port=9237
When using @ ConfigurationProperties, there must be Setter method, which is used to bind
See org.springframework.boot.context.properties.configurationpropertiesbindingpostprocessor? Postprocessbeforeinitialization, a BeanPostProcessor
In this way, in spring boot, we can inject the AppConfig Bean into other beans to use our configuration.
The above basically meets the needs in development. Most of our configurations are simple, usually numeric and string.
But everything can't be absolute.
Advanced Usage
The following configuration references This is
Array/List
If there are the following requirements, the application only has open access to a few limited IPS, and then we want to write these licensed IP addresses in the configuration file.
At this time, if configuration parsing only supports strings and numeric types, we need to obtain the configuration values ourselves, and then carry out some subsequent processing, such as conversion to arrays or lists.
Fortunately, an excellent framework can always meet most of the requirements. SpringBoot is directly configured to map arrays or lists to
How to use
Java configuration class
@Configuration @ConfigurationProperties(prefix="allows") public class AllowedAccessConfig{ private String[] ipList; // The field type can be list < string > public void setPort(String[] port){ this.ipList = ipList; } }
configuration file
# application.yml allows: ipList: - 192.168.1.1 - 192.168.1.2 - 192.168.1.3 - 192.168.1.4 # or allows: ipList: 192.168.1.1, 192.168.1.2, 192.168.1.3, 192.168.1.4
# application.properties allows.ipList[0]=192.168.1.1 allows.ipList[1]=192.168.1.2 allows.ipList[2]=192.168.1.3 allows.ipList[3]=192.168.1.4 # or allows.ipList= 192.168.1.1, 192.168.1.2, 192.168.1.3, 192.168.1.4
Map
If the array or list does not meet the requirements, the key vlaue type is needed. No problem. SpringBoot is also supported.
Suppose that different AES keys are used for different services of a docking party, then the key should be used as the key according to the service type and the corresponding key as the value when configuring.
Java configuration class
@Configuration @ConfigurationProperties(prefix="aes.keys") public class AesKeyConfig{ private Map<String,String> keys; public void setKeys(Map<String,String> keys){ this.keys = keys; } }
configuration file
# application.yml aes: keys: order: 28jsaS2asf2fSA2 pay: @ra@3safdsR5&sDa # or aes: keys[order]: 28jsaS2asf2fSA2 keys[pay]: @ra@3safdsR5&sDa
# application.properties aes.keys.order=28jsaS2asf2fSA2 aes.keys.pay=@ra@3safdsR5&sDa # or aes.keys[order]=28jsaS2asf2fSA2 aes.keys[pay]=@ra@3safdsR5&sDa
Enum
Enumerate? That must be supported
However, the actual meaning is not great. If the configured value can be converted to enumeration value, the configured value must be the same as the name of enumeration value, and the case must not be bad. Because the spring boot implementation of the configuration to enumeration conversion uses
/* java.lang.Enum#valueOf */ public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name) { // The name here is the character representation of the enumeration value, which is generally uppercase T result = enumType.enumConstantDirectory().get(name); if (result != null) return result; if (name == null) throw new NullPointerException("Name is null"); throw new IllegalArgumentException( "No enum constant " + enumType.getCanonicalName() + "." + name); }
For the understanding of this code, please refer to another article Deep understanding of Java enumeration
If there are other fields in the enumeration, there is no way
JavaBean
what? Or not satisfied? Want to bind the configuration directly to a JavaBean?
Just do it!
JavaBean
@Configuration @ConfigurationProperties(prefix="upload") public class UploadConfig{ private String rootPath; private String fileType; private int fileSize; private boolean rename; // Omit Setter method }
configuration file
# application.yml upload: root-path: /xx/xx/xx file-type: zip fileSize: 1M rename: false
# application.properties upload.rootPath=/xx/xx/xx upload.fileType=zip upload.fileSize=1M upload.rename=false
The above usages can be used in combination, which is very flexible
However, if it is an array or a List of JavaBean s, or as the value of a Map, it will not be bound.
The reason is that the default binding is based on the Setter method to bind and assign a single field. What we want here is a JavaBean. We need to create a JavaBean object, and then do the attribute binding assignment.
If you follow these two steps, you can also successfully bind to a JavaBean object as an element.
It's just that SpringBoot doesn't do that. Instead, it provides an @ ConstructorBinding annotation. Let's use constructors to bind data.
Complex configuration
JavBean
@Configuration @ConfigurationProperties(prefix="app") public class AppConfig{ private Map<String, DataSourceMetadata> multiDataSourceMap; public void setMultiDataSourceMap(Map<String, DataSourceMetadata> multiDataSourceMap){ this.multiDataSourceMap = multiDataSourceMap; } } public class DataSourceMetadata{ private String url; private String driverClass; private String username; private String passowrd; // Omit Setter and Getter }
configuration file
app: multiDataSourceMap: ds1: url: jdbc:// driver-class: com.mysql.cj.Driver username: xxx password: xxx ds2: url: jdbc:// driver-class: com.mysql.cj.Driver username: 12sds password: adfwqw # or app: multiDataSourceMap: ds1: { url: jdbc:// driver-class: com.mysql.cj.Driver username: xxx password: xxx } ds2: { url: jdbc:// driver-class: com.mysql.cj.Driver username: 12sds password: adfwqw }
Then start, walk, and immediately you will find the familiar and angry NPE
The reason is simple. SpringBoot failed to read the corresponding configuration data from the configuration file and instantiate a Map because
The situation it is facing now is more complicated than before. Now JavaBean is a Map value
The solution is to use the constructor binding method, and you need to use the annotation @ ConstructorBinding in the constructor
public class DataSourceMetadata{ private String url; private String driverClass; private String username; private String passowrd; @ConstructorBinding public DataSourceMetadata(String url, String driverClass, String username, String passowrd){ this.url = url; this.driverClass = driverClass; this.username = username; this.password = password; } // Omit Setter and Getter }
As long as it's done, it's normal. There won't be any annoying NPE
I don't know if there are other ways to do it, such as continuing to use Setter method for data binding
Project Svelte
All of the above configurations, if any, are written to the application.yml or application.properties file, which will result in too much content in the configuration file, and various configurations are mixed together, which is not easy to manage and maintain.
If you need to change a configuration, you need to change the entire file. There is a certain risk that other configurations will be changed by mistake.
So it should be a kind of configuration, put together to manage.
Similarly, a type of configuration usually corresponds to a function. If one of the configuration changes, the corresponding test can also ensure that the modification of the same configuration file will not cause other problems.
So it's necessary to split application.yml.
It took a lot of effort to split one to upload.yml, and then import the configuration file as follows
By default, the configuration file is placed in the resources directory (maven/gradle). After compiling and packaging, the configuration file will be located in the root directory of classes, which is called classpath
@Configuration @PropertySource("classpath:upload.yml") @ConfigurationProperties(prefix="upload") public class UploadConfig{ private String rootPath; private String fileType; private int fileSize; private boolean rename; // Omit Setter method }
The problem is that you can't bind data to JavaBean properties.
Debug looks at the source code and can't get out of it. Try to use profile to solve it. Although it can be solved, it is not the same use scenario after all, and it is not appropriate.
Finally, I asked for help and told @ PropertySource doesn't support yaml files, only properties. So I tried. It was true
SpringBoot
Version is2.2.6
,A group of friends said he1.5
Yes, I do, butSpringBoot
The official made it clear that they did not support2.7.4. YAML Shortcomings
YAML files cannot be loaded by using the
@PropertySource
annotation. So, in the case that you need to load values that way, you need to use a properties file.
As you can see above, in fact, yaml configuration has more advantages, so if you want to continue to use yaml, it's not impossible
@PropertySource supports custom file formats
// Here, the DefaultPropertySourceFactory is inherited, or the PropertySourceFactory can be directly implemented public class YamlPropertySourceFactory extends DefaultPropertySourceFactory { public YamlPropertySourceFactory () { super(); } @Override public PropertySource<?> createPropertySource (String name, EncodedResource resource) throws IOException { // This judgment is necessary, because the direct use of name is null, and the reason is not explored String nameToUse = name != null ? name : resource.getResource().getFilename(); // yml file, use YamlPropertiesFactoryBean to build a Properties object from yaml file Resource // Then use PropertiesPropertySource to encapsulate the propertiesource object, which can be added to the Environment if (nameToUse.endsWith(".yml")) { YamlPropertiesFactoryBean factoryBean = new YamlPropertiesFactoryBean(); factoryBean.setResources(resource.getResource()); factoryBean.afterPropertiesSet(); return new PropertiesPropertySource(nameToUse, factoryBean.getObject()); } // If not, use the default implementation return super.createPropertySource(name, resource); } }
When using, @ PropertySource(factory=YamlPropertySourceFactory.class) is enough.
Use @ Value
@Value is an annotation of the Spring Framework and does not belong to spring boot. Its typical use scenario is to inject external configuration attributes and official documents @Values introduce
@Value uses the Spring built-in converter simpletypconverter, which supports Integer, String, and comma separated String arrays.
If the support is not enough, you can customize the transformation support, customize a Converter, and then add it to the ConverterService Bean, because the latter BeanPostProcessor relies on the ConverterService to process the transformation
So if there are some complex configurations, it's better to use spring boot.
@The advantage of Value is that it supports spiel and can be used on any Bean's method parameters or fields
So these are two different use scenarios, depending on the situation.
However, in general, I prefer the former one, because if @ Value is used directly in other beans, in case we need to change the configuration name, the result is that @ Value is used everywhere, which is troublesome to change, so from the perspective of management and maintenance, @ Value is too wild.
By the way, the processing location for @ Value is org.springframework.beans.factory.support.abstractautowirecapablebeanfactory ා populatebean. Of course, this is also the place to process @ input @ Autowired @ resource
Postscript
There are two steps from configuration file to configuration value used in program
- Read the configuration content into the Environment. No matter what the configuration is, the last is a Property Source
- Configuration value binding injection through BeanPostProcessor
If the configuration does not meet the properties or yaml format, you can customize the PropertySourceLoader, which can refer to
org.springframework.boot.env.YamlPropertySourceLoader and org.springframework.boot.env.PropertiesPropertySourceLoader