SpringBoot成長記8:SpringBoot如何實現自動裝配配置和擴充套件

SpringBoot成長記8:SpringBoot如何實現自動裝配配置和擴充套件

文章配圖

前面我們摸清楚了整個invokeBeanFactoryPostProcessors方法的if-else邏輯和3個for迴圈的核心脈絡邏輯。

接下來我們來看下細節,我會透過抓大放小的思想,帶大家看到在擴充套件點執行的過程中,最最要的有哪一些。

SpringBoot的自動裝配配置如何做到的、第三方技術如何進行擴充套件的。

SpringBoot的自動裝配配置如何做到的?

if-else邏輯中哪些BeanFactoryPostProcessor執行了?

之前我們提到過,invokeBeanFactoryPostProcessors執行的BeanFactoryPostProcessor主要來源是容器的兩個屬性

beanFactoryPostProcessors

BeanDefinitionMap

首先這兩個屬性,會在之前執行擴充套件操作,比如listener或者initializer的方法時,設定進去值的。執行到invokeBeanFactoryPostProcessors時,之前會設定如下圖所示的值:

SpringBoot成長記8:SpringBoot如何實現自動裝配配置和擴充套件

文章配圖

從上圖可以看出來,執行invokeBeanFactoryPostProcessors的時候已經有4個BeanFactoryPostProcessor。

當執行invokeBeanFactoryPostProcessors,核心脈絡上一節我們分析出了是主要一個if-else+3個for迴圈組成的,這個if-else中有分了內部的、實現PriorityOrderd、Ordered、NonOrder這個四個順序執行。結合上面4個BeanFactoryPostProcessor,整體執行如下圖所示:

SpringBoot成長記8:SpringBoot如何實現自動裝配配置和擴充套件

文章配圖

從圖中可以看出來,非常關鍵的一點那就是:

在執行擴充套件方法1的過程中,透過Spring內部的一個ConfigurationClassPostProcessor,補充了新的BeanDefinition,增加了新的BeanFactoryPostProcessor。

ConfigurationClassPostProcessor這個執行非常關鍵,因為它補充了新的BeanDefinition。

它核心用來進行載入ClassPath下所有java註解定義的BeanDefinition。比如:自己包下定義的@Service,@Component,@Controller@Configuration @Bean等註解定義的Bean,也包括外部的starter中@Configuration @Bean等配置。

也就是你定義的大多數bean和外部starter定義的Bean的BeanDefinition都會被放入到容器中。

另外,補充了新的BeanDefinition,這裡我們簡化了下,假設當前應用,只依賴了一個myBatis-starter,之後只會補充一個MyBatis相關的BeanDefinition,一個BeanFactoryPostProcessor—MapperScannerConfigurer。從名字上猜測,它應該是用來掃描MyBatis相關bean的。

invokeBeanFactoryPostProcessors的if-else邏輯中,觸發了2個擴充套件操作,最後還會執行擴充套件方法2,之前的所有BeanFactoryPostProcessor,統一會執行擴充套件方法2。

擴充套件方法2執行的邏輯,基本沒有什麼核心的,這裡我們就直接過了,你知道invokeBeanFactoryPostProcessors這裡會觸發這個擴充套件點,並且在擴充套件方法1之後執行就行了。

最終執行完if-else後,BeanFactory中的主要有如下的beanDefination:

