SpringBoot自動化配置mybatis源碼分析

上一篇文章,講到源碼的分析,這次來分析mybatis與spring進行集成的源碼。

Spring骨架接口定義

spring裏的一些基本概念,要做一個簡單的介紹,否則深入到spring與mybatis集成的源碼是比較難理解。
在這裏插入圖片描述
Spring的類圖輪廓,其中BeanFactory是容器的最抽象接口,下面一些接口擴展了接口抽象,繼承的層次越多,一般接口的能力就越強大,設計這樣的層次接口,但是真正實現類會聚合在幾個類。
所以爲什麼要設計這樣複雜的接口層次呢,寫一個大接口,去實現不就可以了嗎,面向對象設計原則裏有一個準則就是接口隔離,和最小職責。對於每個容器的試用方來說,耦合儘量少的接口信息,所以往往會聲明爲更抽象的接口,這樣降低耦合度。另一方面接口的層次結構,可以給後面的擴展提供空間,只是現在spring的默認容器實現了比較豐富的很多接口,可能其他jar通過SPI實現了其他類型的容器,這就給其他jar提供了選擇的權利。
在這裏插入圖片描述

Spring執行與重要擴展

在這裏插入圖片描述
整個執行鏈路主要就是在掃描配置,加載類註冊爲bean到一個容器,並在內存構建一個對象關係,依賴注入就是通過大的工廠容器注入,控制權的反轉就是把對象生成的控制權由開發者反轉給容器去處理。圖上描述了AbstractApplicationContext整個容器啓動的的SOP,其中有一大特點就是對於擴展點的開放,我們可以在整個容器的聲明週期去做很多幹預,例如bean的註冊,屬性的初始化,初始化的前後等等。spring提供了類似於觀察者模式的方式處理。這裏介紹幾個此次分析需要的幾個重要擴展接口:
BeanFactoryPostProcessor是容器在初始化beanDefinition但是還沒實力話的時候擴展接口。
FactoryBean 是一個特殊的bean,spring在處理實現這個接口的bean的時候,並不會直接把它註冊爲一個bean,而是會調用其getObject()方法返回的對象作爲bean管理。
BeanDefinition 這是Spring對於所有Bean的一個封裝,因爲在Spring看來任何需要被容器管理的對象都需要遵從一套標準的管理規則,例如其是否單例,其在容器中的是否單例,是否懶加載等等,所以Spring容器啓動的第一步就是把所有識別所有需要被管理的類,並進行封裝成BeanDefinition管理。

相關jar

Spring boot環境下,我們只要依賴一堆jar即可完成對很多功能的集成,同樣mybatis也是這樣被集成進來,其主要涉及到這樣四個包。
在這裏插入圖片描述
其中Spring主要包括了spring核心的幾個包和spring boot的包,這裏不展開。
mybatis-spring-boot-autoconfigure與mybatis-spring是完成mybatis與spring集成的模塊,後者主要完成了前者的一個自動裝配。

源碼分析

在spring boot的環境下應用啓動會通過SpringApplication這個類的run方法啓動spring boot容器。
SpringApplication.run主要做了是那麼事呢?
在這裏插入圖片描述
我們進入創建容器的方法,內部會根據當前環境選擇對應默認的上下文實現。如果沒有集成servlet容器就會使用AnnotationConfigApplicationContext進行初始化。其支持註冊配置的容器上下文,其構造方法如下:
在這裏插入圖片描述
這是Spring-context的類,其是在spring3.0才支持的註解方式類掃描定義註冊上下文。AnnotationConfigApplicationContext同時也間接實現了BeanDefinitionRegistry,也就是說其可以去註冊bean,它把自己當作註冊器傳遞給註冊器AnnotatedBeanDefinitionReader。在這裏插入圖片描述
registerAnnotationConfigProcessors這裏完成了默認BeanFactoryPostProcessor。
在這裏插入圖片描述
這裏會註冊一個擴展點ConfigurationClassPostProcessor,其就是用來在容器啓動中去擴展處理配置bean的,我們再回顧一下它會在哪裏進行切入。
在這裏插入圖片描述
也就是整個spring容器進行刷新的時候擴展。
在這裏插入圖片描述
並且其先後順序先處理registry註冊處理器,再處理容器處理器,簡單來說先調用postProcessBeanDefinitionRegistry, 再調用postProcessBeanFactory:
在這裏插入圖片描述
所以我們先分析postProcessBeanDefinitionRegistry
在這裏插入圖片描述
processConfigBeanDefinitions實現比較長,我們挑我們關注點,不要陷入到其他實現細節去。其中關鍵代碼

	public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
		List<BeanDefinitionHolder> configCandidates = new ArrayList<BeanDefinitionHolder>();
		String[] candidateNames = registry.getBeanDefinitionNames();
		// 此處遍歷所有已經註冊的bean
		for (String beanName : candidateNames) {
			BeanDefinition beanDef = registry.getBeanDefinition(beanName);
			if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) ||
					ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) {
				if (logger.isDebugEnabled()) {
					logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
				}
			}
			else if 
			(ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
			// checkConfigurationClassCandidate遞歸判斷了,bean的註解是否包含@Configuration註解,如果是就添加到configCandidates,也就是把它識別爲一個配置bean的一個配置類,後面會再次處理這個bean的註解,解析它是否還定義了其他的bean的加載,bean的註冊,例如@ComponentScan和@Import的方式
				configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
			}
		}
		
		// Parse each @Configuration class
		ConfigurationClassParser parser = new ConfigurationClassParser(
				this.metadataReaderFactory, this.problemReporter, this.environment,
				this.resourceLoader, this.componentScanBeanNameGenerator, registry);

		Set<BeanDefinitionHolder> candidates = new LinkedHashSet<BeanDefinitionHolder>(configCandidates);
		Set<ConfigurationClass> alreadyParsed = new HashSet<ConfigurationClass>(configCandidates.size());
		do {
			parser.parse(candidates);
			parser.validate();
			// ...
		}
		while (!candidates.isEmpty());
		// ...
		}
	}

