springboot configuration injection enhancement (2) Principle of attribute injection

One principle

1 configured storage

When springboot starts, it will build an object of type org.springframework.core.env.Environment. This object is used to store configuration. As shown in the figure, springboot will create an Environment object at the beginning of startup.

This webApplicationType enumeration is specified when new SpringApplication()

  • If org.springframework.web.reactive.DispatcherHandler exists and is loadable (either itself or one of its dependencies does not exist or cannot be loaded), and org.springframework.web.servlet.DispatcherServlet does not exist or cannot be loaded, and org.glassfish .jersey.servlet.ServletContainer does not exist or cannot be loaded, then WebApplicationType.REACTIVE will be used to build an Environment object of ApplicationReactiveWebEnvironment type, that is, the Spring WebFlux framework
  • If any of javax.servlet.Servlet, org.springframework.web.context.ConfigurableWebApplicationContext does not exist or cannot be loaded, then WebApplicationType.NONE will be used to build an Environment object of ApplicationEnvironment type, that is, a normal spring non-web framework
  • Otherwise, WebApplicationType.SERVLET will be used to build an Environment object of ApplicationServletEnvironment type, that is, Servlet, which is the spring mvc framework.

Let’s take the commonly used spring mvc as an example. Let’s first look at the data structure of the ApplicationServletEnvironment class (all are similar)

It is essentially a PropertyResolver interface. The core is to provide a method to obtain the corresponding attribute value according to the key of a certain attribute in the configuration and a method to parse the attribute value according to a certain rule. All its subclasses are making some extensions to it. , making it more convenient to use, such as the MutablePropertySources getPropertySources() method added to ConfigurableEnvironment, in order to obtain all configuration content

In fact, these three Environments of springboot are all subclasses of StandardEnvironment, and the parent class of StandardEnvironment, AbstractEnvironment, uses MutablePropertySources as the type of data source collection. Of course, it also determines the data source collection based on the MutablePropertySources getPropertySources() method in its implementation interface ConfigurableEnvironment. type.

It can be seen that this class has one more data source collection than the original PropertyResolver interface. The configuration principle of springboot is simply to assemble the configurations from different sources into data source objects of different data source types, and then put them in MutablePropertySources according to the name and data source. The object performs key-value storage. When used, it traverses the values ​​of all data sources in MutablePropertySources, finds the first value that meets the conditions, and then parses it after finding it, such as ${xxxx}. The logic used, that is, all implementations of the PropertyResolver interface, are proxied by the PropertySourcesPropertyResolver object (a new PropertySourcesPropertyResolver object will be created in the construction method of AbstractEnvironment)

There is a List<PropertySource<?>> propertySourceList member variable in MutablePropertySources. This is the data source collection mentioned above.

PropertySource is the specific configuration. In fact, there are only two variables, name: data source name, source: specific data source. There are many implementation classes for this, and we can also define it ourselves. For example, we can create a class ourselves to use as a source, and then implement the methods of PropertySource that query values ​​based on the source.

2 Source of configuration

In fact, the source of this configuration can be added to the Environment object at any time, but if we want springboot to use our data source when loading beans during the startup process, we should add it to the Environment before the postProcessBeanFactory() method of PropertySourcesPlaceholderConfigurer is executed. Lower versions of springboot use PropertyPlaceholderConfigurer, but this has been deprecated long ago, so there is no need to worry about it. Let’s introduce PropertySourcesPlaceholderConfigurer.

You can see that it is a BeanFactoryPostProcessor implementation class. This class will execute the postProcessBeanFactory() method during the refresh phase of startup.

As you can see, a new data source collection will be created here, and environment and localProperties will be added to it. This localProperties is the configuration file path specified when we manually built the PropertySourcesPlaceholderConfigurer.

  @Bean
  public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
    PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer();
    configurer.setLocation(new ClassPathResource("config.properties"));
    return configurer;
  }

This data source collection will then be encapsulated into a StringValueResolver for parsing properties.