beanDefinitionNames = {ArrayList@3752} size = 164 0 = “org。springframework。context。annotation。internalConfigurationAnnotationProcessor” 1 = “org。springframework。context。annotation。internalAutowiredAnnotationProcessor” 2 = “org。springframework。context。annotation。internalCommonAnnotationProcessor” 3 = “org。springframework。context。event。internalEventListenerProcessor” 4 = “org。springframework。context。event。internalEventListenerFactory” 5 = “learnSpringBootApplication” 6 = “org。springframework。boot。autoconfigure。internalCachingMetadataReaderFactory” 7 = “userController” 8 = “myBeanPostProcessor” 9 = “userServiceImpl” 10 = “userMapper” 11 = “org。springframework。boot。autoconfigure。AutoConfigurationPackages” 12 = “org。springframework。boot。autoconfigure。context。PropertyPlaceholderAutoConfiguration” 13 = “propertySourcesPlaceholderConfigurer” 14 = “org。springframework。boot。autoconfigure。websocket。servlet。WebSocketServletAutoConfiguration$TomcatWebSocketConfiguration” 15 = “websocketServletWebServerCustomizer” 16 = “org。springframework。boot。autoconfigure。websocket。servlet。WebSocketServletAutoConfiguration” 17 = “org。springframework。boot。autoconfigure。web。servlet。ServletWebServerFactoryConfiguration$EmbeddedTomcat” 18 = “tomcatServletWebServerFactory” 19 = “org。springframework。boot。autoconfigure。web。servlet。ServletWebServerFactoryAutoConfiguration” 20 = “servletWebServerFactoryCustomizer” 21 = “tomcatServletWebServerFactoryCustomizer” 22 = “org。springframework。boot。context。properties。ConfigurationPropertiesBindingPostProcessor” 23 = “org。springframework。boot。context。internalConfigurationPropertiesBinderFactory” 24 = “org。springframework。boot。context。internalConfigurationPropertiesBinder” 25 = “org。springframework。boot。context。properties。ConfigurationPropertiesBeanDefinitionValidator” 26 = “org。springframework。boot。context。properties。ConfigurationBeanFactoryMetadata” 27 = “server-org。springframework。boot。autoconfigure。web。ServerProperties” 28 = “webServerFactoryCustomizerBeanPostProcessor” 29 = “errorPageRegistrarBeanPostProcessor” 30 = “org。springframework。boot。autoconfigure。web。servlet。DispatcherServletAutoConfiguration$DispatcherServletConfiguration” 31 = “dispatcherServlet” 32 = “spring。mvc-org。springframework。boot。autoconfigure。web。servlet。WebMvcProperties” 33 = “spring。http-org。springframework。boot。autoconfigure。http。HttpProperties” 34 = “org。springframework。boot。autoconfigure。web。servlet。DispatcherServletAutoConfiguration$DispatcherServletRegistrationConfiguration” 35 = “dispatcherServletRegistration” 36 = “org。springframework。boot。autoconfigure。web。servlet。DispatcherServletAutoConfiguration” 37 = “org。springframework。boot。autoconfigure。task。TaskExecutionAutoConfiguration” 38 = “taskExecutorBuilder” 39 = “applicationTaskExecutor” 40 = “spring。task。execution-org。springframework。boot。autoconfigure。task。TaskExecutionProperties” 41 = “org。springframework。boot。autoconfigure。validation。ValidationAutoConfiguration” 42 = “defaultValidator” 43 = “methodValidationPostProcessor” 44 = “org。springframework。boot。autoconfigure。web。servlet。error。ErrorMvcAutoConfiguration$WhitelabelErrorViewConfiguration” 45 = “error” 46 = “beanNameViewResolver” 47 = “org。springframework。boot。autoconfigure。web。servlet。error。ErrorMvcAutoConfiguration$DefaultErrorViewResolverConfiguration” 48 = “convention​ErrorViewResolver” 49 = “org。springframework。boot。autoconfigure。web。servlet。error。ErrorMvcAutoConfiguration” 50 = “errorAttributes” 51 = “basicErrorController” 52 = “errorPageCustomizer” 53 = “preserveErrorControllerTargetClassPostProcessor” 54 = “spring。resources-org。springframework。boot。autoconfigure。web。ResourceProperties” 55 = “org。springframework。boot。autoconfigure。web。servlet。WebMvcAutoConfiguration$EnableWebMvcConfiguration” 56 = “requestMappingHandlerAdapter” 57 = “requestMappingHandlerMapping” 58 = “welcomePageHandlerMapping” 59 = “mvcConversionService” 60 = “mvcValidator” 61 = “mvcContentNegotiationManager” 62 = “mvcPathMatcher” 63 = “mvcUrlPathHelper” 64 = “viewControllerHandlerMapping” 65 = “beanNameHandlerMapping” 66 = “routerFunctionMapping” 67 = “resourceHandlerMapping” 68 = “mvcResourceUrlProvider” 69 = “defaultServletHandlerMapping” 70 = “handlerFunctionAdapter” 71 = “mvcUriComponentsContributor” 72 = “httpRequestHandlerAdapter” 73 = “simpleControllerHandlerAdapter” 74 = “handlerExceptionResolver” 75 = “mvcViewResolver” 76 = “mvcHandlerMappingIntrospector” 77 = “org。springframework。boot。autoconfigure。web。servlet。WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter” 78 = “defaultViewResolver” 79 = “viewResolver” 80 = “requestContextFilter” 81 = “org。springframework。boot。autoconfigure。web。servlet。WebMvcAutoConfiguration” 82 = “formContentFilter” 83 = “org。springframework。boot。autoconfigure。jdbc。DataSourceConfiguration$Hikari” 84 = “dataSource” 85 = “org。springframework。boot。autoconfigure。jdbc。DataSourceJmxConfiguration$Hikari” 86 = “org。springframework。boot。autoconfigure。jdbc。DataSourceJmxConfiguration” 87 = “org。springframework。boot。autoconfigure。jdbc。DataSourceAutoConfiguration$PooledDataSourceConfiguration” 88 = “org。springframework。boot。autoconfigure。jdbc。metadata。DataSourcePoolMetadataProvidersConfiguration$HikariPoolDataSourceMetadataProviderConfiguration” 89 = “hikariPoolDataSourceMetadataProvider” 90 = “org。springframework。boot。autoconfigure。jdbc。metadata。DataSourcePoolMetadataProvidersConfiguration” 91 = “org。springframework。boot。autoconfigure。jdbc。DataSourceInitializerInvoker” 92 = “org。springframework。boot。autoconfigure。jdbc。DataSourceInitializationConfiguration” 93 = “dataSourceInitializerPostProcessor” 94 = “org。springframework。boot。autoconfigure。jdbc。DataSourceAutoConfiguration” 95 = “spring。datasource-org。springframework。boot。autoconfigure。jdbc。DataSourceProperties” 96 = “com。baomidou。mybatisplus。autoconfigure。MybatisPlusAutoConfiguration” 97 = “sqlSessionFactory” 98 = “sqlSessionTemplate” 99 = “mybatis-plus-com。baomidou。mybatisplus。autoconfigure。MybatisPlusProperties”

