外部配置屬性值是如何被綁定到XxxProperties類屬性上的?--SpringBoot源碼(五)

注:該源碼分析對應SpringBoot版本爲2.1.0.RELEASEjava

1 前言

本篇接 SpringBoot是如何實現自動配置的?--SpringBoot源碼(四)web

溫故而知新,咱們來簡單回顧一下上篇的內容,上一篇咱們分析了SpringBoot的自動配置的相關源碼,自動配置相關源碼主要有如下幾個重要的步驟:spring

  1. 從spring.factories配置文件中加載自動配置類;
  2. 加載的自動配置類中排除掉@EnableAutoConfiguration註解的exclude屬性指定的自動配置類;
  3. 而後再用AutoConfigurationImportFilter接口去過濾自動配置類是否符合其標註註解(如有標註的話)@ConditionalOnClass,@ConditionalOnBean@ConditionalOnWebApplication的條件,若都符合的話則返回匹配結果;
  4. 而後觸發AutoConfigurationImportEvent事件,告訴ConditionEvaluationReport條件評估報告器對象來分別記錄符合條件和exclude的自動配置類。
  5. 最後spring再將最後篩選後的自動配置類導入IOC容器中

本篇繼續來分析SpringBoot的自動配置的相關源碼,咱們來分析下@EnableConfigurationProperties@EnableConfigurationProperties這兩個註解,來探究下外部配置屬性值是如何被綁定到@ConfigurationProperties註解的類屬性中的?bootstrap

舉個栗子:以配置web項目的服務器端口爲例,若咱們要將服務器端口配置爲 8081,那麼咱們會在 application.properties配置文件中配置 server.port=8081,此時該配置值 8081就將會綁定到被 @ConfigurationProperties註解的類 ServerProperties的屬性 port上,從而使得配置生效。

2 @EnableConfigurationProperties

咱們接着前面的設置服務器端口的栗子來分析,咱們先直接來看看ServerProperties的源碼,應該能找到源碼的入口:設計模式

@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties {
    /**
     * Server HTTP port.
     */
    private Integer port;
    // ...省略非關鍵代碼
}

能夠看到,ServerProperties類上標註了@ConfigurationProperties這個註解,服務器屬性配置前綴爲server,是否忽略未知的配置值(ignoreUnknownFields)設置爲true數組

那麼咱們再來看下@ConfigurationProperties這個註解的源碼:緩存

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ConfigurationProperties {
    
    // 前綴別名
    @AliasFor("prefix")
    String value() default "";
    
    // 前綴
    @AliasFor("value")
    String prefix() default "";
    
    // 忽略無效的配置屬性
    boolean ignoreInvalidFields() default false;
    
    // 忽略未知的配置屬性
    boolean ignoreUnknownFields() default true;
}

@ConfigurationProperties這個註解的做用就是將外部配置的配置值綁定到其註解的類的屬性上,能夠做用於配置類或配置類的方法上。能夠看到@ConfigurationProperties註解除了有設置前綴,是否忽略一些不存在或無效的配置等屬性等外,這個註解沒有其餘任何的處理邏輯,能夠看到@ConfigurationProperties是一個標誌性的註解,源碼入口不在這裏服務器

這裏講的是服務器的自動配置,天然而然的,咱們來看下自動配置類ServletWebServerFactoryAutoConfiguration的源碼:app

@Configuration
@EnableConfigurationProperties(ServerProperties.class)
// ...省略非關鍵註解
public class ServletWebServerFactoryAutoConfiguration {
    // ...省略非關鍵代碼
}

爲了突出重點,我已經把ServletWebServerFactoryAutoConfiguration的非關鍵代碼和非關鍵註解省略掉了。能夠看到,ServletWebServerFactoryAutoConfiguration自動配置類中有一個@EnableConfigurationProperties註解,且註解值是前面講的ServerProperties.class,所以@EnableConfigurationProperties註解確定就是咱們關注的重點了。ide

一樣,再來看下@EnableConfigurationProperties註解的源碼:

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

    // 這個值指定的類就是@ConfigurationProperties註解標註的類,其將會被註冊到spring容器中
    Class<?>[] value() default {};

}

@EnableConfigurationProperties註解的主要做用就是爲@ConfigurationProperties註解標註的類提供支持,即對將外部配置屬性值(好比application.properties配置值)綁定到@ConfigurationProperties標註的類的屬性中。

注意:SpringBoot源碼中還存在了 ConfigurationPropertiesAutoConfiguration這個自動配置類,同時 spring.factories配置文件中的 EnableAutoConfiguration接口也配置了 ConfigurationPropertiesAutoConfiguration,這個自動配置類上也有 @EnableConfigurationProperties這個註解,堆屬性綁定進行了默認開啓。

那麼,@EnableConfigurationProperties這個註解對屬性綁定提供怎樣的支持呢?

能夠看到@EnableConfigurationProperties這個註解上還標註了@Import(EnableConfigurationPropertiesImportSelector.class),其導入了EnableConfigurationPropertiesImportSelector,所以能夠確定的是@EnableConfigurationProperties這個註解對屬性綁定提供的支持一定跟EnableConfigurationPropertiesImportSelector有關。

到了這裏,EnableConfigurationPropertiesImportSelector這個哥們是咱們接下來要分析的對象,那麼咱們下面繼續來分析EnableConfigurationPropertiesImportSelector是如何承擔將外部配置屬性值綁定到@ConfigurationProperties標註的類的屬性中的。

3 EnableConfigurationPropertiesImportSelector

EnableConfigurationPropertiesImportSelector類的做用主要用來處理外部屬性綁定的相關邏輯,其實現了ImportSelector接口,咱們都知道,實現ImportSelector接口的selectImports方法能夠向容器中註冊bean。

那麼,咱們來看下EnableConfigurationPropertiesImportSelector覆寫的selectImports方法:

// EnableConfigurationPropertiesImportSelector.java

class EnableConfigurationPropertiesImportSelector implements ImportSelector {
        // IMPORTS數組便是要向spring容器中註冊的bean
    private static final String[] IMPORTS = {
            ConfigurationPropertiesBeanRegistrar.class.getName(),
            ConfigurationPropertiesBindingPostProcessorRegistrar.class.getName() };

    @Override
    public String[] selectImports(AnnotationMetadata metadata) {
        // 返回ConfigurationPropertiesBeanRegistrar和ConfigurationPropertiesBindingPostProcessorRegistrar的全限定名
        // 即上面兩個類將會被註冊到Spring容器中
        return IMPORTS;
    }
    
}

