Spring Boot external configuration of (b) - @ConfigurationProperties, @ EnableConfigurationProperties

3, outside of the core configuration

        Subsequently the last chapter, "the Spring of the Boot external configuration (a)"

3.2 @ConfigurationProperties

It is well known, when the integrated Spring Boot external components, can be in propertiesor the YAMLattribute definition file configuration required components, such as Rediscomponents:

spring.redis.url=redis://user:[email protected]:6379
spring.redis.host=localhost
spring.redis.password=123456
spring.redis.port=6379

Which are to spring.redisbe prefixed. This is in fact Spring Bootprovided for each component corresponding to a Propertiesconfiguration class, attribute values and the configuration file to the configuration maps to the class, and they have a characteristic, are to Propertiesend, such as Redisthe corresponding configuration class is RedisProperties:

@ConfigurationProperties(prefix = "spring.redis")
public class RedisProperties {
    private String url;

    private String host = "localhost";

    private String password;

    private int port = 6379;
    
    ...
}

Which has a named @ConfigurationPropertiesannotation, its prefixargument is a good agreement prefix. The annotation function is to profile attributes and Propertiesconfiguration class attribute mapping, to achieve the purpose of automatic configuration. This two-step process, the first step is a registered Propertiesconfiguration class, the second step is to bind configuration properties, the process also involves a comment, it is @EnableConfigurationPropertiesthe annotation that is used to trigger a two-step operation. We have Redisan example look at ways to use it:

...
@EnableConfigurationProperties(RedisProperties.class)
public class RedisAutoConfiguration {
    ...
}

You can see its parameters are RedisPropertiesconfigured class. Through previous "Spring Boot automatic assembly (a)" we know that the annotation belongs @Enablemodule annotated, so that the annotation must have @Importintroduced implements ImportSelectoror ImportBeanDefinitionRegistrartype of interface, by introducing a specific functional classes to implement. We enter this comment:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(EnableConfigurationPropertiesImportSelector.class)
public @interface EnableConfigurationProperties {

    /**
     * Convenient way to quickly register {@link ConfigurationProperties} annotated beans
     * with Spring. Standard Spring Beans will also be scanned regardless of this value.
     * @return {@link ConfigurationProperties} annotated beans to register
     */
    Class<?>[] value() default {};

}

Sure enough, by @Importintroducing the EnableConfigurationPropertiesImportSelectorentire processing flow in this class are handled classes:

class EnableConfigurationPropertiesImportSelector implements ImportSelector {

    private static final String[] IMPORTS = { ConfigurationPropertiesBeanRegistrar.class.getName(),
            ConfigurationPropertiesBindingPostProcessorRegistrar.class.getName() };

    @Override
    public String[] selectImports(AnnotationMetadata metadata) {
        return IMPORTS;
    }

    ...
}

This class implements ImportSelectorthe interface, and rewrite selectImports method, which returns the class is Springloaded. Here you can see the two classes returned, which ConfigurationPropertiesBeanRegistraris used to register Propertiesconfiguration class, and ConfigurationPropertiesBindingPostProcessorRegistraris used to bind configuration properties, and they have achieved ImportBeanDefinitionRegistrarthe interface, will be registered directly registerBeanDefinitions method of rewriting Beanoperations. These characteristics are "Spring Boot automatic assembly (a)" 3.1 bar introduced, is not described here. Next, we describe these two categories.

3.2.1 Register Configuration Properties class

Let's look at ConfigurationPropertiesBeanRegistrarhow these configurations registered classes. We go directly to implementation of this class:

public static class ConfigurationPropertiesBeanRegistrar implements ImportBeanDefinitionRegistrar {

        // 1、第一步会先执行重写的 registerBeanDefinitions 方法,
        // 入参分别是 AnnotationMetadata 和 BeanDefinitionRegistry。
        // AnnotationMetadata 是获取类的元数据的,如注解信息、 classLoader 等,
        // BeanDefinitionRegistry 则是直接注册所需要的 Bean 
        public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
            