SpringBoot的自動裝配配置實際是

透過starter+@Conditional註解+@Import註解+invokeBeanFactoryPostProcessors觸發擴充套件點共同實現的。

上面每個點都有一些比較有意思的設計。

這句話你第一次聽肯定不理解,沒關係,等我講完這這一節你就會明白SpringBoot核心裝配置的的幾個設計點了。

既然從容器中獲取到之前放入的物件ConfigurationClassPostProcessor這個物件非常核心,用來增加BeanDefination的。

接下來,我們就來詳細分析下它的核心原理,看看它是如何增加BeanDefination的,並且掃描出外部的starter的。

術語普及starter是什麼?

starter是SpringBoot定義的已經有一些預設javaConfig配置好類,透過封裝成jar,定義好maven的依賴,為我們提供了便利的配置起步依賴。

也就是說,我們可以定義了常見的Spring和第三方技術整合的預設配置,或者我們自己定義預設的整合第三方技術或者自研技術的配置,這樣的功能是非常便利的。

在上面我們提到了invokeBeanFactoryPostProcessors中核心觸發的擴充套件操作postProcessBeanDefinitionRegistry是:透過Spring內部的一個ConfigurationClassPostProcessor,補充了新的BeanDefinition。

當執行完這個類的擴充套件操作後,容器的BeanDefinitionMap中多了很多BeanDefinition,有SpringMVC相關的,有MyBatis相關、有我們自己定義的Controller和Service相關等。

那我們大體就可以分為兩步來看,

1) 自己定義的Controller和Service相關的BeanDefinition新增2) starter、其他框架的BeanDefinition新增

SpringBoot成長記8:SpringBoot如何實現自動裝配配置和擴充套件

文章配圖

自己定義的Controller和Service相關的BeanDefinition如何新增的?

在看第一步之前,你可以思考下,我們定義的Bean是不是通常有以下幾種

@Bean+@Configuration註解定義的,@Service,@Controller等註解定義的、也有用xml、groovy定義的bean

而且可以透過@Import,@ImportResource匯入其他的xml或者JavaConfig定義的Bean,@ComponentScan指定掃描Bean的路徑等等。

也就是說有一大堆註解需要我們分析和解析。這個是需要一個解析器和掃描器來進行的。就類似於之前我們提到的Reader和Scanner。

那麼接下來,我們看下第一步,自己定義的Controller和Service相關的BeanDefinition是如何被新增的?

剛才我們分析到,有一大堆註解需要ConfigurationClassPostProcessor分析和解析,那ConfigurationClassPostProcessor中核心的那幾個元件來負責做這些事情的呢?

