從原始碼中理解Spring Boot自動裝配原理

2022-09-16 06:00:49

個人部落格:槿蘇的知識鋪

一、什麼是自動裝配

SpringBoot 定義了一套介面規範,這套規範規定:SpringBoot在啟動時會掃描外部參照jar包中的META-INF/spring.factories檔案,將檔案中設定的型別資訊載入到Spring容器,並執行類中定義的各種操作。對於外部jar包來說,只需要按照SpringBoot定義的標準,就能將自己的功能裝配到SpringBoot中。

二、自動裝配的實現原理

自動裝配的實現,離不開SpringBootApplication這個核心註解。檢視這個註解的原始碼,我們會發現在SpringBootApplication註解上,存在著幾個註解,其中SpringBootConfigurationEnableAutoConfigurationComponentScan這三個註解是需要我們注意的。

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
		@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
	...
}

(1) ComponentScan

掃描被@Component 、@Service註解的bean,該註解預設會掃描啟動類所在的包下所有的類 ,可以自定義不掃描某些 bean。如SpringBootApplication註解原始碼所示,容器中將排除TypeExcludeFilterh和AutoConfigurationExcludeFilter。

(2) EnableAutoConfiguration

啟用 SpringBoot 的自動設定機制

(3) Configuration

允許在上下文中註冊額外的 bean 或匯入其他設定類

2.1 EnableAutoConfiguration詳解

@EnableAutoConfiguration是實現自動裝配的重要註解,在這個註解上存在以下兩個註解:AutoConfigurationPackageImport

@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
	...
}
2.1.1 AutoConfigurationPackage

表示對於標註該註解的類的包,應當使用AutoConfigurationPackages註冊。實質上,它負責儲存標註相關注解的類的所在包路徑。使用一個BasePackage類,儲存這個路徑。然後使用@Import註解將其注入到ioc容器中。這樣,可以在容器中拿到該路徑。

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
	@Override
	public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
		register(registry, new PackageImport(metadata).getPackageName());
	}
}

檢視AutoConfigurationPackages中的Registrar這個類的原始碼,在Registrar#registerBeanDefinitions方法中有這樣一句程式碼new PackageImport(metadata).getPackageName(),檢視PackageImport的構造器後不難發現,這裡獲取的是StandardAnnotationMetadata這個範例所在的包名。

/**
 * metadata: 實際上是 StandardAnnotationMetadata 範例。
 * metadata#getClassName(): 獲取標註 @AutoConfigurationPackage 註解的類的全限定名。
 * ClassUtils.getPackageName(…): 獲取其所在包。
 */
PackageImport(AnnotationMetadata metadata) {
	this.packageName = ClassUtils.getPackageName(metadata.getClassName());
}

此時再回去看Registrar#registerBeanDefinitions中呼叫的AutoConfigurationPackages#register方法

public static void register(BeanDefinitionRegistry registry, String... packageNames) {
	// BEAN:AutoConfigurationPackages類的全限定名
	// 此時判斷BeanDefinitionRegistry中是否存在以BEAN作為beanName的BeanDefinition物件
	// 如果不存在,走else方法,構造了一個BackPackages範例,進行註冊
	if (registry.containsBeanDefinition(BEAN)) {
		BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
		ConstructorArgumentValues constructorArguments = beanDefinition
				.getConstructorArgumentValues();
		constructorArguments.addIndexedArgumentValue(0,
				addBasePackages(constructorArguments, packageNames));
	} else {
		GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
		beanDefinition.setBeanClass(BasePackages.class);
		beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
		beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
		registry.registerBeanDefinition(BEAN, beanDefinition);
	}
}
2.1.2 Import(AutoConfigurationImportSelector.class)

它是利用AutoConfigurationImportSelector機制再來給容器中批次匯入一些設定東西的,接下來帶大家瞭解究竟匯入了哪些內容。

/**
 * AutoConfigurationImportSelector類中存在一個叫selectImports的方法,就是我們到底要向容器中匯入哪些
 * 內容,都會在這裡進行掃描並匯入。
 */
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
    // 判斷EnableAutoConfiguration是否開啟預設開啟true
    if (!isEnabled(annotationMetadata)) {
        return NO_IMPORTS;
    }
    // 1.載入META-INF/spring-autoconfigure-metadata.properties 檔案
    // 2.從中獲取所有符合條件的支援自動裝配的類
    // 自動設定類全名.條件=條件的值
    AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
        .loadMetadata(this.beanClassLoader);
    // 獲取AutoConfigurationEntry
    AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,annotationMetadata);
    return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

接下來重點看getAutoConfigurationEntry(annotationMetadata)方法,利用這個方法向容器中批次匯入一些預設支援自動設定的類,當你理解了這部分內容之後,就基本瞭解了Spring Boot是如何進行自動裝配的,廢話不多說,讓我們進入正題。

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
    // 判斷EnableAutoConfiguration是否開啟預設開啟true
    if (!isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    }
    // 獲取註解屬性
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    // 呼叫getCandidateConfigurations(annotationMetadata, attributes),利用loadSpringFactories(ClassLoader classLoader)載入當前系統所有的META-INF/spring.factories檔案,得到預設支援的自動設定的類的列表
    List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    // 去除重複的 configuration
    configurations = removeDuplicates(configurations);
    // 獲取到SpringBootApplication上exclude和excludeName設定的需要排除的類
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    // 檢查configurations是否含有exclusions中的類
    checkExcludedClasses(configurations, exclusions);
    // 將exclusions中的類從configurations中排除
    configurations.removeAll(exclusions);
    // 對所有候選的自動設定類進行篩選,
    // 比如ConditionalOnProperty  當屬性存在時
    // ConditionalOnClass  當class存在
    // ConditionalOnMissingClass  當這個clas不存在時才去設定
    // 過濾器
    configurations = getConfigurationClassFilter().filter(configurations);
    // 將自動設定的類,匯入事件監聽器,並觸發fireAutoConfigurationImportEvents事件
	// 載入META-INF\spring.factories中的AutoConfigurationImportListener 
    fireAutoConfigurationImportEvents(configurations, exclusions);
    // 建立AutoConfigurationEntry物件
    return new AutoConfigurationEntry(configurations, exclusions);
}