能夠看到EnableConfigurationPropertiesImportSelector類中的selectImports方法中返回的是IMPORTS數組,而這個IMPORTS是一個常量數組,值是ConfigurationPropertiesBeanRegistrarConfigurationPropertiesBindingPostProcessorRegistrar。即EnableConfigurationPropertiesImportSelector的做用是向Spring容器中註冊了ConfigurationPropertiesBeanRegistrarConfigurationPropertiesBindingPostProcessorRegistrar這兩個bean

咱們在EnableConfigurationPropertiesImportSelector類中沒看處處理外部屬性綁定的相關邏輯,其只是註冊了ConfigurationPropertiesBeanRegistrarConfigurationPropertiesBindingPostProcessorRegistrar這兩個bean,接下來咱們再看下注冊的這兩個bean類。

4 ConfigurationPropertiesBeanRegistrar

咱們先來看下ConfigurationPropertiesBeanRegistrar這個類。

ConfigurationPropertiesBeanRegistrarEnableConfigurationPropertiesImportSelector的內部類,其實現了ImportBeanDefinitionRegistrar接口,覆寫了registerBeanDefinitions方法。可見,ConfigurationPropertiesBeanRegistrar又是用來註冊一些bean definition的,即也是向Spring容器中註冊一些bean。

先看下ConfigurationPropertiesBeanRegistrar的源碼:

// ConfigurationPropertiesBeanRegistrar$ConfigurationPropertiesBeanRegistrar.java

public static class ConfigurationPropertiesBeanRegistrar
            implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata,  // metadata是AnnotationMetadataReadingVisitor對象,存儲了某個配置類的元數據
            BeanDefinitionRegistry registry) {
        // (1)獲得@EnableConfigurationProperties註解的全部屬性值,
        // 好比@EnableConfigurationProperties(ServerProperties.class),那麼獲得的值是ServerProperties.class
        // (2)而後再將獲得的@EnableConfigurationProperties註解的全部屬性值註冊到容器中
        getTypes(metadata).forEach((type) -> register(registry,
                (ConfigurableListableBeanFactory) registry, type));
    }
}

ConfigurationPropertiesBeanRegistrar實現的registerBeanDefinitions中,能夠看到主要作了兩件事:

  1. 調用getTypes方法獲取@EnableConfigurationProperties註解的屬性值XxxProperties
  2. 調用register方法將獲取的屬性值XxxProperties註冊到Spring容器中,用於之後和外部屬性綁定時使用。

咱們來看下getTypes方法的源碼:

// ConfigurationPropertiesBeanRegistrar$ConfigurationPropertiesBeanRegistrar.java

private List<Class<?>> getTypes(AnnotationMetadata metadata) {
    // 獲得@EnableConfigurationProperties註解的全部屬性值,
    // 好比@EnableConfigurationProperties(ServerProperties.class),那麼獲得的值是ServerProperties.class
    MultiValueMap<String, Object> attributes = metadata
            .getAllAnnotationAttributes(
                    EnableConfigurationProperties.class.getName(), false);
    // 將屬性值取出裝進List集合並返回
    return collectClasses((attributes != null) ? attributes.get("value")
            : Collections.emptyList());
}

getTypes方法裏面的邏輯很簡單即將@EnableConfigurationProperties註解裏面的屬性值XxxProperties(好比ServerProperties.class)取出並裝進List集合並返回。

getTypes方法拿到@EnableConfigurationProperties註解裏面的屬性值XxxProperties(好比ServerProperties.class)後,此時再遍歷將XxxProperties逐個註冊進Spring容器中,咱們來看下register方法:

// ConfigurationPropertiesBeanRegistrar$ConfigurationPropertiesBeanRegistrar.java

private void register(BeanDefinitionRegistry registry,
        ConfigurableListableBeanFactory beanFactory, Class<?> type) {
    // 獲得type的名字,通常用類的全限定名做爲bean name
    String name = getName(type);
    // 根據bean name判斷beanFactory容器中是否包含該bean
    if (!containsBeanDefinition(beanFactory, name)) {
        // 若不包含,那麼註冊bean definition
        registerBeanDefinition(registry, name, type);
    }
}

咱們再來看下由EnableConfigurationPropertiesImportSelector導入的另外一個類ConfigurationPropertiesBindingPostProcessorRegistrar又是幹嗎的呢?

5 ConfigurationPropertiesBindingPostProcessorRegistrar

能夠看到ConfigurationPropertiesBindingPostProcessorRegistrar類名字又是以Registrar單詞爲結尾,說明其確定又是導入一些bean definition的。直接看源碼:

// ConfigurationPropertiesBindingPostProcessorRegistrar.java

public class ConfigurationPropertiesBindingPostProcessorRegistrar
        implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
            BeanDefinitionRegistry registry) {
        // 若容器中沒有註冊ConfigurationPropertiesBindingPostProcessor這個處理屬性綁定的後置處理器,
        // 那麼將註冊ConfigurationPropertiesBindingPostProcessor和ConfigurationBeanFactoryMetadata這兩個bean
        // 注意onApplicationEnvironmentPreparedEvent事件加載配置屬性在先,而後再註冊一些後置處理器用來處理這些配置屬性
        if (!registry.containsBeanDefinition(
                ConfigurationPropertiesBindingPostProcessor.BEAN_NAME)) {
            // (1)註冊ConfigurationPropertiesBindingPostProcessor後置處理器,用來對配置屬性進行後置處理
            registerConfigurationPropertiesBindingPostProcessor(registry);
            // (2)註冊一個ConfigurationBeanFactoryMetadata類型的bean,
            // 注意ConfigurationBeanFactoryMetadata實現了BeanFactoryPostProcessor,而後其會在postProcessBeanFactory中註冊一些元數據
            registerConfigurationBeanFactoryMetadata(registry);
        }
    }
    // 註冊ConfigurationPropertiesBindingPostProcessor後置處理器
    private void registerConfigurationPropertiesBindingPostProcessor(
            BeanDefinitionRegistry registry) {
        GenericBeanDefinition definition = new GenericBeanDefinition();
        definition.setBeanClass(ConfigurationPropertiesBindingPostProcessor.class);
        definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        registry.registerBeanDefinition(
                ConfigurationPropertiesBindingPostProcessor.BEAN_NAME, definition);

    }
    // 註冊ConfigurationBeanFactoryMetadata後置處理器
    private void registerConfigurationBeanFactoryMetadata(
            BeanDefinitionRegistry registry) {
        GenericBeanDefinition definition = new GenericBeanDefinition();
        definition.setBeanClass(ConfigurationBeanFactoryMetadata.class);
        definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        registry.registerBeanDefinition(ConfigurationBeanFactoryMetadata.BEAN_NAME,
                definition);
    }

}