            // 2、调用 getTypes 方法,返回 Properties 配置类集合。进入 2.1 详细查看
            // 3、调用 register 方法,把 Properties 配置类注册到 Spring 容器中。进入 3.1 详细查看
            getTypes(metadata).forEach((type) -> register(registry, (ConfigurableListableBeanFactory) registry, type));
        }

        // 2.1 
        private List<Class<?>> getTypes(AnnotationMetadata metadata) {
            
            // 获取指定注解的所有属性值,key是属性名称,Value是值
            MultiValueMap<String, Object> attributes = metadata
                    .getAllAnnotationAttributes(EnableConfigurationProperties.class.getName(), false);
            
            // 返回 key 名称为 value 的值,这里返回的就是 Properties 配置类
            return collectClasses((attributes != null) ? attributes.get("value") : Collections.emptyList());
        }

        // 3.1
        private void register(BeanDefinitionRegistry registry, ConfigurableListableBeanFactory beanFactory,
                Class<?> type) {
            // getName 返回的是 Bean 的名称。进入 3.2 详细查看
            String name = getName(type);
            
            // 判断有没有注册过这个 Bean
            if (!containsBeanDefinition(beanFactory, name)) {
            
                // 没有则注册该 Bean。入参是注册器、Bean 的名称、需注册的 Bean。进入 4 详细查看
                registerBeanDefinition(registry, name, type);
            }
        }

        // 3.2
        private String getName(Class<?> type) {
            
            // 获取 Properties 配置类上标注的 ConfigurationProperties 注解信息
            ConfigurationProperties annotation = AnnotationUtils.findAnnotation(type, ConfigurationProperties.class);
            
            // 获取该注解中 prefix 的属性值
            String prefix = (annotation != null) ? annotation.prefix() : "";
            
            // 最后返回的是名称格式是 属性前缀-配置类全路径名,如:
            // spring.redis-org.springframework.boot.autoconfigure.data.redis.RedisProperties
            return (StringUtils.hasText(prefix) ? prefix + "-" + type.getName() : type.getName());
        }

        // 4、
        private void registerBeanDefinition(BeanDefinitionRegistry registry, String name, Class<?> type) {
            assertHasAnnotation(type);
            GenericBeanDefinition definition = new GenericBeanDefinition();
            definition.setBeanClass(type);
            
            // 通过 registerBeanDefinition 方法,注册 Bean 。
            // 后期会有 Spring 系列的文章详细介绍该过程,到时候大家再一起讨论。
            registry.registerBeanDefinition(name, definition);
        }
    }

After the implementation of all of our Propertiesconfiguration class was registered to the Springvessel. Next, we look at the data in the configuration file and how Propertiesto bind configuration class property.

3.2.2 Binding Configuration Properties

We go directly to ConfigurationPropertiesBindingPostProcessorRegistrarclass view:

public class ConfigurationPropertiesBindingPostProcessorRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        if (!registry.containsBeanDefinition(ConfigurationPropertiesBindingPostProcessor.BEAN_NAME)) {
            registerConfigurationPropertiesBindingPostProcessor(registry);
            registerConfigurationBeanFactoryMetadata(registry);
        }
    }

    ...
}

Here also registered two in registerBeanDefinitions method overridden Bean, one ConfigurationBeanFactoryMetadata, this is used to store metadata, we do not do too much attention; the other is ConfigurationPropertiesBindingPostProcessor, the class is used to bind the property, we mainly class discussion:

public class ConfigurationPropertiesBindingPostProcessor
        implements BeanPostProcessor, PriorityOrdered, ApplicationContextAware, InitializingBean {

    ...
}

It can be seen, which implements a number of interfaces, and are Springprovided by the extended interface. Here we briefly describe:

1, BeanPostProcessor: this is the Beanpost-processor. This class has two methods, one is postProcessBeforeInitialization, Beanbefore the initialization method is called;
the other is postProcessAfterInitialization, Beanthe method will be called after initialization; must be noted that, Springin the context of all Beanthe initialization will trigger these two methods.

2, ApplicationContextAware: This is Springa Awareone serial interface. SetApplicationContext class has a method, is mainly used to obtain ApplicationContextcontext object; Similarly, if the other prefix Aware, the prefix corresponding to the object name acquired.