You can see that this is first used to parse bean attributes such as ${xx}. In fact, I feel that this is also for compatibility with old code. After all, the earliest spring only injected this method in the configuration file.

<bean id="cacheService" class="my.user.UserImpl">
        <property name="name" value="${user.123.name}"/>
    </bean>

Then you can see that the beanFactoryToProcess.addEmbeddedValueResolver(valueResolver) method is executed. This method only saves the valueResolver resolver and is not used yet.

It will be used in AutowiredAnnotationBeanPostProcessor when parsing @value. Here are some commonly used data sources for system configuration.        

2.1 System environment variables and startup parameters when starting java

When StandardEnvironment is initialized, the parent class will execute the customizePropertySources method of StandardEnvironment to create two data sources and put them in the data source collection. We are also familiar with these two data sources, which are the system environment variables and the startup parameters when starting Java, namely System.getenv () and System.getProperties()

2.2 application.yml

The old version is loaded by ConfigFileApplicationListener as the startup listener, after listening to the ApplicationEnvironmentPreparedEvent event, which is an event that will be sent after the Environment is created.

It can be seen that he also implements the EnvironmentPostProcessor interface, and merges himself and the org.springframework.boot.env.EnvironmentPostProcessor=xxxx configured in other spring.factories files to execute the corresponding postProcessEnvironment() method, and his own postProcessEnvironment method The application.yml file will be loaded

The new version is loaded using ConfigDataEnvironmentPostProcessor, and ConfigFileApplicationListener is abandoned. Instead, EnvironmentPostProcessorApplicationListener is used to execute the EnvironmentPostProcessor method. The single responsibility is clearer.

2.3 PropertySources/PropertySource

PropertySources is actually a collection of PropertySources. ConfigurationClassPostProcessor adds the configuration file content of the PropertySource path to the environment.

You can see that this path even supports dynamic paths such as ${xxx} (environment.resolveRequiredPlaceholders will get the real value of ${xxx} from the environment object), and then combine the propertySources of multiple locations to form a CompositePropertySource object

3 Configuration usage

3.1 Property injection of BeanDefinition

As introduced above, it is usually to parse the propertyValues ​​of BeanDefinition in PropertySourcesPlaceholderConfigurer

<bean id="cacheService" class="my.user.UserImpl">
        <property name="name" value="${user.123.name}"/>
    </bean>
3.2 @Value

As mentioned above, it will be parsed in AutowiredAnnotationBeanPostProcessor.

    @Value("${user.123.name}")
    private String user123Name;
3.3 @ConfigurationProperties
@Data
@Component
@ConfigurationProperties(prefix = "user.123")
public class UserConfiguration {
    /**
     * 姓名
     */
    private String name;

    /**
     * 性别
     */
    private String sex;
}

It is bound by postProcessBeforeInitialization(Object bean, String beanName) of ConfigurationPropertiesBindingPostProcessor

You can see that what is actually executed is Binder's bind() method. This method can bind a property with the same prefix to the properties of the corresponding object. It mainly focuses on the first two parameters of the Binder constructor, Iterable<ConfigurationPropertySource> sources. , PlaceholdersResolver placeholdersResolver, you can see that propertySources are used as data sources to search and parse, and this propertySources is generated by calling the ConfigurationPropertiesBinder.Factory#create() method in the ConfigurationPropertiesBinder.register(registry) method.

You can see that if there is only one bean of type PropertySourcesPlaceholderConfigurer, this data source is the appliedPropertySources of the same data source PropertySourcesPlaceholderConfigurer we mentioned above as used in 3.1 and 3.2. Otherwise, Environment will be used as the data source.

3.4 environment

This is easy, just get it directly from the environment object

  • environment.getProperty("user.123.name")
  • environment.resolvePlaceholders("${user.123.name}")
  • environment.resolveRequiredPlaceholders("${user.123.name}") will report an error if it cannot be resolved.

Guess you like

Origin blog.csdn.net/cjc000/article/details/132800290