A background.

While the previous article systematically introduced @Value best practices, this article will document @ConfigurationProperties, another major player in Springboot externalization configuration.

Best practices for @ConfigurationProperties

Many components in Springboot can be implemented out of the box with zero configuration. For example, introducing a spring-boot-redis-starter can use the local redis. Zero configuration capability is possible because of the ability to rely on @ConfigurationProperties.

2.1 Basic Usage mode

ConfigurationProperties allows us to modularize our configuration. In this way, centralized management is convenient for use and maintenance, and each module configuration does not affect each other. We can also use @Value, Environment, start parameters, and so on. However, it is obviously not applicable in the case of modular functionality.

The basic use of @ConfigurationProperties is as follows:

Custom configuration classes
/ * * *@author sunliming11
 * @date created in 2021/2/17
 */
@Data
@ToString
@Component
@ConfigurationProperties(prefix = "my.plugin")
public class MyPluginProperties {
    /** * Whether to enable */
    private Boolean enabled;
    /** * Plug-in name */
    private String name;
    /** * alias */
    private List<String> alias;
    /** * Map attribute */
    private Map<String, String> map;
    /** * obj */
    private Security security;

    @Data
    @ToString
    public static class Security {
        /** * User name */
        private String username;
        /** * Password */
        private String password;
        /** * Role list */
        private List<String> roles = new ArrayList<>(Collections.singleton("USER")); }}Copy the code
The introduction of the jar package

Need to introduce the spring – the boot – configuration – processor package, the package is the most important ability provides ConfigurationMetadataAnnotationProcessor this post processor, What it does is generate the metadata file for @ConfigurationProperties.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>
Copy the code
configuration

Before configuring, let’s build the project. For IDEA, click 🔨 in the upper right corner:

Then you can see in the compile directory:

In the yML configuration file, you can see the following information:

The complete configuration of YAML is as follows:

my:
    plugin:
        name: The plug-in name
        enabled: true
        alias:
            - The alias 1
            - The alias 2
            - The alias 3
        map:
            "[key1]": value1
            "[key2]": value2
            "/key3": value3
        security:
            password: 12345
            username: The user name
            roles:
                - USER
                - ADMIN
Copy the code

If the properties configuration is used, the complete configuration is as follows:

my.plugin.name=The plug-in name
my.plugin.enabled=true
my.plugin.alias[0]=The alias 1
my.plugin.alias[1]=The alias 2
my.plugin.alias[2]=The alias 3
my.plugin.map[key1]=value1
my.plugin.map[key2]=value2
my.plugin.map[key3]=value3
my.plugin.security.username=The user name
my.plugin.security.password=12345
my.plugin.security.roles[0]=ADMIN
my.plugin.security.roles[1]=USER
Copy the code

The above example shows how to configure a POJO with @ConfigurationProperties, making it easy to inject complex property types. But @Value requires a bit of ingenuity to inject complex types.

2.2 relaxed binding

Springboot supports the @ConfigurationProperties loose binding rule. For example acme. MyProject. Person firstName this attribute configuration and it is of the same effect.

acme.my-project.person.first-name=LEON
acme.my_project.person.first_name=LEON
ACME_MYPROJECT_PERSON_FIRSTNAME=LEON
.
Copy the code

Based on the loose binding principle, all of the above writing methods can be parsed eventually. That is, the same word can be resolved as long as the pure letters after removing the camel, underscore (\_), and dash (-) rules are ultimately the same (case insensitive).

2.3 The best way to activate

Using the @ConfigurationProperties annotation, which is not available directly, we also need to enable the configuration class. If this configuration class is not activated, the Springboot project can still start normally, but the configuration class is not available. There are several ways to enable @ConfigurationProperties, but in principle it is possible to inject the configuration class into the container. Based on this principle, there are the following ways to activate:

2.3.1 @ Component
@Component // The @Component is scanned by the container and can be injected into the container.
@ConfigurationProperties(prefix = "my.plugin")
public class MyPluginProperties {
……
}
Copy the code
2.3.2 @ the Configuration
@Configuration
@ConfigurationProperties(prefix = "my.plugin")
public class MyPluginProperties {
……
}
Copy the code
2.3.3 @ Bean
@Configuration
public class MyConfiguration {

	@Bean
    public MyPluginProperties myPluginProperties(a) {
    	return newMyPluginProperties(); }}Copy the code
2.3.4 @ EnableConfigurationProperties
@Configuration
@EnableConfigurationProperties(MyPluginProperties.class)
public class MyConfiguration {}Copy the code

In the source code comments, @ EnableConfigurationProperties role is to provide support for @ ConfigurationProperties. How is it supported? Definition of class in the source code, there are: @ Import (EnableConfigurationPropertiesRegistrar. Class), meaning that it says support by @ the ability of the Import. EnableConfigurationPropertiesRegistrar ImportBeanDefinitionRegistrar is achieved, thus can be injected into the custom BeanDefinition, The custom here bd original Class is @ EnableConfigurationProperties annotations in the specified value. With bd, beans are generated naturally.