3, InitializingBean: This is Beanthe life cycle of related interfaces. This class has a method afterPropertiesSet, when Beanall the properties after initialization, this method will be called.

Among them, BeanPostProcessorand InitializingBeanthe functions are in Beanadditional operations performed life cycle.

Here we simply understand on the line, will later Springdiscuss in detail the series of articles.

Next, we introduce the method in this class:

public class ConfigurationPropertiesBindingPostProcessor
        implements BeanPostProcessor, PriorityOrdered, ApplicationContextAware, InitializingBean {

    ...

    public static final String VALIDATOR_BEAN_NAME = "configurationPropertiesValidator";

    private ConfigurationBeanFactoryMetadata beanFactoryMetadata;

    private ApplicationContext applicationContext;

    private ConfigurationPropertiesBinder configurationPropertiesBinder;

    // 1、这是重写的 ApplicationContextAware 接口中的方法,用来获取 ApplicationContext 上下文对象
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    // 2、这是重写的 InitializingBean 接口中的方法,当 Bean 的属性初始化后会被调用。
    // 该方法主要对 ConfigurationBeanFactoryMetadata 和 ConfigurationPropertiesBinder 进行实例化
    @Override
    public void afterPropertiesSet() throws Exception {
        this.beanFactoryMetadata = this.applicationContext.getBean(ConfigurationBeanFactoryMetadata.BEAN_NAME,
                ConfigurationBeanFactoryMetadata.class);
        this.configurationPropertiesBinder = new ConfigurationPropertiesBinder(this.applicationContext,
                VALIDATOR_BEAN_NAME);
    }

    // 3、这是重写的 BeanPostProcessor 接口中的方法,在 Bean 初始化前会被调用,绑定属性的操作就是从这里开始。
    // 入参 bean 就是待初始化的 Bean,beanName 就是 Bean 的名称
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        ConfigurationProperties annotation = getAnnotation(bean, beanName, ConfigurationProperties.class);
        if (annotation != null) {
            bind(bean, beanName, annotation);
        }
        return bean;
    }

    ...
}

AfterPropertiesSet us first look at the second step, the method of the two classes is instantiated, one is from ApplicationContextthe acquired
ConfigurationBeanFactoryMetadataclass, operand data is used, too much attention is not; the other is configured by a parameterized initialization of the ConfigurationPropertiesBinderclass, the parameter is ApplicationContextan object and configurationPropertiesValidator string. We enter the class constructor:

class ConfigurationPropertiesBinder {

    private final ApplicationContext applicationContext;

    private final PropertySources propertySources;

    private final Validator configurationPropertiesValidator;

    private final boolean jsr303Present;

    ...

    ConfigurationPropertiesBinder(ApplicationContext applicationContext, String validatorBeanName) {
        this.applicationContext = applicationContext;
        this.propertySources = new PropertySourcesDeducer(applicationContext).getPropertySources();
        this.configurationPropertiesValidator = getConfigurationPropertiesValidator(applicationContext,
                validatorBeanName);
        this.jsr303Present = ConfigurationPropertiesJsr303Validator.isJsr303Present(applicationContext);
    }
    ...
}

Instantiate the class and four classes, we focus on the PropertySourcesinstantiation process, specifically by PropertySourcesDeducergetPropertySources class method, we enter the class:

class PropertySourcesDeducer {
    
    ...
    private final ApplicationContext applicationContext;