ConfigurationPropertiesBindingPostProcessorRegistrar類的邏輯很是簡單,主要用來註冊外部配置屬性綁定相關的後置處理器即ConfigurationBeanFactoryMetadataConfigurationPropertiesBindingPostProcessor

那麼接下來咱們再來探究下注冊的這兩個後置處理器又是執行怎樣的後置處理邏輯呢?

6 ConfigurationBeanFactoryMetadata

先來看ConfigurationBeanFactoryMetadata這個後置處理器,其實現了BeanFactoryPostProcessor接口的postProcessBeanFactory方法,在初始化bean factory時將@Bean註解的元數據存儲起來,以便在後續的外部配置屬性綁定的相關邏輯中使用。

先來看下ConfigurationBeanFactoryMetadata類實現BeanFactoryPostProcessor接口的postProcessBeanFactory方法源碼:

// ConfigurationBeanFactoryMetadata

public class ConfigurationBeanFactoryMetadata implements BeanFactoryPostProcessor {

    /**
     * The bean name that this class is registered with.
     */
    public static final String BEAN_NAME = ConfigurationBeanFactoryMetadata.class
            .getName();

    private ConfigurableListableBeanFactory beanFactory;
    /**
     * beansFactoryMetadata集合存儲beansFactory的元數據
     * key:某個bean的名字  value:FactoryMetadata對象(封裝了工廠bean名和工廠方法名)
     * 好比下面這個配置類:
     *
     * @Configuration
     * public class ConfigA {
     *      @Bean
     *      public BeanXXX methodB(configA, ) {
     *          return new BeanXXX();
     *      }
     * }
     *
     * 那麼:key值爲"methodB",value爲FactoryMetadata(configA, methodB)對象,其bean屬性值爲"configA",method屬性值爲"methodB"
     */
    private final Map<String, FactoryMetadata> beansFactoryMetadata = new HashMap<>();

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
            throws BeansException {
        this.beanFactory = beanFactory;
        // 遍歷beanFactory的beanDefinitionName,即每一個bean的名字(好比工廠方法對應的bean名字)
        for (String name : beanFactory.getBeanDefinitionNames()) {
            // 根據name獲得beanDefinition
            BeanDefinition definition = beanFactory.getBeanDefinition(name);
            // 工廠方法名:通常是註解@Bean的方法名
            String method = definition.getFactoryMethodName();
            // 工廠bean名:通常是註解@Configuration的類名
            String bean = definition.getFactoryBeanName();
            if (method != null && bean != null) {
                // 將beanDefinitionName做爲Key,封裝了工廠bean名和工廠方法名的FactoryMetadata對象做爲value裝入beansFactoryMetadata中
                this.beansFactoryMetadata.put(name, new FactoryMetadata(bean, method));
            }
        }
    }
}

從上面代碼能夠看到ConfigurationBeanFactoryMetadata類覆寫的postProcessBeanFactory方法作的事情就是將工廠Bean(能夠理解爲@Configuration註解的類)及其@Bean註解的工廠方法的一些元數據緩存到beansFactoryMetadata集合中,以便後續使用,這個後面會詳述。

由上代碼中咱們看到了ConfigurationBeanFactoryMetadata類的beansFactoryMetadata集合類型是Map<String, FactoryMetadata>,那麼咱們再來看下封裝相關工廠元數據的FactoryMetadata類:

// ConfigurationBeanFactoryMetadata$FactoryMetadata.java

private static class FactoryMetadata {
    // @Configuration註解的配置類的類名
    private final String bean;
    // @Bean註解的方法名
    private final String method;

    FactoryMetadata(String bean, String method) {
        this.bean = bean;
        this.method = method;
    }

    public String getBean() {
        return this.bean;
    }

    public String getMethod() {
        return this.method;
    }

}

FactoryMetadata僅有兩個屬性beanmethod,分別表示@Configuration註解的工廠bean@Bean註解的工廠方法。

上面說了那麼多,直接舉個栗子會更直觀:

/**
 * beansFactoryMetadata集合存儲beansFactory的元數據
 * key:某個bean的名字  value:FactoryMetadata對象(封裝了工廠bean名和工廠方法名)
 * 好比下面這個配置類:
 *
 * @Configuration
 * public class ConfigA {
 *      @Bean
 *      public BeanXXX methodB(configA, ) {
 *          return new BeanXXX();
 *      }
 * }
 *
 * 那麼:key值爲"methodB",value爲FactoryMetadata(configA, methodB)對象,其bean屬性值爲"configA",method屬性值爲"methodB"
 */
 private final Map<String, FactoryMetadata> beansFactoryMetadata = new HashMap<>();

爲了更好理解上面beansFactoryMetadata集合存儲的數據是啥,建議最好本身動手調試看看其裏面裝的是什麼哦。總之這裏記住一點就行了:ConfigurationBeanFactoryMetadata類的beansFactoryMetadata集合存儲的是工廠bean的相關元數據,以便在ConfigurationPropertiesBindingPostProcessor後置處理器中使用。

7 ConfigurationPropertiesBindingPostProcessor

咱們再來看下ConfigurationPropertiesBindingPostProcessorRegistrar類註冊的另一個後置處理器ConfigurationPropertiesBindingPostProcessor,這個後置處理器就尤爲重要了,主要承擔了將外部配置屬性綁定到@ConfigurationProperties註解標註的XxxProperties類的屬性中(好比application.properties配置文件中設置了server.port=8081,那麼8081將會綁定到ServerProperties類的port屬性中)的實現邏輯。

一樣,先來看下ConfigurationPropertiesBindingPostProcessor的源碼:

// ConfigurationPropertiesBindingPostProcessor.java

public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProcessor,
    PriorityOrdered, ApplicationContextAware, InitializingBean {
    @Override
    public void afterPropertiesSet() throws Exception {
        // ...這裏省略實現代碼先
    }
    
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        // ...這裏省略實現代碼先
    }
    
    // ...省略非關鍵代碼
}

能夠看到ConfigurationPropertiesBindingPostProcessor後置處理器實現了兩個重要的接口InitializingBeanBeanPostProcessor