這裏回到SpringBoot的啓動類的註解@SpringBootApplication,其定義在這裏插入圖片描述
因此會被識別爲一個候選配置根類。
@EnableAutoConfiguration的定義:
在這裏插入圖片描述
其有個Import,導入了一個選擇器EnableAutoConfigurationImportSelector。

我們繼續回到processConfigBeanDefinitions方法,解析到配置根類之後
在這裏插入圖片描述
對其進行parse操作,一步步進入調用棧,會到doProcessConfigurationClass方法,在這裏插入圖片描述
這裏就開始處理剛纔配置根類的擴展導入BeanDefinition,doProcessConfigurationClass內部實現了一個遞歸掃描的結構processImports,大概做的事情就是根據這個根配置類去遍歷所有註解上需要導入的bean,對於SpringBoot的@SpringBootApplication有EnableAutoConfigurationImportSelector。
在這裏插入圖片描述
processImports會把所有選擇器(實現)
processImports處理完了所有Import註解擴展選擇器之後賦值給deferredImportSelectors。再在processDeferredImportSelectors進行統一處理。
這裏會把實現ImpoertSelector的接口select賦值給deferredImportSelectors,在processDeferredImportSelectors統一去處理

	private void processDeferredImportSelectors() {
		List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
		this.deferredImportSelectors = null;
		Collections.sort(deferredImports, DEFERRED_IMPORT_COMPARATOR);

		for (DeferredImportSelectorHolder deferredImport : deferredImports) {
			ConfigurationClass configClass = deferredImport.getConfigurationClass();
			try {
			// 遍歷selector進行導入
				String[] imports = deferredImport.getImportSelector().selectImports(configClass.getMetadata());
				processImports(configClass, asSourceClass(configClass), asSourceClasses(imports), false);
			}
			catch (BeanDefinitionStoreException ex) {
				throw ex;
			}
			catch (Throwable ex) {
				throw new BeanDefinitionStoreException("Failed to process import candidates for configuration class [" +
						configClass.getMetadata().getClassName() + "]", ex);
			}
		}
	}

在這裏插入圖片描述
在這裏插入圖片描述
SpringFactoriesLoader.loadFactoryNames的實現就是去指定目錄加載Factory
在這裏插入圖片描述
在這裏插入圖片描述
這裏加載jar的spring.factories文件,我們看下這個文件是啥
在這裏插入圖片描述
只要是xxx-autoconfigure都有這樣一個配置文件,再來看這個方法的調用
在這裏插入圖片描述
我們看mybatis的自動配置的包:
在這裏插入圖片描述
這就是提供了SPI機制去加載,SpringFactoriesLoader是spring-core的包的類,可以主動去加載所有jar的spring.factories進行初始化。mybatis的spring.factories裏配置了MybatisAutoConfiguration,就能被加載到,其內部初始化了SqlSessionFactory與Mapper接口的掃描

結論

所以springboot對於mybatis的集成主要通過Spring.factories,包括其他的自動化配置包,都是一個套路,根據源碼的分析,我們主要學習了,進行bean註冊的方式,不僅有xml、@Componet,還有@ComponentScan,實現ImportSelector或則實現ImportBeanDefinitionRegistrar。以後我們可以更加靈活的應用SpringBoot的這種擴展去控制bean加載的方式。

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