Spring Boot中@ConfigurationProperties註解實現原理源碼解析

0. 開源項目推薦

Pepper Metrics是我與同事開發的一個開源工具(github.com/zrbcool/pep…),其經過收集jedis/mybatis/httpservlet/dubbo/motan的運行性能統計,並暴露成prometheus等主流時序數據庫兼容數據,經過grafana展現趨勢。其插件化的架構也很是方便使用者擴展並集成其餘開源組件。
請你們給個star,同時歡迎你們成爲開發者提交PR一塊兒完善項目。html

1. 概述

不用說你們都知道Spring Boot很是的方便,快捷,讓開發的同窗簡單的幾行代碼加上幾行配置甚至零配置就能啓動並使用一個項目,項目當中咱們也可能常常使用 @ConfigurationProperties將某個Bean與properties配置當中的prefix相綁定,使配置值與定義配置的Bean分離,方便管理。
那麼,這個@ConfigurationProperties是什麼機制,如何實現的呢?咱們今天來聊聊這個話題java

2. 正文

2.1 從EnableConfigurationProperties提及

爲何從EnableConfigurationProperties講? Spring Boot項目自身當中大量autoconfigure都是使用EnableConfigurationProperties註解啓用XXXProperties功能,例如spring-data-redis的 這個RedisAutoConfigurationgit

@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class) //看這裏
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
    // ...
}
複製代碼

而RedisProperties中又帶有註解@ConfigurationProperties(prefix = "spring.redis"),這樣就將spring.redis這個前綴的配置項與RedisProperties 這個實體類進行了綁定。github

2.2 EnableConfigurationProperties內部實現解析

說完來由,咱們就來講說內部實現,先來看看redis

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(EnableConfigurationPropertiesImportSelector.class)
public @interface EnableConfigurationProperties {
	Class<?>[] value() default {};
}
複製代碼

@Import(EnableConfigurationPropertiesImportSelector.class)指明瞭這個註解的處理類EnableConfigurationPropertiesImportSelector, 查看EnableConfigurationPropertiesImportSelector源碼spring

class EnableConfigurationPropertiesImportSelector implements ImportSelector {

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

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

咱們先看這塊關鍵部分返回了一個IMPORTS數組,數組中包含ConfigurationPropertiesBeanRegistrar.class,ConfigurationPropertiesBindingPostProcessorRegistrar.class兩個元素
根據@Import及ImportSelector接口的原理(其原理能夠參考同事寫的一篇文章:相親相愛的@Import和@EnableXXX),咱們得知spring會初始化上面兩個Registrar到spring容器當中,而兩個Registrar均實現了ImportBeanDefinitionRegistrar接口, 而ImportBeanDefinitionRegistrar會在處理Configuration時觸發調用(其原理能夠參考文章:這塊找一篇文章),下面咱們分別深刻兩個Registrar的源碼:數據庫

  • 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);
	}
}
複製代碼

能夠看到註冊了兩個Bean到spring容器微信

  • ConfigurationPropertiesBindingPostProcessor
    • 其實現以下接口: BeanPostProcessor, PriorityOrdered, ApplicationContextAware, InitializingBean
      • PriorityOrdered
        Ordered.HIGHEST_PRECEDENCE + 1保證前期執行,且非最早
      • ApplicationContextAware
        獲取到ApplicationContext設置到內部變量
      • InitializingBean
        afterPropertiesSet方法在Bean建立時被調用,保證內部變量configurationPropertiesBinder被初始化,這個binder類就是使prefix與propertyBean進行值綁定的關鍵工具類
      • BeanPostProcessor postProcessBeforeInitialization方法處理具體的bind邏輯以下:
@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
    若是某些Bean是經過FactoryBean建立,則該類用於保存FactoryBean的各類原信息,用於ConfigurationPropertiesBindingPostProcessor當中的元數據查詢,這裏就不作展開

2.2.2 ConfigurationPropertiesBeanRegistrar

其實ConfigurationPropertiesBeanRegistrar是EnableConfigurationPropertiesImportSelector的靜態內部類,在前面貼代碼時被省略的部分,上代碼mybatis

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<Class<?>> getTypes(AnnotationMetadata metadata) {
			MultiValueMap<String, Object> attributes = metadata
					.getAllAnnotationAttributes(EnableConfigurationProperties.class.getName(), false);
			return collectClasses((attributes != null) ? attributes.get("value") : Collections.emptyList());
		}

		private List<Class<?>> 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) {...}
	}
複製代碼

邏輯解讀:
主邏輯入口(registerBeanDefinitions) 1 -> getTypes(metadata)拿到標註EnableConfigurationProperties註解的配置值,整理成List<Class<?>>而後逐個處理 2 -> 對每一個元素(Class<?>)調用register方法處理 3 -> register方法經過類當中的ConfigurationProperties註解的prefix值加類名字做爲beanName經過registry.registerBeanDefinition調用將Class<?>註冊到registry當中 當標註註解@ConfigurationProperties的XXXProperties的BeanDefinition加入到registry中後,Bean的初始化就交給spring容器, 而這個過程當中前面提到的ConfigurationPropertiesBindingPostProcessorRegistrar就完成一系列的後置操做幫助咱們完成最終的值綁定

3. 總結

@ConfigurationProperties的總體處理過程,本文已經基本講述完畢,如今大致總結一下: EnableConfigurationProperties完成ConfigurationPropertiesBindingPostProcessorRegistrar及ConfigurationPropertiesBeanRegistrar的引入 其中:

  • ConfigurationPropertiesBeanRegistrar完成標註@ConfigurationProperties的類的查找並組裝成BeanDefinition加入registry
  • ConfigurationPropertiesBindingPostProcessorRegistrar完成ConfigurationPropertiesBindingPostProcessor及ConfigurationBeanFactoryMetadata
    • ConfigurationPropertiesBindingPostProcessor完成全部標註@ConfigurationProperties的Bean到prefix的properties值綁定
    • ConfigurationBeanFactoryMetadata僅用於提供上面處理中須要的一些元數據信息

4. 做者其餘文章

github.com/zrbcool/blo…

5. 微信訂閱號

相關文章
相關標籤/搜索