咱們都知道:
  1. InitializingBean接口的afterPropertiesSet方法會在bean屬性賦值後調用,用來執行一些自定義的初始化邏輯好比檢查某些強制的屬性是否有被賦值,校驗某些配置或給一些未被賦值的屬性賦值。
  2. BeanPostProcessor接口是bean的後置處理器,其有postProcessBeforeInitializationpostProcessAfterInitialization兩個勾子方法,分別會在bean初始化先後被調用來執行一些後置處理邏輯,好比檢查標記接口或是否用代理包裝了bean

同時由上代碼能夠看到ConfigurationPropertiesBindingPostProcessor後置處理器覆寫了InitializingBeanafterPropertiesSet方法和BeanPostProcessorpostProcessBeforeInitialization方法。

接下來咱們再來探究ConfigurationPropertiesBindingPostProcessor後置處理器覆寫的兩個方法的源碼。

7.1 在執行外部屬性綁定邏輯前先準備好相關元數據和配置屬性綁定器

咱們先來分析下ConfigurationPropertiesBindingPostProcessor覆寫InitializingBean接口的afterPropertiesSet方法:

// ConfigurationPropertiesBindingPostProcessor.java

        /**
     * 配置屬性校驗器名字
     */
    public static final String VALIDATOR_BEAN_NAME = "configurationPropertiesValidator";
    /**
     * 工廠bean相關元數據
     */
    private ConfigurationBeanFactoryMetadata beanFactoryMetadata;
    /**
     * 上下文
     */
    private ApplicationContext applicationContext;
    /**
     * 配置屬性綁定器
     */
    private ConfigurationPropertiesBinder configurationPropertiesBinder;
    
    
    // 這裏主要是給beanFactoryMetadata和configurationPropertiesBinder的屬性賦值,用於後面的後置處理器方法處理屬性綁定的時候用
    @Override
    public void afterPropertiesSet() throws Exception {
        // We can't use constructor injection of the application context because
        // it causes eager factory bean initialization
        // 【1】利用afterPropertiesSet這個勾子方法從容器中獲取以前註冊的ConfigurationBeanFactoryMetadata對象賦給beanFactoryMetadata屬性
        // (問1)beanFactoryMetadata這個bean是何時註冊到容器中的?
        // (答1)在ConfigurationPropertiesBindingPostProcessorRegistrar類的registerBeanDefinitions方法中將beanFactoryMetadata這個bean註冊到容器中
        // (問2)從容器中獲取beanFactoryMetadata對象後,何時會被用到?
        // (答2)beanFactoryMetadata對象的beansFactoryMetadata集合保存的工廠bean相關的元數據,在ConfigurationPropertiesBindingPostProcessor類
        //        要判斷某個bean是否有FactoryAnnotation或FactoryMethod時會根據這個beanFactoryMetadata對象的beansFactoryMetadata集合的元數據來查找
        this.beanFactoryMetadata = this.applicationContext.getBean(
                ConfigurationBeanFactoryMetadata.BEAN_NAME,
                ConfigurationBeanFactoryMetadata.class);
        // 【2】new一個ConfigurationPropertiesBinder,用於後面的外部屬性綁定時使用
        this.configurationPropertiesBinder = new ConfigurationPropertiesBinder(
                this.applicationContext, VALIDATOR_BEAN_NAME); // VALIDATOR_BEAN_NAME="configurationPropertiesValidator"
    }

能夠看到以上代碼主要邏輯就是在執行外部屬性綁定邏輯前先準備好相關元數據和配置屬性綁定器,即從Spring容器中獲取到以前註冊的ConfigurationBeanFactoryMetadata對象賦給ConfigurationPropertiesBindingPostProcessor後置處理器的beanFactoryMetadata屬性,還有就是新建一個ConfigurationPropertiesBinder配置屬性綁定器對象並賦值給configurationPropertiesBinder屬性。

咱們再來看下ConfigurationPropertiesBinder這個配置屬性綁定器對象是如何構造的。

// ConfigurationPropertiesBinder.java

ConfigurationPropertiesBinder(ApplicationContext applicationContext,
        String validatorBeanName) {
    this.applicationContext = applicationContext;
    // 將applicationContext封裝到PropertySourcesDeducer對象中並返回
    this.propertySources = new PropertySourcesDeducer(applicationContext)
            .getPropertySources(); // 獲取屬性源,主要用於在ConfigurableListableBeanFactory的後置處理方法postProcessBeanFactory中處理
    // 若是沒有配置validator的話,這裏通常返回的是null
    this.configurationPropertiesValidator = getConfigurationPropertiesValidator(
            applicationContext, validatorBeanName);
    // 檢查實現JSR-303規範的bean校驗器相關類在classpath中是否存在
    this.jsr303Present = ConfigurationPropertiesJsr303Validator
            .isJsr303Present(applicationContext);
}

能夠看到在構造ConfigurationPropertiesBinder對象時主要給其相關屬性賦值(通常構造器邏輯都是這樣):

  1. applicationContext屬性賦值注入上下文對象;
  2. propertySources屬性賦值,屬性源即外部配置值好比application.properties配置的屬性值,注意這裏的屬性源是由ConfigFileApplicationListener這個監聽器負責讀取的,ConfigFileApplicationListener將會在後面源碼分析章節中詳述。
  3. configurationPropertiesValidator屬性賦值,值來自Spring容器中名爲configurationPropertiesValidatorbean
  4. jsr303Present屬性賦值,當javax.validation.Validator,javax.validation.ValidatorFactoryjavax.validation.bootstrap.GenericBootstrap"這三個類同時存在於classpathjsr303Present屬性值才爲true
關於JSR303JSR-303是JAVA EE 6中的一項子規範,叫作 Bean ValidationHibernate ValidatorBean Validation的參考實現 。 Hibernate Validator提供了 JSR 303規範中全部內置 constraint 的實現,除此以外還有一些附加的 constraint

7.2 執行真正的外部屬性綁定邏輯【主線】

前面分析了那麼多,發現都還沒到外部屬性綁定的真正處理邏輯,前面步驟都是在作一些準備性工做,爲外部屬性綁定作鋪墊。

在執行外部屬性綁定邏輯前,準備好了相關元數據和配置屬性綁定器後,此時咱們再來看看ConfigurationPropertiesBindingPostProcessor實現BeanPostProcessor接口的postProcessBeforeInitialization後置處理方法了,外部屬性綁定邏輯都是在這個後置處理方法裏實現,是咱們關注的重中之重

直接看代碼:

// ConfigurationPropertiesBindingPostProcessor.java