    PropertySourcesDeducer(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    // 1、通过 extractEnvironmentPropertySources 方法,返回 MutablePropertySources 对象,
    // MutablePropertySources 是 PropertySources 的实现类
    public PropertySources getPropertySources() {
        
        ...
        
        MutablePropertySources sources = extractEnvironmentPropertySources();
        if (sources != null) {
            return sources;
        }
        throw new IllegalStateException(
                "Unable to obtain PropertySources from " + "PropertySourcesPlaceholderConfigurer or Environment");
    }
    
    // 2、调用 Environment 的 getPropertySources 方法,返回 MutablePropertySources
    private MutablePropertySources extractEnvironmentPropertySources() {
        Environment environment = this.applicationContext.getEnvironment();
        if (environment instanceof ConfigurableEnvironment) {
            return ((ConfigurableEnvironment) environment).getPropertySources();
        }
        return null;
    }
    ...
    
}

See this, we should be more familiar with, Environmentis what we in "Spring Boot externalize configuration (a)" environmental applications running in Section 3.1 have said, access to all of the external configuration data through the class, and MutablePropertySourcesit is an external storage configuration of the underlying real object.

Here, the second step of the method proceeds afterPropertiesSet finished, mainly instantiated ConfigurationPropertiesBinderobject, the object is stored in all of the external configuration. Then look postProcessBeforeInitialization method of the third step:

public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    ConfigurationProperties annotation = getAnnotation(bean, beanName, ConfigurationProperties.class);
    if (annotation != null) {
        bind(bean, beanName, annotation);
    }
    return bean;
}

Mentioned above, all Beaninitialization will call this method, we first determine whether the current Beanthere is no marked @ConfigurationPropertiesnotes, there is then the current Beanis Propertiesconfigured class, and call the bind method of the bind attribute class operation, we enter the method:

private void bind(Object bean, String beanName, ConfigurationProperties annotation) {
    
    ...
    
    try {
        this.configurationPropertiesBinder.bind(target);
    }
    catch (Exception ex) {
        throw new ConfigurationPropertiesBindException(beanName, bean, annotation, ex);
    }
}

Here in the second step is called instantiation ConfigurationPropertiesBinderbind method object:

class ConfigurationPropertiesBinder {
    
    ...
    
    public void bind(Bindable<?> target) {
        
        ...
        
        getBinder().bind(annotation.prefix(), target, bindHandler);
    }
    
    ...
    
    private Binder getBinder() {
        if (this.binder == null) {
            this.binder = new Binder(getConfigurationPropertySources(), getPropertySourcesPlaceholdersResolver(),
                    getConversionService(), getPropertyEditorInitializer());
        }
        return this.binder;
    }
    
    private Iterable<ConfigurationPropertySource> getConfigurationPropertySources() {
        return ConfigurationPropertySources.from(this.propertySources);
    }
    
    ...
}

Back through the first inside getBinder () Binderobject. GetBinder by the method Binderof the object created with the constructor parameters, we focus on the first argument getConfigurationPropertySources method returns:

class ConfigurationPropertiesBinder {
    
    ...
    
    private final PropertySources propertySources;
    
    ...
    
    private Iterable<ConfigurationPropertySource> getConfigurationPropertySources() {
        return ConfigurationPropertySources.from(this.propertySources);
    }
    
    ...
}

Specifically by ConfigurationPropertySourcesreturn from the method, the parameter propertySourcesis the second step of example ConfigurationPropertiesBinderinitialization good value when the object, the source object is stored inside the external configuration of the PropertySource, we enter the method:

public final class ConfigurationPropertySources {
    
    ...

    public static Iterable<ConfigurationPropertySource> from(Iterable<PropertySource<?>> sources) {
        return new SpringConfigurationPropertySources(sources);
    }
    
    ...
}

Eventual return is SpringConfigurationPropertySourcesarranged source object, the "Spring Boot external configuration of (a)," in said before, this type of adapter is doing a work to MutablePropertySourcesbe converted to ConfigurationPropertySource.

Then, the object passed in Binderthe constructor used to create the object:

public class Binder {
    
    ...
    
    private final Iterable<ConfigurationPropertySource> sources;
    
    ...
    
    public Binder(Iterable<ConfigurationPropertySource> sources,
            PlaceholdersResolver placeholdersResolver,
            ConversionService conversionService,
            Consumer<PropertyEditorRegistry> propertyEditorInitializer) {
        
        this.sources = sources;
        
        ...
    }
    
    ...
}

Thus, Binderthe object on the outside of a data configuration there, and all the subsequent operations are carried out in binding the class. Due to the subsequent intermediate process is too complex and difficult to understand, here we go directly to the final step, the detailed process of students interested your own research, not repeat them here.

Enter the final stage of the bind method:

