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

開源項目推薦

Pepper Metrics是我與同事開發的一個開源工具(https://github.com/zrbcool/pepper-metrics),其通過收集jedis/mybatis/httpservlet/dubbo/motan的運行性能統計,並暴露成prometheus等主流時序數據庫兼容數據,通過grafana展示趨勢。其插件化的架構也非常方便使用者擴展並集成其他開源組件。
請大家給個star,同時歡迎大家成爲開發者提交PR一起完善項目。

  1. 概述

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

  1. 正文

2.1 從EnableConfigurationProperties說起

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

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

// ...

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

2.2 EnableConfigurationProperties內部實現解析

說完來由,我們就來說說內部實現,先來看看

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

Class<?>[] value() default {};

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

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的靜態內部類,在前面貼代碼時被省略的部分,上代碼

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然後逐個處理 2 -> 對每個元素(Class)調用register方法處理
3 -> register方法通過類當中的ConfigurationProperties註解的prefix值加類名字作爲beanName通過registry.registerBeanDefinition調用將Class<?>註冊到registry當中
當標註註解@ConfigurationProperties的XXXProperties的BeanDefinition加入到registry中後,Bean的初始化就交給spring容器, 而這個過程中前面提到的ConfigurationPropertiesBindingPostProcessorRegistrar就完成一系列的後置操作幫助我們完成最終的值綁定

  1. 總結

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

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

  1. 作者其他文章

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章