// 由於是外部配置屬性後置處理器,所以這裏對@ConfigurationProperties註解標註的XxxProperties類進行後置處理完成屬性綁定
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
        throws BeansException {
    // 注意,BeanPostProcessor後置處理器默認會對全部的bean進行處理,所以須要根據bean的一些條件進行過濾獲得最終要處理的目的bean,
    // 這裏的過濾條件就是判斷某個bean是否有@ConfigurationProperties註解
    // 【1】從bean上獲取@ConfigurationProperties註解,若bean有標註,那麼返回該註解;若沒有,則返回Null。好比ServerProperty上標註了@ConfigurationProperties註解
    ConfigurationProperties annotation = getAnnotation(bean, beanName,
            ConfigurationProperties.class);
    // 【2】若標註有@ConfigurationProperties註解的bean,那麼則進行進一步處理:將配置文件的配置注入到bean的屬性值中
    if (annotation != null) {
        /********主線,重點關注】********/
        bind(bean, beanName, annotation); 
    }
    // 【3】返回外部配置屬性值綁定後的bean(通常是XxxProperties對象)
    return bean;
}

ConfigurationPropertiesBindingPostProcessor類覆寫的postProcessBeforeInitialization方法的作的事情就是將外部屬性配置綁定到@ConfigurationProperties註解標註的XxxProperties類上,現關鍵步驟總結以下:

  1. bean上獲取@ConfigurationProperties註解;
  2. 若標註有@ConfigurationProperties註解的bean,那麼則進行進一步的處理:將外部配置屬性值綁定到bean的屬性值中後再返回bean;若沒有標註有@ConfigurationProperties註解的bean,那麼將直接原樣返回bean
注意:後置處理器默認會對每一個容器中的 bean進行後置處理,由於這裏只針對標註有 @ConfigurationProperties註解的 bean進行外部屬性綁定,所以沒有標註 @ConfigurationProperties註解的 bean將不會被處理。

接下來咱們緊跟主線,再來看下外部配置屬性是如何綁定到@ConfigurationProperties註解的XxxProperties類屬性上的呢?

直接看代碼:

// ConfigurationPropertiesBindingPostProcessor.java

private void bind(Object bean, String beanName, ConfigurationProperties annotation) {
    // 【1】獲得bean的類型,好比ServerPropertie這個bean獲得的類型是:org.springframework.boot.autoconfigure.web.ServerProperties
    ResolvableType type = getBeanType(bean, beanName);
    // 【2】獲取bean上標註的@Validated註解
    Validated validated = getAnnotation(bean, beanName, Validated.class);
    // 若標註有@Validated註解的話則跟@ConfigurationProperties註解一塊兒組成一個Annotation數組
    Annotation[] annotations = (validated != null)
            ? new Annotation[] { annotation, validated }
            : new Annotation[] { annotation };
    // 【3】返回一個綁定了XxxProperties類的Bindable對象target,這個target對象即被外部屬性值注入的目標對象
    // (好比封裝了標註有@ConfigurationProperties註解的ServerProperties對象的Bindable對象)
    Bindable<?> target = Bindable.of(type).withExistingValue(bean)
            .withAnnotations(annotations); // 設置annotations屬性數組
    try {
        // 【4】執行外部配置屬性綁定邏輯
        /********【主線,重點關注】********/
        this.configurationPropertiesBinder.bind(target);
    }
    catch (Exception ex) {
        throw new ConfigurationPropertiesBindException(beanName, bean, annotation,
                ex);
    }
}

關鍵步驟上面代碼已經標註【x】,這裏在繼續講解外部配置屬性綁定的主線邏輯(在<font color=blue>8 ConfigurationPropertiesBinder</font>這一小節分析 )前先穿插一個知識點,還記得ConfigurationBeanFactoryMetadata覆寫的postProcessBeanFactory方法裏已經將相關工廠bean的元數據封裝到ConfigurationBeanFactoryMetadata類的beansFactoryMetadata集合這一回事嗎?

咱們再來看下上面代碼中的【1】getBeanType【2】getAnnotation方法源碼:

// ConfigurationPropertiesBindingPostProcessor.java

private ResolvableType getBeanType(Object bean, String beanName) {
    // 首先獲取有沒有工廠方法
    Method factoryMethod = this.beanFactoryMetadata.findFactoryMethod(beanName);
    // 如有工廠方法
    if (factoryMethod != null) { 
        return ResolvableType.forMethodReturnType(factoryMethod);
    } 
    // 沒有工廠方法,則說明是普通的配置類
    return ResolvableType.forClass(bean.getClass());
}

private <A extends Annotation> A getAnnotation(Object bean, String beanName,
        Class<A> type) {
    A annotation = this.beanFactoryMetadata.findFactoryAnnotation(beanName, type);
    if (annotation == null) {
        annotation = AnnotationUtils.findAnnotation(bean.getClass(), type);
    }
    return annotation;
}

注意到上面代碼中的beanFactoryMetadata對象沒,ConfigurationPropertiesBindingPostProcessor後置處理器的getBeanTypegetAnnotation方法分別會調用ConfigurationBeanFactoryMetadatafindFactoryMethodfindFactoryAnnotation方法,而ConfigurationBeanFactoryMetadatafindFactoryMethodfindFactoryAnnotation方法又會依賴存儲工廠bean元數據的beansFactoryMetadata集合來尋找是否有FactoryMethodFactoryAnnotation。所以,到這裏咱們就知道之ConfigurationBeanFactoryMetadatabeansFactoryMetadata集合存儲工廠bean元數據的做用了。

8 ConfigurationPropertiesBinder

咱們再繼續緊跟外部配置屬性綁定的主線,繼續前面看<font color=blue>7.2 執行真正的外部屬性綁定邏輯</font>中的this.configurationPropertiesBinder.bind(target);這句代碼:

// ConfigurationPropertiesBinder.java

public void bind(Bindable<?> target) {
    //【1】獲得@ConfigurationProperties註解
    ConfigurationProperties annotation = target
            .getAnnotation(ConfigurationProperties.class);
    Assert.state(annotation != null,
            () -> "Missing @ConfigurationProperties on " + target);
    // 【2】獲得Validator對象集合,用於屬性校驗
    List<Validator> validators = getValidators(target);
    // 【3】獲得BindHandler對象(默認是IgnoreTopLevelConverterNotFoundBindHandler對象),
    // 用於對ConfigurationProperties註解的ignoreUnknownFields等屬性的處理
    BindHandler bindHandler = getBindHandler(annotation, validators);
    // 【4】獲得一個Binder對象,並利用其bind方法執行外部屬性綁定邏輯
    /********************【主線,重點關注】********************/
    getBinder().bind(annotation.prefix(), target, bindHandler);
}