讓我先來看下ConfigurationClassPostProcessor它擴充套件方法的脈絡:

@Overridepublic void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) { int registryId = System。identityHashCode(registry); if (this。registriesPostProcessed。contains(registryId)) { throw new IllegalStateException( “postProcessBeanDefinitionRegistry already called on this post-processor against ” + registry); } if (this。factoriesPostProcessed。contains(registryId)) { throw new IllegalStateException( “postProcessBeanFactory already called on this post-processor against ” + registry); } this。registriesPostProcessed。add(registryId); processConfigBeanDefinitions(registry);}

方法脈絡其實很簡單,核心觸發了processConfigBeanDefinitions這個方法,其餘的if判斷只是些校驗而已。

而觸發的這個方法內容就比較多了:

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) { List configCandidates = new ArrayList<>(); String[] candidateNames = registry。getBeanDefinitionNames(); for (String beanName : candidateNames) { BeanDefinition beanDef = registry。getBeanDefinition(beanName); if (beanDef。getAttribute(ConfigurationClassUtils。CONFIGURATION_CLASS_ATTRIBUTE) != null) { if (logger。isDebugEnabled()) { logger。debug(“Bean definition has already been processed as a configuration class: ” + beanDef); } } else if (ConfigurationClassUtils。checkConfigurationClassCandidate(beanDef, this。metadataReaderFactory)) { configCandidates。add(new BeanDefinitionHolder(beanDef, beanName)); } } // Return immediately if no @Configuration classes were found if (configCandidates。isEmpty()) { return; } // Sort by previously determined @Order value, if applicable configCandidates。sort((bd1, bd2) -> { int i1 = ConfigurationClassUtils。getOrder(bd1。getBeanDefinition()); int i2 = ConfigurationClassUtils。getOrder(bd2。getBeanDefinition()); return Integer。compare(i1, i2); }); // Detect any custom bean name generation strategy supplied through the enclosing application context SingletonBeanRegistry sbr = null; if (registry instanceof SingletonBeanRegistry) { sbr = (SingletonBeanRegistry) registry; if (!this。localBeanNameGeneratorSet) { BeanNameGenerator generator = (BeanNameGenerator) sbr。getSingleton( AnnotationConfigUtils。CONFIGURATION_BEAN_NAME_GENERATOR); if (generator != null) { this。componentScanBeanNameGenerator = generator; this。importBeanNameGenerator = generator; } } } if (this。environment == null) { this。environment = new StandardEnvironment(); } // Parse each @Configuration class ConfigurationClassParser parser = new ConfigurationClassParser( this。metadataReaderFactory, this。problemReporter, this。environment, this。resourceLoader, this。componentScanBeanNameGenerator, registry); Set candidates = new LinkedHashSet<>(configCandidates); Set alreadyParsed = new HashSet<>(configCandidates。size()); do { parser。parse(candidates); parser。validate(); Set configClasses = new LinkedHashSet<>(parser。getConfigurationClasses()); configClasses。removeAll(alreadyParsed); // Read the model and create bean definitions based on its content if (this。reader == null) { this。reader = new ConfigurationClassBeanDefinitionReader( registry, this。sourceExtractor, this。resourceLoader, this。environment, this。importBeanNameGenerator, parser。getImportRegistry()); } this。reader。loadBeanDefinitions(configClasses); alreadyParsed。addAll(configClasses); candidates。clear(); if (registry。getBeanDefinitionCount() > candidateNames。length) { String[] newCandidateNames = registry。getBeanDefinitionNames(); Set oldCandidateNames = new HashSet<>(Arrays。asList(candidateNames)); Set alreadyParsedClasses = new HashSet<>(); for (ConfigurationClass configurationClass : alreadyParsed) { alreadyParsedClasses。add(configurationClass。getMetadata()。getClassName()); } for (String candidateName : newCandidateNames) { if (!oldCandidateNames。contains(candidateName)) { BeanDefinition bd = registry。getBeanDefinition(candidateName); if (ConfigurationClassUtils。checkConfigurationClassCandidate(bd, this。metadataReaderFactory) && !alreadyParsedClasses。contains(bd。getBeanClassName())) { candidates。add(new BeanDefinitionHolder(bd, candidateName)); } } } candidateNames = newCandidateNames; } } while (!candidates。isEmpty()); // Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes if (sbr != null && !sbr。containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) { sbr。registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser。getImportRegistry()); } if (this。metadataReaderFactory instanceof CachingMetadataReaderFactory) { // Clear cache in externally provided MetadataReaderFactory; this is a no-op // for a shared cache since it‘ll be cleared by the ApplicationContext。 ((CachingMetadataReaderFactory) this。metadataReaderFactory)。clearCache(); } }

方法長或者不好理解,沒有關係。你用我教你的思想分析就好,

按照for迴圈和if,劃分脈絡,抓大放小後,最核心的邏輯可以分為如下3點:

1)第一個for迴圈:獲取BeanDefinitionMap所有的beanName,查找出標記了@Configuration註解的BeanDefinition,這裡找到的其實是learnSpringBootApplication

