Categories
Uncategorized

Spring Boot in @ConfigurationProperties The principle source parsing comment

0. open source projects recommended

Pepper Metrics is My colleagues and I developed an open source (https://github.com/zrbcool/pepper-metrics) tool, which was collected by jedis / mybatis / httpservlet / dubbo / motan operating performance statistics, and exposure to mainstream prometheus etc. timing database compatibility data, by grafana show trends. Of its plug-in architecture is very user-friendly extensions and other integrated open source components.
    Please give a star, while welcome to become a developer to submit PR together to improve the project.

1 Overview

Needless to say, we all know that Spring Boot very easy and fast, so that students develop a simple few lines of code to configure even add a few lines of zero-configuration will be able to launch and use a program, project which we may often use
    The prefix configuration @ConfigurationProperties Bean and among certain properties bound to the configuration values ​​defining the configuration Bean separation, easy to manage.
    Well, this is what @ConfigurationProperties mechanism, how to achieve it? We today to talk about this topic

2. The text

Speaking from 2.1 EnableConfigurationProperties

Why From EnableConfigurationProperties?
    Spring Boot project itself among a large number autoconfigure is enabled using EnableConfigurationProperties comment XXXProperties features, such as the spring-data-redis
    This RedisAutoConfiguration

@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class) //看这里
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
    // ...
}

And in turn RedisProperties annotated @ConfigurationProperties (prefix = “spring.redis”), and so will spring.redis CI RedisProperties the prefix
    The entity class is bound.

2.2 EnableConfigurationProperties resolve internal implementation

Having reason, we are concerned that the internal implementation, take a look at

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(EnableConfigurationPropertiesImportSelector.class)
public @interface EnableConfigurationProperties {
    Class[] value() default {};
}

@Import (EnableConfigurationPropertiesImportSelector.class) indicates the handler class EnableConfigurationPropertiesImportSelector this comment,
    View EnableConfigurationPropertiesImportSelector source

class EnableConfigurationPropertiesImportSelector implements ImportSelector {

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

    @Override
    public String[] selectImports(AnnotationMetadata metadata) {
        return IMPORTS;
    }
    
    // 省略部分其他方法
}

We look at this key part of the return of a IMPORTS array, the array contains ConfigurationPropertiesBeanRegistrar.class, ConfigurationPropertiesBindingPostProcessorRegistrar.class two elements
    According to the principle @Import and ImportSelector interface (which can refer to the principle of an article written by a colleague: @Import love each other and @EnableXXX), we know that spring will initialize the Registrar to the top two among the spring container, while the two were Registrar implements ImportBeanDefinitionRegistrar interface
    The ImportBeanDefinitionRegistrar will trigger a call to (the principle can refer to the article: find this article) when handling Configuration, here we are two deep Registrar source:

  • ConfigurationPropertiesBeanRegistrar
  • ConfigurationPropertiesBindingPostProcessorRegistrar

    2.2.1 ConfigurationPropertiesBindingPostProcessorRegistrar

    直接看代码

public class ConfigurationPropertiesBindingPostProcessorRegistrar implements ImportBeanDefinitionRegistrar {

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

    private void registerConfigurationPropertiesBindingPostProcessor(BeanDefinitionRegistry registry) {
        GenericBeanDefinition definition = new GenericBeanDefinition();
        definition.setBeanClass(ConfigurationPropertiesBindingPostProcessor.class);
        definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        registry.registerBeanDefinition(ConfigurationPropertiesBindingPostProcessor.BEAN_NAME, definition);
    }

    private void registerConfigurationBeanFactoryMetadata(BeanDefinitionRegistry registry) {
        GenericBeanDefinition definition = new GenericBeanDefinition();
        definition.setBeanClass(ConfigurationBeanFactoryMetadata.class);
        definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        registry.registerBeanDefinition(ConfigurationBeanFactoryMetadata.BEAN_NAME, definition);
    }
}