So which one is better? There is no good or bad. But still it is recommended to use @ EnableConfigurationProperties, special people to do things.

2.4 What if attribute or Attribute Values Fail to match

If additional attributes are configured such as my.plugin.name1 in the example above

my:
    plugin:
        name: The plug-in name
        name1: Redundant configuration
Copy the code

By default, the project starts normally. If you want the project to fail, set ignoreUnknownFields = false:

@ConfigurationProperties(prefix = "my.plugin", ignoreUnknownFields = false)
Copy the code

If the attribute type does not match, the startup fails by default. If you do not want the startup to fail, you can set ignoreInvalidFields = true:

@ConfigurationProperties(prefix = "my.plugin", ignoreInvalidFields = true)
Copy the code

2.5 check

Spring’s validation mechanism is based on JSR303, and Spring also supports JSR303 validation for the @ConfigurationProperties configuration class properties. The way to use it is simple, as follows:

@Data
@ToString
@Component
@ConfigurationProperties(prefix = "my.plugin", ignoreUnknownFields = false, ignoreInvalidFields = true)
@Validated // Enable verification
public class MyPluginProperties {
    /** * Whether to enable */
    private Boolean enabled;
    /** * Plug-in name */
    @NotNull
    @Size(min = 12, max = 16)
    private String name;
    /** * alias */
    @NotNull
    private List<String> alias;
    /** * Map attribute */
    private Map<String, String> map;
    /** * obj */
    private Security security;
Copy the code

So, at sign, Validated on the class. Then use the normal JSR303 supported validation annotations.

2.6 Principle of zero Configuration

How do you implement the 0 configuration in SpringBoot? For example, spring-boot-redis-starter is introduced, which does not need to do any configuration to start the project and connect to the local Redis database. There is also an annotation that relies on @enableAutoConfiguration in addition to @ConfigurationProperties. This annotation also uses the @import capability, which can be seen in the source code:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class) / / entrusted to AutoConfigurationImportSelector
public @interface EnableAutoConfiguration {
    ……
}
Copy the code

Its full link is as follows: @ EnableAutoConfiguration ➡ ️ @ Import ➡ ️ @ AutoConfigurationImportSelector ➡ ️ AutoConfigurationImportSelector ➡ ️ Implement selectImports () ➡ ️ internal call getAutoConfigurationEntry () ➡ ️ internal call getCandidateConfigurations ➡ ️ Org. Springframework. Core. IO. Support. SpringFactoriesLoader# loadFactoryNames ➡ ️ internal call loadSpringFactories () in the method, we can see:

private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
    Map<String, List<String>> result = cache.get(classLoader);
    if(result ! =null) {
        return result;
    }

    result = new HashMap<>();
    try{ Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION); }...Copy the code

In the source code, you can see itFACTORIES_RESOURCE_LOCATIONConstant values are:

That is, if you use the @enableAutoConfiguration annotation, Springboot will scan meta-INF/Spring. factories in all package paths during startup, and spring will scan and inject all the classes in that file.

The custom

Based on the above knowledge, start a new module to define a configuration. The directory structure is as follows:

@Data
@ConfigurationProperties(prefix = "my.plugin")
public class MyPluginProperties {
    /** * Plug-in name */
    private String name = "My Custom plug-in";
    /** * Whether to enable */
    private Boolean enabled;
}

/ / * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

@EnableConfigurationProperties(MyPluginProperties.class)
public class MyPluginAutoConfiguration {}Copy the code

The spring.factories files are as follows:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.leon.myplugin.MyPluginAutoConfiguration
Copy the code

Maven package = Springboot; maven package = Springboot

<dependency> <groupId>com.leon</groupId> <artifactId>spring-boot-starter-myplugin</artifactId> < version > 0.0.1 - the SNAPSHOT < / version > < / dependency >Copy the code

Reimport can be used happily in yML files:

Disadvantages of @ConfigurationProperties

@ConfigurationProperties is very powerful, but one thing that doesn’t match @Value is the lack of support for SpringEL expressions. This is clearly stated on the website. But that doesn’t stop @ConfigurationProperties from being one of the most important underlying capabilities in Springboot.

@ the Value principle is: before the bean initialization, to rely on the injection, mainly is delegated to the rear AutowiredAnnotationBeanPostProcessor processor to complete;

@ ConfigurationProperties principle is: based on the ability of the @ Import by EnableConfigurationPropertiesRegistrar configuration class BeanDefinition injection, then properties of injection.

The principle of implementation is completely different.

In live.

This article focuses on how @ConfigurationProperties is used and how it works, along with a use case for “Springboot” zero configuration and how it works. Finally, the difference between @ConfigurationProperties and @Value is compared.