2)if邏輯,選擇了BeanNameGenerator,bean的名稱生成器相關的設定

3)do-while,建立了ConfigurationClassParser和ConfigurationClassBeanDefinitionReader去掃描和解析ClassPath下的BeanDefinition

整體如下圖所示:

SpringBoot成長記8:SpringBoot如何實現自動裝配配置和擴充套件

文章配圖

這裡我們要強調的是,上面這段邏輯在處理的是我們自定義的Bean,包括Controller、Service和整合其他技術配置的Bean的。

你可以思考下,上面的處理思路就是,查詢到BeanDefination,解析和新增BeanDefination。

從哪裡開始查詢BeanDefination呢?

透過之前prepare和create容器context的時候,透過 Initializers增加了預設的internalBeanDefination和LearnSpringBootApplication的BeanDefination。

(忘記的同學,可以回顧下prepare和create容器,成長記5和6)

ConfigurationClassPostProcessor這裡就是遍歷之前的BeanDefinationMap集合,它查詢的是所有包含@Configuration的BeanDefination

最終查詢到的只有一個LearnSpringBootApplication的BeanDefination。

如下圖所示:

SpringBoot成長記8:SpringBoot如何實現自動裝配配置和擴充套件

文章配圖

怎麼解析出BeanDefination?

當知道了主要是找到了LearnSpringBootApplication這個BeanDefination,以這個為入口開始分析解析

javaConfig定義的bean,其實本質是找到所有@Configuration的配置類,@ComponentScan等註解的類,挨個解析它們定義範圍的Bean。

xml和groovy也有對應的查詢方式,就不贅述了。

具體怎麼處解析我們定義的Bean的呢?

肯定要處理很多註解的,比如@ComponentScan註解、@ImportSource等等註解。

主要使用了的元件主要就是ConfigurationClassBeanDefinitionReader、ConfigurationClassParser,透過元件的parse方法來執行解析的。

概況如下圖所示:

SpringBoot成長記8:SpringBoot如何實現自動裝配配置和擴充套件

文章配圖

執行到這裡,自己定義的Controller和Service相關的BeanDefinition就新增完了。

基本就是從Resource->ClassLoader->註解解析,篩選->ConfigurationClass->BeanDefinition

org。springframework。context。annotation。ClassPathScanningCandidateComponentProvider#scanCandidateComponentsresources = {Resource[7]@4174} 0 = {FileSystemResource@4181} “file [D:\Repository\Gitee\learn-project\learn-spring-projects\learn-springboot\target\classes\org\mfm\learn\springboot\LearnSpringBootApplication。class]” 1 = {FileSystemResource@4185} “file [D:\Repository\Gitee\learn-project\learn-spring-projects\learn-springboot\target\classes\org\mfm\learn\springboot\controller\UserController。class]” 2 = {FileSystemResource@4186} “file [D:\Repository\Gitee\learn-project\learn-spring-projects\learn-springboot\target\classes\org\mfm\learn\springboot\mapper\UserMapper。class]” 3 = {FileSystemResource@4187} “file [D:\Repository\Gitee\learn-project\learn-spring-projects\learn-springboot\target\classes\org\mfm\learn\springboot\model\User。class]” 4 = {FileSystemResource@4188} “file [D:\Repository\Gitee\learn-project\learn-spring-projects\learn-springboot\target\classes\org\mfm\learn\springboot\service\IUserService。class]” 5 = {FileSystemResource@4189} “file [D:\Repository\Gitee\learn-project\learn-spring-projects\learn-springboot\target\classes\org\mfm\learn\springboot\service\MyBeanPostProcessor。class]” 6 = {FileSystemResource@4190} “file [D:\Repository\Gitee\learn-project\learn-spring-projects\learn-springboot\target\classes\org\mfm\learn\springboot\service\UserServiceImpl。class]”