上面代碼的主要邏輯是:

  1. 先獲取target對象(對應XxxProperties類)上的@ConfigurationProperties註解和校驗器(如有);
  2. 而後再根據獲取的的@ConfigurationProperties註解和校驗器來得到BindHandler對象,BindHandler的做用是用於在屬性綁定時來處理一些附件邏輯;在<font color=blue>8.1節分析</font>.
  3. 最後再獲取一個Binder對象,調用其bind方法來執行外部屬性綁定的邏輯,在<font color=blue>8.2節分析</font>.

8.1 獲取BindHandler對象以便在屬性綁定時來處理一些附件邏輯

咱們在看getBindHandler方法的邏輯前先來認識下BindHandler是幹啥的。

BindHandler是一個父類接口,用於在屬性綁定時來處理一些附件邏輯。咱們先看下BindHandler的類圖,好有一個總體的認識:


<center>圖1</center>

能夠看到AbstractBindHandler做爲抽象基類實現了BindHandler接口,其又有四個具體的子類分別是IgnoreTopLevelConverterNotFoundBindHandler,NoUnboundElementsBindHandler,IgnoreErrorsBindHandlerValidationBindHandler

  1. IgnoreTopLevelConverterNotFoundBindHandler:在處理外部屬性綁定時的默認BindHandler,當屬性綁定失敗時會忽略最頂層的ConverterNotFoundException
  2. NoUnboundElementsBindHandler:用來處理配置文件配置的未知的屬性;
  3. IgnoreErrorsBindHandler:用來忽略無效的配置屬性例如類型錯誤;
  4. ValidationBindHandler:利用校驗器對綁定的結果值進行校驗。

分析完類關係後,咱們再來看下BindHandler接口提供了哪些方法在外部屬性綁定時提供一些額外的附件邏輯,直接看代碼:

// BindHandler.java

public interface BindHandler {

    /**
     * Default no-op bind handler.
     */
    BindHandler DEFAULT = new BindHandler() {

    };

    // onStart方法在外部屬性綁定前被調用
    default <T> Bindable<T> onStart(ConfigurationPropertyName name, Bindable<T> target,
            BindContext context) {
        return target;
    }

    // onSuccess方法在外部屬性成功綁定時被調用,該方法可以改變最終返回的屬性值或對屬性值進行校驗
    default Object onSuccess(ConfigurationPropertyName name, Bindable<?> target,
            BindContext context, Object result) {
        return result;
    }

    // onFailure方法在外部屬性綁定失敗(包括onSuccess方法裏的邏輯執行失敗)時被調用,
    // 該方法能夠用來catch住相關異常或者返回一個替代的結果(跟微服務的降級結果有點相似,嘿嘿)
    default Object onFailure(ConfigurationPropertyName name, Bindable<?> target,
            BindContext context, Exception error) throws Exception {
        throw error;
    }

    // 當外部屬性綁定結束時(無論綁定成功仍是失敗)被調用
    default void onFinish(ConfigurationPropertyName name, Bindable<?> target,
            BindContext context, Object result) throws Exception {
    }
}

能夠看到BindHandler接口定義了onStart,onSuccess,onFailureonFinish方法,這四個方法分別會在執行外部屬性綁定時的不一樣時機會被調用,在屬性綁定時用來添加一些額外的處理邏輯,好比在onSuccess方法改變最終綁定的屬性值或對屬性值進行校驗,在onFailure方法catch住相關異常或者返回一個替代的綁定的屬性值。

知道了BindHandler是在屬性綁定時添加一些額外的附件處理邏輯後,咱們再來看下getBindHandler方法的邏輯,直接上代碼:

// ConfigurationPropertiesBinder.java

// 注意BindHandler的設計技巧,應該是責任鏈模式,很是巧妙,值得借鑑
private BindHandler getBindHandler(ConfigurationProperties annotation,
        List<Validator> validators) {
    // 新建一個IgnoreTopLevelConverterNotFoundBindHandler對象,這是個默認的BindHandler對象
    BindHandler handler = new IgnoreTopLevelConverterNotFoundBindHandler();
    // 若註解@ConfigurationProperties的ignoreInvalidFields屬性設置爲true,
    // 則說明能夠忽略無效的配置屬性例如類型錯誤,此時新建一個IgnoreErrorsBindHandler對象
    if (annotation.ignoreInvalidFields()) {
        handler = new IgnoreErrorsBindHandler(handler);
    }
    // 若註解@ConfigurationProperties的ignoreUnknownFields屬性設置爲true,
    // 則說明配置文件配置了一些未知的屬性配置,此時新建一個ignoreUnknownFields對象
    if (!annotation.ignoreUnknownFields()) {
        UnboundElementsSourceFilter filter = new UnboundElementsSourceFilter();
        handler = new NoUnboundElementsBindHandler(handler, filter);
    }
    // 若是@Valid註解不爲空,則建立一個ValidationBindHandler對象
    if (!validators.isEmpty()) {
        handler = new ValidationBindHandler(handler,
                validators.toArray(new Validator[0]));
    }
    // 遍歷獲取的ConfigurationPropertiesBindHandlerAdvisor集合,
    // ConfigurationPropertiesBindHandlerAdvisor目前只在測試類中有用到
    for (ConfigurationPropertiesBindHandlerAdvisor advisor : getBindHandlerAdvisors()) {
        // 對handler進一步處理
        handler = advisor.apply(handler);
    }
    // 返回handler
    return handler;
}

getBindHandler方法的邏輯很簡單,主要是根據傳入的@ConfigurationProperties註解和validators校驗器來建立不一樣的BindHandler具體實現類:

  1. 首先new一個IgnoreTopLevelConverterNotFoundBindHandler做爲默認的BindHandler;
  2. @ConfigurationProperties註解的屬性ignoreInvalidFields值爲true,那麼再new一個IgnoreErrorsBindHandler對象,把剛纔新建的IgnoreTopLevelConverterNotFoundBindHandler對象做爲構造參數傳入賦值給AbstractBindHandler父類的parent屬性;
  3. @ConfigurationProperties註解的屬性ignoreUnknownFields值爲false,那麼再new一個UnboundElementsSourceFilter對象,把以前構造的BindHandler對象做爲構造參數傳入賦值給AbstractBindHandler父類的parent屬性;
  4. ......以此類推,前一個handler對象做爲後一個hangdler對象的構造參數,就這樣利用AbstractBindHandler父類的parent屬性將每個handler鏈起來,最後再獲得最終構造的handler
