0. Open source project recommendation

Pepper Metrics is an open source tool I developed with my colleagues (github.com/zrbcool/pep…) , by collecting jedis mybatis/httpservlet/dubbo/motan performance statistics, and exposure to Prometheus and other mainstream temporal database compatible data, through grafana show the trend. Its plug-in architecture also makes it easy for users to extend and integrate other open source components. Please give us a STAR, and we welcome you to become developers to submit PR and improve the project together.

1. An overview of the

Needless to say, we all know that Spring Boot is very convenient and fast, so that students can start and use a project with a few lines of code and a few lines of configuration or even zero configuration. We may also frequently use @ConfigurationProperties to bind a Bean to the prefix in the properties configuration, separating the configuration value from the Bean defining the configuration for easier management. So, what is this @ConfigurationProperties mechanism and how is it implemented? Let’s talk about that today

2. The body

2.1 speaking EnableConfigurationProperties

Why tell from EnableConfigurationProperties? Spring in itself a lot of autoconfigure Boot project is using EnableConfigurationProperties annotations enable XXXProperties function, For example, spring-data-Redis has this RedisAutoConfiguration

@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class) / / look here
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
    // ...
}
Copy the code

The RedisProperties annotation @configurationProperties (prefix = “spring.redis”), This binds the configuration item with the prefix Spring. redis to the entity class RedisProperties.

2.2 EnableConfigurationProperties internal implementation

With that said, let’s talk about internal implementation. Let’s look at it first

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(EnableConfigurationPropertiesImportSelector.class)
public @interfaceEnableConfigurationProperties { Class<? >[] value()default {};
}
Copy the code

@ Import (EnableConfigurationPropertiesImportSelector. Class) indicate the annotation handler class EnableConfigurationPropertiesImportSelector, Check the 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;
	}
	
	// omit some other methods
}
Copy the code

We look at the key part of the return an array of IMPORTS, ConfigurationPropertiesBeanRegistrar contained in the array. The class, ConfigurationPropertiesBindingPostProcessorRegistrar. Class two elements According to the principle of @ Import and ImportSelector interface (the principle of which can consult colleagues to write an article: Loving @ Import and @ EnableXXX), we learn that spring will initialize the above two the Registrar to the spring container, and both the Registrar has realized ImportBeanDefinitionRegistrar interface, And ImportBeanDefinitionRegistrar would trigger calls when dealing with the Configuration (the principle can refer to the article: this find an article), here we respectively into two Registrar’s source code:

  • ConfigurationPropertiesBeanRegistrar
  • ConfigurationPropertiesBindingPostProcessorRegistrar

2.2.1 ConfigurationPropertiesBindingPostProcessorRegistrar

Look directly at the code

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 = newGenericBeanDefinition(); definition.setBeanClass(ConfigurationBeanFactoryMetadata.class); definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); registry.registerBeanDefinition(ConfigurationBeanFactoryMetadata.BEAN_NAME, definition); }}Copy the code

You can see that two beans are registered to the Spring container

  • ConfigurationPropertiesBindingPostProcessor
    • It implements the following interfaces: BeanPostProcessor, PriorityOrdered, ApplicationContextAware, InitializingBean
      • PriorityOrdered Ordered.HIGHEST_PRECEDENCE + 1 Ensures early execution, but not the first one
      • ApplicationContextAware retrieves ApplicationContext Settings into internal variables
      • InitializingBean afterPropertiesSet method is invoked when Bean creation, ensure internal variables configurationPropertiesBinder is initialized, This Binder class is the key utility class that enables prefix to value bind with propertyBean
      • BeanPostProcessor postProcessBeforeInitialization method of dealing with the specific bind 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 }
                  : newAnnotation[] { annotation }; Bindable<? > target = Bindable.of(type).withExistingValue(bean).withAnnotations(annotations);try {
              // This is where the key prefix to PropertyBean value binding is done, so it's here that the various @ConfigurationProperties annotations finally take effect
              this.configurationPropertiesBinder.bind(target);
          }
          catch (Exception ex) {
              throw newConfigurationPropertiesBindException(beanName, bean, annotation, ex); }}Copy the code
  • ConfigurationBeanFactoryMetadata if some Bean is created through FactoryBean, this class is used to save FactoryBean various original information, Used for ConfigurationPropertiesBindingPostProcessor metadata of query, here is not to do

2.2.2 ConfigurationPropertiesBeanRegistrar

Actually ConfigurationPropertiesBeanRegistrar EnableConfigurationPropertiesImportSelector static inner class, in the previous post code omitted part of 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));
		}

		privateList<Class<? >> getTypes(AnnotationMetadata metadata) { MultiValueMap<String, Object> attributes = metadata .getAllAnnotationAttributes(EnableConfigurationProperties.class.getName(),false);
			returncollectClasses((attributes ! =null)? attributes.get("value") : Collections.emptyList());
		}

		privateList<Class<? >> collectClasses(List<? > values) {returnvalues.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) {...}
	}
Copy the code

Logical interpretation: main logic entrance (registerBeanDefinitions) 1 – > the getTypes (metadata) to indicate EnableConfigurationProperties annotation configuration values, into a List < Class
> Then process 2 -> for each element (Class
Register method of using the Class ConfigurationProperties annotation prefix value added Class name as beanName by registry. RegisterBeanDefinition call Class

3. Summary

The overall processing process of @ConfigurationProperties is basically described in this article. Now we can summarize it in general: Complete ConfigurationPropertiesBindingPostProcessorRegistrar and ConfigurationPropertiesBeanRegistra EnableConfigurationProperties Where r is introduced:

  • ConfigurationPropertiesBeanRegistrar complete annotation @ ConfigurationProperties class lookup and assembled into BeanDefinition join the registry
  • Complete ConfigurationPropertiesBindingPostProcessor and ConfigurationBeanFacto ConfigurationPropertiesBindingPostProcessorRegistrar ryMetadata
    • @ ConfigurationProperties ConfigurationPropertiesBindingPostProcessor to complete all the labeling of beans to prefix the properties value binding
    • ConfigurationBeanFactoryMetadata is only used to provide the above need some metadata information processing

4. Other articles by author

Github.com/zrbcool/blo…

5. Wechat subscription account