注:該源碼分析對應SpringBoot版本爲2.1.0.RELEASEjava
本篇接 SpringBoot是如何實現自動配置的?--SpringBoot源碼(四)web
溫故而知新,咱們來簡單回顧一下上篇的內容,上一篇咱們分析了SpringBoot的自動配置的相關源碼,自動配置相關源碼主要有如下幾個重要的步驟:spring
@EnableAutoConfiguration
註解的exclude
屬性指定的自動配置類;AutoConfigurationImportFilter
接口去過濾自動配置類是否符合其標註註解(如有標註的話)@ConditionalOnClass
,@ConditionalOnBean
和@ConditionalOnWebApplication
的條件,若都符合的話則返回匹配結果;AutoConfigurationImportEvent
事件,告訴ConditionEvaluationReport
條件評估報告器對象來分別記錄符合條件和exclude
的自動配置類。本篇繼續來分析SpringBoot的自動配置的相關源碼,咱們來分析下@EnableConfigurationProperties
和@EnableConfigurationProperties
這兩個註解,來探究下外部配置屬性值是如何被綁定到@ConfigurationProperties註解的類屬性中的?bootstrap
舉個栗子:以配置web項目的服務器端口爲例,若咱們要將服務器端口配置爲8081
,那麼咱們會在application.properties
配置文件中配置server.port=8081
,此時該配置值8081
就將會綁定到被@ConfigurationProperties
註解的類ServerProperties
的屬性port
上,從而使得配置生效。
咱們接着前面的設置服務器端口的栗子來分析,咱們先直接來看看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
標註的類的屬性中的。
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
是一個常量數組,值是ConfigurationPropertiesBeanRegistrar
和ConfigurationPropertiesBindingPostProcessorRegistrar
。即EnableConfigurationPropertiesImportSelector
的做用是向Spring容器中註冊了ConfigurationPropertiesBeanRegistrar
和ConfigurationPropertiesBindingPostProcessorRegistrar
這兩個bean
。
咱們在EnableConfigurationPropertiesImportSelector
類中沒看處處理外部屬性綁定的相關邏輯,其只是註冊了ConfigurationPropertiesBeanRegistrar
和ConfigurationPropertiesBindingPostProcessorRegistrar
這兩個bean
,接下來咱們再看下注冊的這兩個bean
類。
咱們先來看下ConfigurationPropertiesBeanRegistrar
這個類。
ConfigurationPropertiesBeanRegistrar
是EnableConfigurationPropertiesImportSelector
的內部類,其實現了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
中,能夠看到主要作了兩件事:
getTypes
方法獲取@EnableConfigurationProperties
註解的屬性值XxxProperties
;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
又是幹嗎的呢?
能夠看到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
類的邏輯很是簡單,主要用來註冊外部配置屬性綁定相關的後置處理器即ConfigurationBeanFactoryMetadata
和ConfigurationPropertiesBindingPostProcessor
。
那麼接下來咱們再來探究下注冊的這兩個後置處理器又是執行怎樣的後置處理邏輯呢?
先來看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
僅有兩個屬性bean
和method
,分別表示@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
後置處理器中使用。
咱們再來看下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
後置處理器實現了兩個重要的接口InitializingBean
和BeanPostProcessor
。
咱們都知道:
InitializingBean
接口的afterPropertiesSet
方法會在bean
屬性賦值後調用,用來執行一些自定義的初始化邏輯好比檢查某些強制的屬性是否有被賦值,校驗某些配置或給一些未被賦值的屬性賦值。BeanPostProcessor
接口是bean
的後置處理器,其有postProcessBeforeInitialization
和postProcessAfterInitialization
兩個勾子方法,分別會在bean
初始化先後被調用來執行一些後置處理邏輯,好比檢查標記接口或是否用代理包裝了bean
。同時由上代碼能夠看到ConfigurationPropertiesBindingPostProcessor
後置處理器覆寫了InitializingBean
的afterPropertiesSet
方法和BeanPostProcessor
的postProcessBeforeInitialization
方法。
接下來咱們再來探究ConfigurationPropertiesBindingPostProcessor
後置處理器覆寫的兩個方法的源碼。
咱們先來分析下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
對象時主要給其相關屬性賦值(通常構造器邏輯都是這樣):
applicationContext
屬性賦值注入上下文對象;propertySources
屬性賦值,屬性源即外部配置值好比application.properties
配置的屬性值,注意這裏的屬性源是由ConfigFileApplicationListener
這個監聽器負責讀取的,ConfigFileApplicationListener
將會在後面源碼分析章節中詳述。configurationPropertiesValidator
屬性賦值,值來自Spring
容器中名爲configurationPropertiesValidator
的bean
。jsr303Present
屬性賦值,當javax.validation.Validator
,javax.validation.ValidatorFactory
和javax.validation.bootstrap.GenericBootstrap"
這三個類同時存在於classpath
中jsr303Present
屬性值才爲true
。關於JSR303:JSR-303
是JAVA EE 6中的一項子規範,叫作Bean Validation
,Hibernate Validator
是Bean Validation
的參考實現 。Hibernate Validator
提供了JSR 303
規範中全部內置constraint
的實現,除此以外還有一些附加的constraint
。
前面分析了那麼多,發現都還沒到外部屬性綁定的真正處理邏輯,前面步驟都是在作一些準備性工做,爲外部屬性綁定作鋪墊。
在執行外部屬性綁定邏輯前,準備好了相關元數據和配置屬性綁定器後,此時咱們再來看看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
類上,現關鍵步驟總結以下:
bean
上獲取@ConfigurationProperties
註解;@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
後置處理器的getBeanType
和getAnnotation
方法分別會調用ConfigurationBeanFactoryMetadata
的findFactoryMethod
和findFactoryAnnotation
方法,而ConfigurationBeanFactoryMetadata
的findFactoryMethod
和findFactoryAnnotation
方法又會依賴存儲工廠bean
元數據的beansFactoryMetadata
集合來尋找是否有FactoryMethod
和FactoryAnnotation
。所以,到這裏咱們就知道之ConfigurationBeanFactoryMetadata
的beansFactoryMetadata
集合存儲工廠bean
元數據的做用了。
咱們再繼續緊跟外部配置屬性綁定的主線,繼續前面看<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); }
上面代碼的主要邏輯是:
target
對象(對應XxxProperties
類)上的@ConfigurationProperties
註解和校驗器(如有);@ConfigurationProperties
註解和校驗器來得到BindHandler
對象,BindHandler
的做用是用於在屬性綁定時來處理一些附件邏輯;在<font color=blue>8.1節分析</font>.Binder
對象,調用其bind
方法來執行外部屬性綁定的邏輯,在<font color=blue>8.2節分析</font>.咱們在看getBindHandler
方法的邏輯前先來認識下BindHandler
是幹啥的。
BindHandler
是一個父類接口,用於在屬性綁定時來處理一些附件邏輯。咱們先看下BindHandler
的類圖,好有一個總體的認識:
<center>圖1</center>
能夠看到AbstractBindHandler
做爲抽象基類實現了BindHandler
接口,其又有四個具體的子類分別是IgnoreTopLevelConverterNotFoundBindHandler
,NoUnboundElementsBindHandler
,IgnoreErrorsBindHandler
和ValidationBindHandler
。
IgnoreTopLevelConverterNotFoundBindHandler
:在處理外部屬性綁定時的默認BindHandler
,當屬性綁定失敗時會忽略最頂層的ConverterNotFoundException
;NoUnboundElementsBindHandler
:用來處理配置文件配置的未知的屬性;IgnoreErrorsBindHandler
:用來忽略無效的配置屬性例如類型錯誤;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
,onFailure
和onFinish
方法,這四個方法分別會在執行外部屬性綁定時的不一樣時機會被調用,在屬性綁定時用來添加一些額外的處理邏輯,好比在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
具體實現類:
new
一個IgnoreTopLevelConverterNotFoundBindHandler
做爲默認的BindHandler
;@ConfigurationProperties
註解的屬性ignoreInvalidFields
值爲true
,那麼再new
一個IgnoreErrorsBindHandler
對象,把剛纔新建的IgnoreTopLevelConverterNotFoundBindHandler
對象做爲構造參數傳入賦值給AbstractBindHandler
父類的parent
屬性;@ConfigurationProperties
註解的屬性ignoreUnknownFields
值爲false
,那麼再new
一個UnboundElementsSourceFilter
對象,把以前構造的BindHandler
對象做爲構造參數傳入賦值給AbstractBindHandler
父類的parent
屬性;handler
對象做爲後一個hangdler
對象的構造參數,就這樣利用AbstractBindHandler
父類的parent
屬性將每個handler
鏈起來,最後再獲得最終構造的handler
。GET技巧:上面的這個設計模式是否是很熟悉,這個就是 責任鏈模式。咱們學習源碼,同時也是學習別人怎麼熟練運用設計模式。責任鏈模式的應用案例有不少,好比Dubbo
的各類Filter
們(好比AccessLogFilter
是用來記錄服務的訪問日誌的,ExceptionFilter
是用來處理異常的...),咱們一開始學習java web時的Servlet
的Filter
,MyBatis
的Plugin
們以及Netty
的Pipeline
都採用了責任鏈模式。
咱們瞭解了BindHandler
的做用後,再來緊跟主線,看屬性綁定是如何綁定的?
這裏接<font color=blue>8 ConfigurationPropertiesBinder</font>節代碼中標註【4】
的主線代碼getBinder().bind(annotation.prefix(), target, bindHandler);
.
能夠看到這句代碼主要作了兩件事:
getBinder
方法獲取用於屬性綁定的Binder
對象;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
,ConversionService
和PropertyEditorInitializer
這四個對象,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
對象,Context
是Binder
的內部類,爲Binder
的上下文,利用Context
上下文能夠獲取Binder
的屬性好比獲取Binder
的sources
屬性值並綁定到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
中執行屬性綁定的邏輯會根據不一樣的屬性類型進入不一樣的綁定邏輯中,舉個栗子:
application.properties
配置文件中配置了spring.profiles.active=dev
的話,那麼將會進入return bindAggregate(name, target, handler, context, aggregateBinder);
這個屬性綁定的代碼邏輯;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纔出現的,即對以前的屬性綁定相關代碼進行推翻重寫了。屬性綁定相關的源碼也比較多,後續有須要再另開一篇來分析探究吧。
好了,外部配置屬性值是如何被綁定到XxxProperties
類屬性上的源碼分析就到此結束了,又是蠻長的一篇文章,不知本身表述清楚沒,重要步驟現總結下:
@EnableConfigurationProperties
註解import
了EnableConfigurationPropertiesImportSelector
後置處理器;EnableConfigurationPropertiesImportSelector
後置處理器又向Spring
容器中註冊了ConfigurationPropertiesBeanRegistrar
和ConfigurationPropertiesBindingPostProcessorRegistrar
這兩個bean
;ConfigurationPropertiesBeanRegistrar
向Spring
容器中註冊了XxxProperties
類型的bean
;ConfigurationPropertiesBindingPostProcessorRegistrar
向Spring
容器中註冊了ConfigurationBeanFactoryMetadata
和ConfigurationPropertiesBindingPostProcessor
兩個後置處理器;ConfigurationBeanFactoryMetadata
後置處理器在初始化bean
factory
時將@Bean
註解的元數據存儲起來,以便在後續的外部配置屬性綁定的相關邏輯中使用;ConfigurationPropertiesBindingPostProcessor
後置處理器將外部配置屬性值綁定到XxxProperties
類屬性的邏輯委託給ConfigurationPropertiesBinder
對象,而後ConfigurationPropertiesBinder
對象又最終將屬性綁定的邏輯委託給Binder
對象來完成。可見,重要的是上面的第5步。
原創不易,幫忙點個讚唄!
PS:原本打算這篇開始分析SpringBoot的啓動流程的,可是回過頭去看看自動配置的相關源碼,還有蠻多沒有分析的,所以再來一波自動配置相關的源碼先。
因爲筆者水平有限,若文中有錯誤還請指出,謝謝。
參考:
1,JSR-303
歡迎關注【源碼筆記】公衆號,一塊兒學習交流。