GET技巧:上面的這個設計模式是否是很熟悉,這個就是 責任鏈模式。咱們學習源碼,同時也是學習別人怎麼熟練運用設計模式。責任鏈模式的應用案例有不少,好比 Dubbo的各類 Filter們(好比 AccessLogFilter是用來記錄服務的訪問日誌的, ExceptionFilter是用來處理異常的...),咱們一開始學習java web時的 ServletFilter, MyBatisPlugin們以及 NettyPipeline都採用了責任鏈模式。

咱們瞭解了BindHandler的做用後,再來緊跟主線,看屬性綁定是如何綁定的?

8.2 獲取Binder對象用於進行屬性綁定【主線】

這裏接<font color=blue>8 ConfigurationPropertiesBinder</font>節代碼中標註【4】的主線代碼getBinder().bind(annotation.prefix(), target, bindHandler);.

能夠看到這句代碼主要作了兩件事:

  1. 調用getBinder方法獲取用於屬性綁定的Binder對象;
  2. 調用Binder對象的bind方法進行外部屬性綁定到@ConfigurationProperties註解的XxxProperties類的屬性上。

那麼咱們先看下getBinder方法源碼:

// ConfigurationPropertiesBinder.java

private Binder getBinder() {
    // Binder是一個能綁定ConfigurationPropertySource的容器對象
    if (this.binder == null) {
        // 新建一個Binder對象,這個binder對象封裝了ConfigurationPropertySources,
        // PropertySourcesPlaceholdersResolver,ConversionService和PropertyEditorInitializer對象
        this.binder = new Binder(getConfigurationPropertySources(), // 將PropertySources對象封裝成SpringConfigurationPropertySources對象並返回
                getPropertySourcesPlaceholdersResolver(), getConversionService(), // 將PropertySources對象封裝成PropertySourcesPlaceholdersResolver對象並返回,從容器中獲取到ConversionService對象
                getPropertyEditorInitializer()); // 獲得Consumer<PropertyEditorRegistry>對象,這些初始化器用來配置property editors,property editors一般能夠用來轉換值
    }
    // 返回binder
    return this.binder;
}

能夠看到Binder對象封裝了ConfigurationPropertySources,PropertySourcesPlaceholdersResolver,ConversionServicePropertyEditorInitializer這四個對象,Binder對象封裝了這四個哥們確定是在後面屬性綁定邏輯中會用到,先看下這四個對象是幹嗎的:

  • ConfigurationPropertySources:外部配置文件的屬性源,由ConfigFileApplicationListener監聽器負責觸發讀取;
  • PropertySourcesPlaceholdersResolver:解析屬性源中的佔位符${}
  • ConversionService:對屬性類型進行轉換
  • PropertyEditorInitializer:用來配置property editors

那麼,咱們獲取了Binder屬性綁定器後,再來看下它的bind方法是如何執行屬性綁定的。

// Binder.java

public <T> BindResult<T> bind(String name, Bindable<T> target, BindHandler handler) {
    // ConfigurationPropertyName.of(name):將name(這裏指屬性前綴名)封裝到ConfigurationPropertyName對象中
    // 將外部配置屬性綁定到目標對象target中
    return bind(ConfigurationPropertyName.of(name), target, handler); 
}

public <T> BindResult<T> bind(ConfigurationPropertyName name, Bindable<T> target,
        BindHandler handler) {
    Assert.notNull(name, "Name must not be null");
    Assert.notNull(target, "Target must not be null");
    handler = (handler != null) ? handler : BindHandler.DEFAULT;
    // Context是Binder的內部類,實現了BindContext,Context能夠理解爲Binder的上下文,能夠用來獲取binder的屬性好比Binder的sources屬性
    Context context = new Context();
    // 進行屬性綁定,並返回綁定屬性後的對象bound,注意bound的對象類型是T,T就是@ConfigurationProperties註解的類好比ServerProperties
    /********【主線,重點關注】************/
    T bound = bind(name, target, handler, context, false);
    // 將剛纔返回的bound對象封裝到BindResult對象中並返回
    return BindResult.of(bound);
}

上面代碼中首先建立了一個Context對象,ContextBinder的內部類,爲Binder的上下文,利用Context上下文能夠獲取Binder的屬性好比獲取Bindersources屬性值並綁定到XxxProperties屬性中。而後咱們再緊跟主線看下 bind(name, target, handler, context, false)方法源碼:

// Binder.java

protected final <T> T bind(ConfigurationPropertyName name, Bindable<T> target,
        BindHandler handler, Context context, boolean allowRecursiveBinding) {
    // 清空Binder的configurationProperty屬性值
    context.clearConfigurationProperty(); 
    try { 
        // 【1】調用BindHandler的onStart方法,執行一系列的責任鏈對象的該方法
        target = handler.onStart(name, target, context);
        if (target == null) {
            return null;
        }// 【2】調用bindObject方法對Bindable對象target的屬性進行綁定外部配置的值,並返回賦值給bound對象。
        // 舉個栗子:好比設置了server.port=8888,那麼該方法最終會調用Binder.bindProperty方法,最終返回的bound的value值爲8888
        /************【主線:重點關注】***********/
        Object bound = bindObject(name, target, handler, context, 
                allowRecursiveBinding);
        // 【3】封裝handleBindResult對象並返回,注意在handleBindResult的構造函數中會調用BindHandler的onSucess,onFinish方法
        return handleBindResult(name, target, handler, context, bound); 
    }
    catch (Exception ex) {
        return handleBindError(name, target, handler, context, ex);
    }
}

上面代碼的註釋已經很是詳細,這裏再也不詳述。咱們接着緊跟主線來看看bindObject方法源碼:

// Binder.java