{ConfigurationClass@4654} “ConfigurationClass: beanName ’userController‘, class path resource [org/mfm/learn/springboot/controller/UserController。class]” -> {ConfigurationClass@4654} “ConfigurationClass: beanName ’userController‘, class path resource [org/mfm/learn/springboot/controller/UserController。class]”{ConfigurationClass@4853} “ConfigurationClass: beanName ’myBeanPostProcessor‘, class path resource [org/mfm/learn/springboot/service/MyBeanPostProcessor。class]” ->

beanDefinitionNames = {ArrayList@4316} size = 10 0 = “org。springframework。context。annotation。internalConfigurationAnnotationProcessor” 1 = “org。springframework。context。annotation。internalAutowiredAnnotationProcessor” 2 = “org。springframework。context。annotation。internalCommonAnnotationProcessor” 3 = “org。springframework。context。event。internalEventListenerProcessor” 4 = “org。springframework。context。event。internalEventListenerFactory” 5 = “learnSpringBootApplication” 6 = “org。springframework。boot。autoconfigure。internalCachingMetadataReaderFactory” 7 = “userController” 8 = “myBeanPostProcessor” 9 = “userServiceImpl”

SpringBoot成長記8:SpringBoot如何實現自動裝配配置和擴充套件

文章配圖

starter和其他框架的BeanDefinition如何新增的?

透過上面的分析你應該知道了ConfigurationClassPostProcessor是透過ConfigurationClassParser的parse方法查詢和解析ClassPath下的BeanDefination

主要分為兩步:

第一步主要是新增自己定義的Controller和Service相關的BeanDefinition

第二步主要做了什麼?

答案是:添加了starter中其他框架的BeanDefinition如何新增的

那透過什麼新增starter中的BeanDefinition呢?

在之前第一步解析import註解的時候,會掃描出來一個

AutoConfigurationImportSelector

。透過這個Selector會補充新增starter中的BeanDefinition

如下圖所示:

SpringBoot成長記8:SpringBoot如何實現自動裝配配置和擴充套件

文章配圖

AutoConfigurationImportSelector具體怎麼補充starter中的BeanDefinition,

其實一句話概括就是查找了所有ClassPath下的META-INF/spring.factories定義的EnableAutoConfiguration.class,透過條件註解解析加載出對應的自動配置,將配置中的@Bean載入為BeanDefinition

我透過一張圖給大家解釋下:

SpringBoot成長記8:SpringBoot如何實現自動裝配配置和擴充套件

文章配圖

這裡有大堆的封裝,不是非常重要,可以跳過,大家知道封裝了之前掃描import註解獲取的AutoConfigurationImportSelector這個元件就可以了。

上圖核心就是讀取了

META-INF/spring.factories

中定義的一對AutoConfiguration。有了這些AutoConfiguration,自然就可以解析出對應的BeanDefinition了。

SpringBoot成長記8:SpringBoot如何實現自動裝配配置和擴充套件

文章配圖

思考:ConfigurationClassPostProcessor的新增BeanDefination的設計

ConfigurationClassParser,內部使用了Scanner,掃描了ClassPath下幾乎所有的BeanDefination,自己定義的註解bean也好,外部的基於條件註解的自動配置也好,都會掃描到,它們統統為Resource資源,只不過這些Resource資源表示的是Bean的配置類而已,一般使用Java定義的話都是ConfigurationClasss,它們作為候選者,透過Reader解析器解析後,將所有的BeanDefination放入到容器中。

其實符合我們之前將Spring容器抽象設計的思想,Resource——>Reader——>BeanDefination,只不過它封裝很多元件類來實現這個過程而已。

經過思考後,你可以抓住重點,概況總結下。最終SpringBoot的Starter進行自動裝配配置的核心流程和設計我們可以概況如下圖所示:

SpringBoot成長記8:SpringBoot如何實現自動裝配配置和擴充套件

文章配圖

第三方技術如何進行擴充套件的

思考:mybatis的Mapper如何被新增的?

之前我們添加了很多透過註解定義bean,無論是我們自己些的@Service、@Controller定義的Bean,還是starter中的@Bean。

可是還有一些Bean沒有註解,也能被新增,比如mybatis的mapper。