// 这里着重介绍一下 BeanProperty 类,该类存储了 properties 配置类中的字段及字段的set、get方法,存储的是反射中的类。
// 如 RedisProperties 中的 url 字段,则 BeanProperty 对象中存储的是
// url 的 Field 类、setUrl 的 Method 类、getUrl 的 Method 类。
private <T> boolean bind(BeanSupplier<T> beanSupplier,
            BeanPropertyBinder propertyBinder, BeanProperty property) {
    
    // 这里获取的是字段名
    String propertyName = property.getName();
    
    // 这里获取的是字段类型
    ResolvableType type = property.getType();
    Supplier<Object> value = property.getValue(beanSupplier);
    Annotation[] annotations = property.getAnnotations();
    
    // 这里获取到了配置文件中的值,该值来源于 SpringConfigurationPropertySources 对象
    Object bound = propertyBinder.bindProperty(propertyName,
            Bindable.of(type).withSuppliedValue(value).withAnnotations(annotations));
    if (bound == null) {
        return false;
    }
    if (property.isSettable()) {
        
        // 最后则是通过 set Method 的 invoke 方法,也就是反射的形式进行赋值。
        property.setValue(beanSupplier, bound);
    }
    else if (value == null || !bound.equals(value.get())) {
        throw new IllegalStateException(
                "No setter found for property: " + property.getName());
    }
    return true;
}

At this point, the entire configuration properties of the binding process ends. You can see, the final acquisition of external configuration data from previously loaded Environmentobject.

Finally, briefly recall @ConfigurationPropertiesNotes implementation profile property values and configuration class property mapping:

1, the first @ConfigurationPropertieslabel in the Propertiesconfiguration class, attribute prefix parameter is a good agreement.

2, then @EnableConfigurationPropertiesto trigger the whole process, it is the parameter Propertiesconfiguration class.

3, @EnableConfigurationPropertiesby @importintroducing a EnableConfigurationPropertiesImportSelectorclass, the class and two classes loaded, for registering a Propertiesconfiguration class, to bind the other configuration attributes.

4, finally, it is reflected by way of binding properties and property values derived Environment.

3.1.3 ConfigurationPropertiesAutoConfiguration

In fact, when we use @ConfigurationPropertiesthe time, no marked @EnableConfigurationPropertiesnotes, because Spring Bootwill help us in the process of automatic assembly loaded in a named ConfigurationPropertiesAutoConfigurationclass that is in spring.factoriesgood defined:

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration

Specific automatic assembly process in "Spring Boot automatic assembly (B)" discussed in this article, will not repeat them here. Let's look at ConfigurationPropertiesAutoConfigurationimplementation:

@Configuration
@EnableConfigurationProperties
public class ConfigurationPropertiesAutoConfiguration {

}

Very simple, direct by tagging @EnableConfigurationPropertiesto open the process to automatically configure annotation. So that registered how Propertiesto configure it like? Because of the above mentioned, Propertiesconfiguration class is a parameter passed in the annotation. In fact, just marked on the configuration class @Componentnotes on the line, then it will be Springscanned, and then register.

4, summary

        Finally, to Spring Bootconfigure externalizing do a whole summary:

1, firstly, it is arranged outside of Spring Boota feature, achieved primarily through external configuration code resources and cooperating to avoid hard-coded, to provide flexibility in application data or behavior changes.

2, then introduces several resource types of external configurations, such as propertiesand the YAMLprofile type, and describes the configuration of access to external resources in several ways.

3. Secondly, the Environmentloading process class, and all loaded into the external configuration of Environmentthe bottom layer is achieved. EnvironmentIs the Spring Bootouter configuration of the core class that stores all of the external configuration of resources, and other resources to obtain the external configuration of the embodiment are also dependent on the class.

4. Finally, the Spring Bootframework core of @ConfigurationPropertiesannotation, the annotation is to applicationprofile property values and Propertiesconfiguration class attribute mapping, automatic configuration to achieve the purpose, and take you to explore the underlying process of implementation.

These are the contents of this chapter, such as over or there is an error in the article should be added please put forward, I am grateful.

Guess you like

Origin www.cnblogs.com/loongk/p/12076402.html