@ConfigurationProperties annotation principle

foreword

@ConfigurationPropertiesAnnotation is a more convenient way provided by SpringBoot to process property values ​​in configuration files. Through mechanisms such as automatic binding and type conversion, the set of properties with a specified prefix can be automatically bound to a Bean object.

loading principle

In the Springboot startup process loading configuration  prepareEnvironment() method, there is an important step method  bindToSpringApplication(environment), which is used to bind the property values ​​in the configuration file to the  @ConfigurationProperties annotated Bean object. But these objects have not been managed by the Spring container at this time, so the automatic injection of properties cannot be completed. So when were these Bean objects registered in the Spring container?

This involves  ConfigurationPropertiesBindingPostProcessor classes, which are Bean post-processors responsible for scanning all Bean objects marked with @ConfigurationProperties annotations in the container. If found, the Binder component will be used to bind the value of the external property to them, so as to achieve automatic injection.

  • bindToSpringApplication Mainly bind the property value to the Bean object;
  • ConfigurationPropertiesBindingPostProcessor Responsible for registering the annotated Bean object into the container when the Spring container starts, and completing subsequent attribute injection operations;

Look at the configuration initialization from the springboot program startup

In the Springboot program startup and loading process, the method SpringApplication.runin execution will be executed prepareEnvironment()to initialize the configuration, so what is done in each step of the initialization process?

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
   DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
      /** 
      * 1、创建 ConfigurableEnvironment 对象:首先调用 getOrCreateEnvironment() 方法获取或创建
      * ConfigurableEnvironment 对象,该对象用于存储环境参数。如果已经存在 ConfigurableEnvironment 对象,则直接使用它;否则,根据用户的配置和默认配置创建一个新的。
      */
      ConfigurableEnvironment environment = getOrCreateEnvironment();
      /**
      * 2、解析并加载用户指定的配置文件,将其作为 PropertySource 添加到环境对象中。该方法默认会解析 application.properties 和 application.yml 文件,并将其添加到 ConfigurableEnvironment 对象中。
      * PropertySource 或 PropertySourcesPlaceholderConfigurer 加载应用程序的定制化配置。
      */
      configureEnvironment(environment, applicationArguments.getSourceArgs());
      // 3、加载所有的系统属性,并将它们添加到 ConfigurableEnvironment 对象中
      ConfigurationPropertySources.attach(environment);
      // 4、通知监听器环境参数已经准备就绪
      listeners.environmentPrepared(bootstrapContext, environment);
      /**
      *  5、将默认的属性源中的所有属性值移到环境对象的队列末尾,
      这样用户自定义的属性值就可以覆盖默认的属性值。这是为了避免用户无意中覆盖了 Spring Boot 所提供的默认属性。
      */
      DefaultPropertiesPropertySource.moveToEnd(environment);
      Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
          "Environment prefix cannot be set via properties.");
      // 6、将 Spring Boot 应用程序的属性绑定到环境对象上,以便能够正确地读取和使用这些配置属性
      bindToSpringApplication(environment);
      // 7、如果没有自定义的环境类型,则使用 EnvironmentConverter 类型将环境对象转换为标准的环境类型,并添加到 ConfigurableEnvironment 对象中。
      if (!this.isCustomEnvironment) {
        EnvironmentConverter environmentConverter = new EnvironmentConverter(getClassLoader());
        environment = environmentConverter.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
      }
      // 8、再次加载系统配置,以防止被其他配置覆盖
      ConfigurationPropertySources.attach(environment);
      return environment;
}

Take a look at its configuration loading process steps:

  • Create  an environment object ConfigurableEnvironment  to store environment parameters;
  • configureEnvironment The method loads the default  application.properties and  application.yml configuration files; and the user-specified configuration files are encapsulated as  PropertySource  and added to the environment object;
  • attach(): load all system properties and add them to the environment object;
  • listeners.environmentPrepared(): Send a listening notification that the environment parameter configuration is ready;
  • moveToEnd(): Move  all property values ​​in the system default  property source to the end of the environment object queue, so that user-defined property values ​​can override the default property values.
  • bindToSpringApplication: The properties of the application are bound to the Bean object;
  • attach(): Load the system configuration again to prevent it from being overwritten by other configurations;

In the above configuration loading process, various configuration attributes will be encapsulated into abstract data structures  PropertySource. The code format of this data structure is as follows, in the form of key-value.

public abstract class PropertySource<T> {
    protected final String name; // 属性源名称
    protected final T source; // 属性源值(一个泛型,比如Map,Property)
    public String getName();  // 获取属性源的名字  
    public T getSource(); // 获取属性源值  
    public boolean containsProperty(String name);  //是否包含某个属性  
    public abstract Object getProperty(String name);   //得到属性名对应的属性值   

PropertySource There are various implementation classes for managing application configuration properties. Different PropertySource implementation classes can obtain configuration properties from different sources, such as files, environment variables, command line parameters, and so on. Some of the implementation classes involved are:

relation chart

  • MapPropertySource: An adapter that converts the object of Map key-value pairs into PropertySource objects;
  • PropertiesPropertySource: All configuration properties in the Properties object are converted to property values ​​in the Spring environment;
  • ResourcePropertySource: Load configuration properties from the file system or classpath and encapsulate them into PropertySource objects;
  • ServletConfigPropertySource: Read configuration properties in Servlet configuration and encapsulate them into PropertySource objects;
  • ServletContextPropertySource: Read configuration properties in the Servlet context and encapsulate them into PropertySource objects;
  • StubPropertySource: is an empty implementation class, its role is only to give the CompositePropertySource class as the default parent property source to avoid null pointer exceptions;
  • CompositePropertySource: It is a compound implementation class, which maintains the PropertySource collection queue internally, and can merge multiple PropertySource objects;
  • SystemEnvironmentPropertySource: Read configuration properties from operating system environment variables and encapsulate them into PropertySource objects;

The PropertySource objects generated by the above various configuration initializations will be maintained in the collection queue.

List<PropertySource<?>> sources = new ArrayList<PropertySource<?>>()

After the configuration is initialized, the application context AbstractApplicationContextwill load the configuration, so that the program can obtain configuration information at any time during runtime.

 private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
   ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
   ApplicationArguments applicationArguments, Banner printedBanner) {
    // 应用上下文加载环境对象
  context.setEnvironment(environment);
  postProcessApplicationContext(context);
    .........
  }

3. Read configuration

After understanding the configuration loading process above, reading the configuration is easy to understand. It is nothing more than traversing the queue PropertySourceand namematching the attribute name with the corresponding attribute value source. PropertyResolverIt is a key class for obtaining configuration. It provides PropertySource methods for operating queues. The core method getProperty(key)obtains configuration values. After looking at the dependencies of this class, it is found  Environment to be a subclass of it.

Then it is actually possible to directly use PropertyResolver to obtain configuration properties. At this point, we have a general understanding of the loading and reading of Springboot configuration.

@Slf4j
@SpringBootTest
public class EnvironmentTest {

    @Resource
    private PropertyResolver env;
    
    @Test
    public void var1Test() {
        String var1 = env.getProperty("env101.var1");
        log.info("Environment 配置获取 {}", var1);
    }
}

Guess you like

Origin blog.csdn.net/qq_28165595/article/details/131272077