Registration can see the two container Bean to spring

  • ConfigurationPropertiesBindingPostProcessor
    • 其实现如下接口:
      BeanPostProcessor, PriorityOrdered, ApplicationContextAware, InitializingBean

        PriorityOrdered
                                Ordered.HIGHEST_PRECEDENCE + 1 to ensure the early implementation of, and not the first

        ApplicationContextAware
                                Gets the ApplicationContext is set to the internal variable

        InitializingBean
                                afterPropertiesSet method is called when the Bean is created, ensure that the internal variables are initialized configurationPropertiesBinder, this binder class is to make the prefix and propertyBean a value binding key tools

        BeanPostProcessor
                                postProcessBeforeInitialization bind specific method for processing logic is as follows:

      @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;
      }
      private void bind(Object bean, String beanName, ConfigurationProperties annotation) {
          ResolvableType type = getBeanType(bean, beanName);
          Validated validated = getAnnotation(bean, beanName, Validated.class);
          Annotation[] annotations = (validated != null) ? new Annotation[] { annotation, validated }
                  : new Annotation[] { annotation };
          Bindable target = Bindable.of(type).withExistingValue(bean).withAnnotations(annotations);
          try {
              // 在这里完成了,关键的prefix到PropertyBean的值绑定部分,所以各种@ConfigurationProperties注解最终生效就靠这部分代码了
              this.configurationPropertiesBinder.bind(target);
          }
          catch (Exception ex) {
              throw new ConfigurationPropertiesBindException(beanName, bean, annotation, ex);
          }
      }
  • ConfigurationBeanFactoryMetadata
            If some of the Bean is created by FactoryBean, the class used to store a variety of original FactoryBean information, metadata ConfigurationPropertiesBindingPostProcessor among the queries do not expand here

2.2.2 ConfigurationPropertiesBeanRegistrar

In fact ConfigurationPropertiesBeanRegistrar is EnableConfigurationPropertiesImportSelector internal static class is omitted in the front part of the code stickers on the code

    public static class ConfigurationPropertiesBeanRegistrar implements ImportBeanDefinitionRegistrar {

        @Override
        public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
            getTypes(metadata).forEach((type) -> register(registry, (ConfigurableListableBeanFactory) registry, type));
        }

        private List> getTypes(AnnotationMetadata metadata) {
            MultiValueMap attributes = metadata
                    .getAllAnnotationAttributes(EnableConfigurationProperties.class.getName(), false);
            return collectClasses((attributes != null) ? attributes.get("value") : Collections.emptyList());
        }

        private List> collectClasses(List values) {
            return values.stream().flatMap((value) -> Arrays.stream((Object[]) value)).map((o) -> (Class) o)
                    .filter((type) -> void.class != type).collect(Collectors.toList());
        }

        private void register(BeanDefinitionRegistry registry, ConfigurableListableBeanFactory beanFactory,
                Class type) {
            String name = getName(type);
            if (!containsBeanDefinition(beanFactory, name)) {
                registerBeanDefinition(registry, name, type);
            }
        }

        private String getName(Class type) {
            ConfigurationProperties annotation = AnnotationUtils.findAnnotation(type, ConfigurationProperties.class);
            String prefix = (annotation != null) ? annotation.prefix() : "";
            return (StringUtils.hasText(prefix) ? prefix + "-" + type.getName() : type.getName());
        }

        private void registerBeanDefinition(BeanDefinitionRegistry registry, String name, Class type) {
            assertHasAnnotation(type);
            GenericBeanDefinition definition = new GenericBeanDefinition();
            definition.setBeanClass(type);
            registry.registerBeanDefinition(name, definition);
        }

        private void assertHasAnnotation(Class type) {...}
        private boolean containsBeanDefinition(ConfigurableListableBeanFactory beanFactory, String name) {...}
    }

Logic Interpretation:
    A main logic inlet (registerBeanDefinitions)
    1 -> getTypes (metadata) to get the configuration values ​​marked EnableConfigurationProperties annotated, organized into List > and then processed one by one?
    2 -> register call processing method for each element (Class <>?)
    3 -?> Register ConfigurationProperties class method by which the notes of value-add name as a prefix to call the Class beanName by registry.registerBeanDefinition <> registered to the registry of them
    When the marked notes @ConfigurationProperties of XXXProperties of BeanDefinition added to the registry in, Bean initialization handed spring container,
    And ConfigurationPropertiesBindingPostProcessorRegistrar the process mentioned above to complete a series of post-operation to help us complete the final value binding

3. Summary

@ConfigurationProperties whole process, this article has been basically completed about now roughly summarize:
    EnableConfigurationProperties completion of the introduction of ConfigurationPropertiesBindingPostProcessorRegistrar and ConfigurationPropertiesBeanRegistrar
    among them:

    ConfigurationPropertiesBeanRegistrar complete labeling @ConfigurationProperties look like and assembled into BeanDefinition join registry

  • ConfigurationPropertiesBindingPostProcessorRegistrar完成ConfigurationPropertiesBindingPostProcessor及ConfigurationBeanFactoryMetadata

      ConfigurationPropertiesBindingPostProcessor complete all marked @ConfigurationProperties of Bean to prefix the value binding properties

    • ConfigurationBeanFactoryMetadata only some metadata for providing the information required for processing the above

      4. On the other article

      https://github.com/zrbcool/blog-public

The micro-channel subscription number

Leave a Reply