private <T> Object bindObject(ConfigurationPropertyName name, Bindable<T> target,
        BindHandler handler, Context context, boolean allowRecursiveBinding) {
    // 從propertySource中的配置屬性,獲取ConfigurationProperty對象property即application.properties配置文件中如有相關的配置的話,
    // 那麼property將不會爲null。舉個栗子:假如你在配置文件中配置了spring.profiles.active=dev,那麼相應property值爲dev;不然爲null
    ConfigurationProperty property = findProperty(name, context);
    // 若property爲null,則不會執行後續的屬性綁定相關邏輯
    if (property == null && containsNoDescendantOf(context.getSources(), name)) {
        // 若是property == null,則返回null
        return null;
    }
    // 根據target類型獲取不一樣的Binder,能夠是null(普通的類型通常是Null),MapBinder,CollectionBinder或ArrayBinder
    AggregateBinder<?> aggregateBinder = getAggregateBinder(target, context);
    // 若aggregateBinder不爲null好比配置了spring.profiles屬性(固然包括其子屬性好比spring.profiles.active等)
    if (aggregateBinder != null) {
        // 若aggregateBinder不爲null,則調用bindAggregate並返回綁定後的對象
        return bindAggregate(name, target, handler, context, aggregateBinder);
    }
    // 若property不爲null
    if (property != null) {
        try {
            // 綁定屬性到對象中,好比配置文件中設置了server.port=8888,那麼將會最終調用bindProperty方法進行屬性設置
            return bindProperty(target, context, property);
        }
        catch (ConverterNotFoundException ex) {
            // We might still be able to bind it as a bean
            Object bean = bindBean(name, target, handler, context,
                    allowRecursiveBinding);
            if (bean != null) {
                return bean;
            }
            throw ex;
        }
    }
    // 只有@ConfigurationProperties註解的類進行外部屬性綁定纔會走這裏
    /***********************【主線,重點關注】****************************/
    return bindBean(name, target, handler, context, allowRecursiveBinding);
}

由上代碼中能夠看到bindObject中執行屬性綁定的邏輯會根據不一樣的屬性類型進入不一樣的綁定邏輯中,舉個栗子:

  1. application.properties配置文件中配置了spring.profiles.active=dev的話,那麼將會進入return bindAggregate(name, target, handler, context, aggregateBinder);這個屬性綁定的代碼邏輯;
  2. application.properties配置文件中配置了server.port=8081的話,那麼將會進入return bindBean(name, target, handler, context, allowRecursiveBinding);的屬性綁定的邏輯。

所以咱們再次緊跟主線,進入@ConfigurationProperties註解的XxxProperties類的屬性綁定邏輯中的bindBean方法中:

// Binder.java

private Object bindBean(ConfigurationPropertyName name, Bindable<?> target, // name指的是ConfigurationProperties的前綴名
        BindHandler handler, Context context, boolean allowRecursiveBinding) {
    // 這裏作一些ConfigurationPropertyState的相關檢查
    if (containsNoDescendantOf(context.getSources(), name)
            || isUnbindableBean(name, target, context)) {
        return null;
    }// 這裏新建一個BeanPropertyBinder的實現類對象,注意這個對象實現了bindProperty方法
    BeanPropertyBinder propertyBinder = (propertyName, propertyTarget) -> bind(
            name.append(propertyName), propertyTarget, handler, context, false);
    /**
     * (propertyName, propertyTarget) -> bind(
     *                 name.append(propertyName), propertyTarget, handler, context, false);
     *     等價於
     *     new BeanPropertyBinder() {
     *        Object bindProperty(String propertyName, Bindable<?> target){
     *            bind(name.append(propertyName), propertyTarget, handler, context, false);
     *        }
     *     }
     */
    // type類型即@ConfigurationProperties註解標註的XxxProperties類
    Class<?> type = target.getType().resolve(Object.class);
    if (!allowRecursiveBinding && context.hasBoundBean(type)) {
        return null;
    }
    // 這裏應用了java8的lambda語法,做爲沒怎麼學習java8的lambda語法的我,不怎麼好理解下面的邏輯,哈哈
    // 真正實現將外部配置屬性綁定到@ConfigurationProperties註解的XxxProperties類的屬性中的邏輯應該就是在這句lambda代碼了
    /*******************【主線】***************************/
    return context.withBean(type, () -> {
        Stream<?> boundBeans = BEAN_BINDERS.stream()
                .map((b) -> b.bind(name, target, context, propertyBinder));
        return boundBeans.filter(Objects::nonNull).findFirst().orElse(null);
    });
    // 根據上面的lambda語句翻譯以下:
    /** 這裏的T指的是各類屬性綁定對象,好比ServerProperties
     * return context.withBean(type, new Supplier<T>() {
     *     T get() {
     *         Stream<?> boundBeans = BEAN_BINDERS.stream()
     *                     .map((b) -> b.bind(name, target, context, propertyBinder));
     *             return boundBeans.filter(Objects::nonNull).findFirst().orElse(null);
     *        }
     *  });
     */
}

從上面代碼中,咱們追根究底來到了外部配置屬性綁定到XxxProperties類屬性中的比較底層的代碼了,能夠看到屬性綁定的邏輯應該就在上面代碼標註【主線】lambda代碼處了。這裏就再也不詳述了,由於這個屬於SpringBoot的屬性綁定Binder的範疇,Binder相關類是SpringBoot2.0纔出現的,即對以前的屬性綁定相關代碼進行推翻重寫了。屬性綁定相關的源碼也比較多,後續有須要再另開一篇來分析探究吧。

9 小結

好了,外部配置屬性值是如何被綁定到XxxProperties類屬性上的源碼分析就到此結束了,又是蠻長的一篇文章,不知本身表述清楚沒,重要步驟現總結下:

  1. 首先是@EnableConfigurationProperties註解importEnableConfigurationPropertiesImportSelector後置處理器;
  2. EnableConfigurationPropertiesImportSelector後置處理器又向Spring容器中註冊了ConfigurationPropertiesBeanRegistrarConfigurationPropertiesBindingPostProcessorRegistrar這兩個bean
  3. 其中ConfigurationPropertiesBeanRegistrarSpring容器中註冊了XxxProperties類型的beanConfigurationPropertiesBindingPostProcessorRegistrarSpring容器中註冊了ConfigurationBeanFactoryMetadataConfigurationPropertiesBindingPostProcessor兩個後置處理器;
  4. ConfigurationBeanFactoryMetadata後置處理器在初始化bean factory時將@Bean註解的元數據存儲起來,以便在後續的外部配置屬性綁定的相關邏輯中使用;
  5. ConfigurationPropertiesBindingPostProcessor後置處理器將外部配置屬性值綁定到XxxProperties類屬性的邏輯委託給ConfigurationPropertiesBinder對象,而後ConfigurationPropertiesBinder對象又最終將屬性綁定的邏輯委託給Binder對象來完成。

可見,重要的是上面的第5步

原創不易,幫忙點個讚唄!

PS:原本打算這篇開始分析SpringBoot的啓動流程的,可是回過頭去看看自動配置的相關源碼,還有蠻多沒有分析的,所以再來一波自動配置相關的源碼先。

因爲筆者水平有限,若文中有錯誤還請指出,謝謝。

參考:
1,JSR-303


歡迎關注【源碼筆記】公衆號,一塊兒學習交流。

相關文章
相關標籤/搜索