SpringBoot自動裝配原理解析

Java伴我餘生發表於2021-03-09

首先對於一個SpringBoot工程來說,最明顯的標誌的就是 @SpringBootApplication它標記了這是一個SpringBoot工程,所以今天的 SpringBoot自動裝配原理也就是從它開始說起。

自動裝配流程

首先我們來看下@SpringBootApplication 這個註解的背後又有什麼玄機呢,我們按下 ctrl + 滑鼠左鍵,輕輕的點一下,此時見證奇蹟的時刻..
我們看到如下優雅的程式碼:

這其中有兩個比較容易引起我們注意的地方,一個是@SpringBootConfiguration註解,另一個是 @EnableAutoConfiguration註解;之所以說這個兩個註解比較吸引我們的眼球, 不是因為它們長大的好看,而是因為其他的註解太難看了(主要是因為其他的註解我們都是比較熟悉,即使不知道他們是幹什麼的,可以肯定更自動裝配是沒有關係的)。 然後我們又伸出了邪惡的小手,開啟了熟悉的操作,按下了Ctrt + 滑鼠左鍵,瞪著色咪咪的小眼睛,瞳孔放大了百倍等待著奇蹟的出現... 擦... 擦...擦...

什麼也沒有...
那我要你有何用,這麼頂級的世界級的開源專案,怎麼會讓一個沒用的傢伙存在呢? 於是動用了上億的腦細胞大軍,經過複雜的運算,得出了一個不靠譜的結論:它可能使用來標記這是一個SpringBoot工程的配置。因為SpringBootConfiguration 翻譯過來就是SpringBoot的配置,於是心中又是幾萬只羊駝在萬馬奔騰,大漠飛揚。

氣定神閒之後,秉承著·失敗是成功之母"的信念, 熟練的左手行雲流水般的按下了 Ctrl + Table 鍵,回到了最初的的地方。眼睛盯著 @EnableAutoConfiguration ,環顧左右,在位址列輸入了谷歌翻譯, 結果顯示 自動裝配。我找的就是你,真是眾裡尋他千百度,那人卻在燈火闌珊處。 熟練的按下了 Ctrl +左鍵,迫不及待的想要進入; 心裡默默背誦起了《桃花源記》的經典詩句 ∶

林盡水源,便得一山,山有小口,彷彿若有光。便舍船,從口入。初極狹,才通人。復行數十步,豁然開朗

此時此刻心情愉悅,有過前面的經歷之後,在面對新的世界時候,我們淡定了許多。 此時大腦高速運轉,沒有再糾結,直搗黃龍,進入了 AutoConfigurationImportSelector.class 類,因為谷歌翻譯告訴我們,這個是自動配置匯入選擇器。 於是我們發現了—片新天地

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
		ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {


	@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
                // 獲取自動配置的實體
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}

        // 具體用來載入自動配置類得方法
	protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return EMPTY_ENTRY;
		}
		AnnotationAttributes attributes = getAttributes(annotationMetadata);
                // 獲取候選的配置類,即使後宮佳麗三千,也是要篩選的
		List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
                // 根據情況,自動配置需要的配置類和不需要的配置了
		configurations = removeDuplicates(configurations);
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
		checkExcludedClasses(configurations, );
		configurations.removeAll(exclusions);
		configurations = getConfigurationClassFilter().filter(configurations);
		fireAutoConfigurationImportEvents(configurations, exclusions);
                // 返回最終需要的配置
		return new AutoConfigurationEntry(configurations, exclusions);
	}
}

而這個自動配置的實體 AutoConfigurationEntry 裡面有兩個屬性,configurationsexclusions

	protected static class AutoConfigurationEntry {
                // 用來儲存需要的配置項
		private final List<String> configurations;
                // 用來儲存排除的配置項
		private final Set<String> exclusions;

		private AutoConfigurationEntry() {
			this.configurations = Collections.emptyList();
			this.exclusions = Collections.emptySet();
		}
        }

在後面可以看到 getAutoConfigurationEntry()方法返回了一個物件 return new AutoConfigurationEntry(configurations, exclusions);這裡也就是把我們需要的配置都拿到了。

那他是怎麼拿到的候選的配置類呢? 我們接著看這個獲取候選配置類的方法 List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
進到方法後我們看到下面這個方法具體獲取候選配置類的方法內容

這裡我們跟著斷點去走,首先進入getSpringFactoriesLoaderFactoryClass()方法

	protected Class<?> getSpringFactoriesLoaderFactoryClass() {
                // 返回的是EnableAutoConfiguration位元組碼物件
		return EnableAutoConfiguration.class;
	}

接著我們在進入getBeanClassLoader()方法,這裡就是一個類載入器

protected ClassLoader getBeanClassLoader() {
		return this.beanClassLoader;
	}

最後我們在進入loadFactoryNames()方法,這個方法就是根據剛才的位元組碼檔案和類載入器來找到候選的配置類。傳遞過來的位元組碼

	public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
		ClassLoader classLoaderToUse = classLoader;
		if (classLoaderToUse == null) {
			classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
		}
                // 獲取的EnableAutoConfiguration.class的許可權定名
                //org.springframework.boot.autoconfigure.EnableAutoConfiguration
		String factoryTypeName = factoryType.getName();
		return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
	}

如下圖:

最後通過loadSpringFactories()來獲取到所有的配置類

	private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
                // 快取載入的配置類
		Map<String, List<String>> result = cache.get(classLoader);
		if (result != null) {
			return result;
		}
                
		result = new HashMap<>();
		try {
                        // 去資源目錄下找
			Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();
				UrlResource resource = new UrlResource(url);
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
					String factoryTypeName = ((String) entry.getKey()).trim();
					String[] factoryImplementationNames =
							StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
					for (String factoryImplementationName : factoryImplementationNames) {
						result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
								.add(factoryImplementationName.trim());
					}
				}
			}

			// Replace all lists with unmodifiable lists containing unique elements
			result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
					.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
                        // 載入完成放到快取中
			cache.put(classLoader, result);
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
                // 返回載入到的配置類
		return result;
	}

這裡我們要看下怎麼從資源目錄下 FACTORIES_RESOURCE_LOCATION 載入的。下面是載入配置檔案的路徑:

也就是專案啟動的時候會去載入所有 META-INF 下的所有的 spring.factories 檔案,我們搜一下這個這個檔案,我搭建的是一個很簡單的 SpringBoot 工程,它會去這幾個 jar 裡面找相關的配置類

但是最後自動裝配的類是這個spring-boot-autoconfigure-2.4.3.RELEASE.jar

而根據EnabLeAutoConfiguration.class位元組碼載入的配置類就只有這118自動配置類

小結
實際上SpringBoot的自動裝配原理,其實就是在專案啟動的時候去載入META-INF下的 spring.factories 檔案,好像也沒有那麼高大上。當然在啟動的過程中還會有其他的配置項的載入,這裡我們麼直說了自動裝配的載入過程。希望對大家可以有所啟發。
問題∶ 明白了SpringBoot的自動裝配原理, 如果我們需要讓專案啟動的時候就載入我們自定義的配置類, 該如何寫呢?

相關文章