public interface UserMapper extends BaseMapper {}@MapperScan(“org。mfm。learn。springboot。mapper”)@SpringBootApplicationpublic class LearnSpringBootApplication {}

其實如果你瞭解無論是註解定義的還是沒有註解的,其實在Spring看來都是一樣的,因為它們都是Resource,我是根據不同的Scanner掃描到不同的Resource資源,透過指定的方式解析出來一些想要的Resource為BeanDefination而已。

那麼你如果想新增額外的BeanDefination,可以自己定義Scanner來補充資源,定義指定的方式新增BeanDefination就行。

mybatis就是這麼幹的。

透過上面的程式碼你其實可以看到@MapperScan註解其實

定義了自己的Scanner:MapperScannerRegistrar

@Retention(RetentionPolicy。RUNTIME)@Target(ElementType。TYPE)@Documented@Import(MapperScannerRegistrar。class)@Repeatable(MapperScans。class)public @interface MapperScan {

MapperScannerRegistrar類實現了關鍵的兩個介面ImportBeanDefinitionRegistrar, ResourceLoaderAware。

public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {}

你可以猜想下:

一個應該Spring設計對新增額外BeanDefinition設計的介面ImportBeanDefinitionRegistrar

一個應該是ResourceLoaderAware,設定好ResourceLoader這個屬性,你還記得ResourceLoader,它是不是封裝ClassLoader,可以Classpath查詢Resource。

因為Scanner主要的作用就是查詢和過濾資源。

知道了這個邏輯理解Mapper怎麼新增的就不難了。我們來看下程式碼:

public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {@Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { AnnotationAttributes mapperScanAttrs = AnnotationAttributes 。fromMap(importingClassMetadata。getAnnotationAttributes(MapperScan。class。getName())); if (mapperScanAttrs != null) { registerBeanDefinitions(mapperScanAttrs, registry); } } void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry) { ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry); // this check is needed in Spring 3。1 Optional。ofNullable(resourceLoader)。ifPresent(scanner::setResourceLoader); Class<? extends Annotation> annotationClass = annoAttrs。getClass(“annotationClass”); if (!Annotation。class。equals(annotationClass)) { scanner。setAnnotationClass(annotationClass); } Class<?> markerInterface = annoAttrs。getClass(“markerInterface”); if (!Class。class。equals(markerInterface)) { scanner。setMarkerInterface(markerInterface); } Class<? extends BeanNameGenerator> generatorClass = annoAttrs。getClass(“nameGenerator”); if (!BeanNameGenerator。class。equals(generatorClass)) { scanner。setBeanNameGenerator(BeanUtils。instantiateClass(generatorClass)); } Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs。getClass(“factoryBean”); if (!MapperFactoryBean。class。equals(mapperFactoryBeanClass)) { scanner。setMapperFactoryBeanClass(mapperFactoryBeanClass); } scanner。setSqlSessionTemplateBeanName(annoAttrs。getString(“sqlSessionTemplateRef”)); scanner。setSqlSessionFactoryBeanName(annoAttrs。getString(“sqlSessionFactoryRef”)); List basePackages = new ArrayList<>(); basePackages。addAll( Arrays。stream(annoAttrs。getStringArray(“value”)) 。filter(StringUtils::hasText) 。collect(Collectors。toList())); basePackages。addAll( Arrays。stream(annoAttrs。getStringArray(“basePackages”)) 。filter(StringUtils::hasText) 。collect(Collectors。toList())); basePackages。addAll( Arrays。stream(annoAttrs。getClassArray(“basePackageClasses”)) 。map(ClassUtils::getPackageName) 。collect(Collectors。toList())); scanner。registerFilters(); scanner。doScan(StringUtils。toStringArray(basePackages)); } }

可以看到MapperScannerRegistrar中主要執行是透過ClassPathMapperScanner來執行的,而且解析了@MapperScan中的屬性,看看是否要過濾那些Bean之類的。

有了MapperScannerRegistrar,那它什麼時候執行的呢?透過一張圖我給大家講下:

SpringBoot成長記8:SpringBoot如何實現自動裝配配置和擴充套件

文章配圖

可以看到就是在我們之前分析觸發擴充套件操作的時候,在執行ConfigurationClassBeanDefinitionReader#loadBeanDefinitions()是,會檢查到標記了@MapperScan註解的ConfigurationClass,處理這個類的時候,會執行MapperScannerRegistrar。

基於ImportBeanDefinitionRegistrar的擴充套件

Spring容器有很多擴充套件點,剛才看到MyBatis利用了ImportBeanDefinitionRegistrar進行擴充套件,還有很多技術都是用這種方式進行一些擴充套件。

比如Qconfig 、Sedis 、QSchedule 等

Qconfig QConfigAutoConfiguration

@Configuration@Import(QConfigAutoConfiguration。Register。class)@Internalpublic class QConfigAutoConfiguration {}

Sedis DbaccessAutoConfiguration

@Configuration@Import(DbaccessAutoConfiguration。Register。class)public class DbaccessAutoConfiguration {}

QSchedule QScheduleAutoConfiguration

@Configuration@Import(QScheduleAutoConfiguration。Register。class)public class QScheduleAutoConfiguration {}

我們以Qconfig 舉例,透過ImportBeanDefinitionRegistrar可以進行什麼樣的擴充套件呢?如下所示:

@Configuration@Import(QConfigAutoConfiguration。Register。class)public class QConfigAutoConfiguration { class Register implements ImportBeanDefinitionRegistrar{ public Register() { //讀取本地配置 //從配置中心獲取配置 } //新增關鍵的beanDefinition @Override public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry registry) { } }}

其實無論如何,你要透過現象看到本質,

擴充套件操作最終,本質都是為了給容器補充一些BeanDefinition、一些Bean,為Bean設定一些屬性。

而這些Bean實現什麼功能,都可以,可以mq相關,配置相關,資料訪問相關都可以,這就是Spring容器幫我們建立物件,物件的維護又不失去靈活性,這一點是非常好的。當然前提是你得熟練掌握Spring容器。

SpringBoot成長記8:SpringBoot如何實現自動裝配配置和擴充套件

文章配圖

基於BeanFactoryPostProcessor的擴充套件

我們現在一直在整個run方法的流程中分析invokeBeanFactoryPostProcessors它的核心邏輯,前面透過invokeBeanFactoryPostProcessors中的if-else邏輯執行了很多擴充套件邏輯。

而invokeBeanFactoryPostProcessors除了if-else中的邏輯執行,其實還有外層的3個for迴圈需要執行。

上一節我們分析過for迴圈是在執行BeanFactoryPostProcessor的一個擴充套件操作。可以是之前我們已經將SpringBoot內部的BeanFactoryPostProcessor按照順序執行過了。後面要執行的這些BeanFactoryPostProcessor哪裡來的呢?

其實很簡單,我們執行if-else的邏輯後,觸發了很多自動裝配配置和第三方的擴充套件操作,補充了一大堆BeanDefinition,這些BeanDefinition如果有新的BeanFactoryPostProcessor,就需要進行再次觸發下BeanFactoryPostProcessor的擴充套件操作了。

至於觸發第三方技術定義的這些BeanFactoryPostProcessor,可以做什麼,其實就很多了。比如新增、修改BeanDefinition,解析自己第三方的配置檔案等等。

這裡我就不具體舉例了,簡單概況了下它執行的邏輯,整體如下圖所示:

SpringBoot成長記8:SpringBoot如何實現自動裝配配置和擴充套件

文章配圖

小結

到這裡,我們就分析完了SpringBoot非常核心的功能,透過分析invokeBeanFactoryPostProcessors的擴充套件點的執行,分析了SpringBoot自動裝配配置的原理。

其實這裡最關鍵的不是SpringBoot自動裝配的原理,最關鍵的主要是兩點:

第一點:自動裝配的關鍵其實是基於擴充套件點介面BeanFactoryPostProcessor的觸發設計,根於Resource到BeanDefinition的抽象設計。

其他的不過是繫結到了某個類或者方法的執行流程中而已,或者基於約定查找了固定名稱的配置檔案而已。

第二點:一個好的框架,是不斷完善的,在一些核心的操作中,如果想要靈活,仍可以做額外的擴充套件設計。

ConfigurationClass解析就是很好的例子,透過ImportBeanDefinitionRegistrar等擴充套件設計,靈活的開放出來,方便讓第三方技術對Bean配置的做補充等。

最後留一個小的思考:SpringMVC常用的核心元件的BeanDefinition是什麼時候載入的呢?如果理解了今天的內容,相信你肯定能回答上來。

答上來的話,其實你也就懂了SpringMVC如何和SpringBoot整合的了。

好,我們下節再見!

本文由部落格群發一文多發等運營工具平臺 OpenWrite 釋出