【Spring Boot源碼分析】@EnableAutoConfiguration註解(一)@AutoConfigurationImportSelector註解的處理

来源:https://www.cnblogs.com/desertfish/archive/2019/10/08/11637933.html
-Advertisement-
Play Games

@EnableAutoConfiguration註解是Spring Boot中配置自動裝載的總開關。本文將從@EnableAutoConfiguration入手,嘗試分析Spring Boot對@AutoConfigurationImportSelector註解的處理過程。 ...


一、概述

@EnableAutoConfiguration註解是Spring Boot中配置自動裝載的總開關。本文將從@EnableAutoConfiguration入手,嘗試通過源碼分析增強對Spring Boot的理解。   所用版本:Spring Boot 2.2.0.M5 + Spring Framework 5.2.0.RC1  

1. 功能的實現:(Spring Boot部分)

boot.autoconfigure.EnableAutoConfiguration註解   -> @Import了一個AutoConfigurationImportSelector實例   -> AutoConfigurationImportSelector類(implement ImportSelector),實現了selectImports() 方法,用來篩選被@Import的Configuration類(減去exclude等)

 

2. 介面的調度:(Spring部分)

context.annotation.ConfigurationClassParser類的parse() 方法     -> 調用對應不同BeanDefinition類型的parse() 方法     |   -> 調用context.annotation.ConfigurationClassParser.doProcessConfigurationClass()方法處理ConfigurationClass     |       -> 調用processImports()方法來處理所有@Import註解     |           -> 遍歷每個@Import標簽,生成被註入的ImportSelector子類的實例     |               -> 對於普通ImportSelector,調用其selectImport()方法,篩掉exclude的,再嵌套processImports(),對需要被@Import的類的@Import註解進行處理     |               -> 對於DefferedImportSelector,只加入deferredImportSelectors列表中     -> 對defferedImportImportSelectors調用相應handler的process()方法進行處理         -> 對DefferedImportImportSelector調用processImports()

 

3. 介面在框架中的位置:(其中一條路徑,由頂向下)

【Spring Boot部分】 boot.SpringApplication.main() 或 ApplicationStarter.main() boot.SpringApplication.run() boot.SpringApplication.refreshContext() boot.SpringApplication.refresh() boot.web.servlet.context.ServletWebServerApplicationContext.refresh() 【Spring部分】 context.support.AbstractApplicationContext.refresh() context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors() context.support.PostPreprocessorRegistrationDelegate.invokeBeanFactoryPostProcessors() context.annotation.ConfigurationClassPostProcessor.postProcessBeanFactory() context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions() context.annotation.ConfigurationClassParser.parse()  (正是上一小結所述介面)

 

二、源碼細節

(SpringBoot) boot.autoconfigure.EnableAutoConfiguration註解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class) // 載入selector,識別AutoConfigutaion類並import
public @interface EnableAutoConfiguration {
	String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
	// @interface的參數,以方法形式聲明
	Class<?>[] exclude() default {};
	String[] excludeName() default {};
}
  @EnableAutoConfiguration註解有兩個參數和一個屬性變數。 註入了一個AutoConfigurationImportSelector類實例,看起來應該是用於篩選AutoConfiguration的Import的。 @AutoConfigurationPackage待以後另行分析。

 

(SpringBoot) boot.autoconfigure.AutoConfigurationImportSelector類

    public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
		ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
    //......
    @Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		// 如果AutoConfiguration沒開,返回{}
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
		// 將spring-autoconfigure-metadata.properties的鍵值對配置載入到PropertiesAutoConfigurationMetadata對象中並返回
		AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
				.loadMetadata(this.beanClassLoader);
        // 基於各種配置計算需要import的configuration和exclusion
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
				annotationMetadata);
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}
            
    // 判斷AudoConfiguration是否開啟
	protected boolean isEnabled(AnnotationMetadata metadata) {
		if (getClass() == AutoConfigurationImportSelector.class) {
			// 如果配置文件中有"spring.boot.enableautoconfiguration",返回該欄位的值;否則返回true
			return getEnvironment().getProperty(EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class, true);
		}
		return true;
	}
            
    protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
			AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return EMPTY_ENTRY;
		}
		// 獲取註解的屬性值
		AnnotationAttributes attributes = getAttributes(annotationMetadata);
		// 從META-INF/spring.factories文件中獲取EnableAutoConfiguration所對應的configurations
		List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
		// 去重,List轉Set再轉List
		configurations = removeDuplicates(configurations);
		// 從註解的exclude/excludeName屬性中獲取排除項
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
		// 對於不屬於AutoConfiguration的exclude報錯
		checkExcludedClasses(configurations, exclusions);
		// 從configurations去除exclusions
		configurations.removeAll(exclusions);
		// 由所有AutoConfigurationImportFilter類的實例再進行一次篩選,去
		configurations = filter(configurations, autoConfigurationMetadata);
		// 把AutoConfigurationImportEvent綁定在所有AutoConfigurationImportListener子類實例上
		fireAutoConfigurationImportEvents(configurations, exclusions);
		// 返回(configurations, exclusions)組
		return new AutoConfigurationEntry(configurations, exclusions);
	}
    // ......
}

 

可見selectImports()是AutoConfigurationImportSelector的核心函數,其核心功能就是獲取spring.factories中EnableAutoConfiguration所對應的Configuration類列表,由@EnableAutoConfiguration註解中的exclude/excludeName參數篩選一遍,再由AutoConfigurationImportFilter類所有實例篩選一遍,得到最終的用於Import的configuration和exclusion。 該函數是被誰調用的呢?在org.springframework.context.annotation.ConfigurationClassParser類中被processImports()調用,而processImports()函數被doProcessConfigurationClass()調用。下麵從doProcessConfigurationClass() 看起。    

(Spring) context.annotation.ConfigurationClassParser.doProcessConfigurationClass()方法

其中,configClass是一個ConfigurationClass實例,記錄了bean name(返回的bean名)和meta data(配置數據);sourceClass是簡單封裝後的有註解的類,主要方便對類的註解的使用,初始值是封裝過的configClass。

doProcessConfigurationClass() 對ConfigurationClass進行了各種配置,包括process @ComponentScan, process @Bean, process @Import等等。如果該SourceClass有父類,返回父類,否則返回null。

 

        @Nullable
	protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
			throws IOException {
		// ......
		// Process any @Import annotations
		processImports(configClass, sourceClass, getImports(sourceClass), true); 

		// ......
		// Process superclass, if any
		if (sourceClass.getMetadata().hasSuperClass()) {
			String superclass = sourceClass.getMetadata().getSuperClassName();
			if (superclass != null && !superclass.startsWith("java") &&
					!this.knownSuperclasses.containsKey(superclass)) {
				this.knownSuperclasses.put(superclass, configClass);
				// Superclass found, return its annotation metadata and recurse
				return sourceClass.getSuperClass();
			}
		}

		// No superclass -> processing is complete
		return null;
	}   

 

其中,processImports()方法的第三個參數中,getImports()方法嵌套的遍歷了sourceClass的註解,搜集所有@Import註解的值,即被Import的類名集合。

 

	private Set<SourceClass> getImports(SourceClass sourceClass) throws IOException {
		Set<SourceClass> imports = new LinkedHashSet<>();
		Set<SourceClass> visited = new LinkedHashSet<>();
		collectImports(sourceClass, imports, visited);
		return imports;
	}

	private void collectImports(SourceClass sourceClass, Set<SourceClass> imports, Set<SourceClass> visited)
			throws IOException {
        // 如果是頭一次添加
		if (visited.add(sourceClass)) {
			for (SourceClass annotation : sourceClass.getAnnotations()) {
				String annName = annotation.getMetadata().getClassName();
                // 當前註解的類名是否是Import
				if (!annName.equals(Import.class.getName())) { 
                    // 嵌套遍歷被Import的類
					collectImports(annotation, imports, visited);
				}
			}
            // 增加Import註解的值,即被Import的類名
			imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
		}
	}

 

(Spring) context.annotation.ConfigurationClassParser.processImports()方法

processImports() 的第一個參數configClass,是上層函數processConfigurationClass()的唯一參數,即被處理的Configuration類。第二個參數currentSourceClass是configClass的SourceClass類封裝。第三個參數是嵌套遍歷出的所有需要被Import的類。第四個參數指定是否檢查迴圈import。

	private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
			Collection<SourceClass> importCandidates, boolean checkForCircularImports) {

		// ......
				for (SourceClass candidate : importCandidates) {
                    // 如果candidate(即被@Import的類)是ImportSelector的子類
					if (candidate.isAssignable(ImportSelector.class)) {
						// Candidate class is an ImportSelector -> delegate to it to determine imports
						Class<?> candidateClass = candidate.loadClass();
                        // 生成candidate class(一個ImportSelector子類)的實例
						ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
                        // 將ImportSelector子類實例掛載為對應功能的Aware類(用於消息通知?)
						ParserStrategyUtils.invokeAwareMethods(
								selector, this.environment, this.resourceLoader, this.registry);
                        // DeferredImportSelector是一種特殊的ImportSelector,這裡單獨處理
						if (selector instanceof DeferredImportSelector) {
                            // 掛到deferredImportSelectors列表上
							this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
						}
						else {
                            // 篩選符合要求的@Import的名字
							String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
                            // 轉換成名字對應的類的集合
							Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
                            // 嵌套判斷被@Import的類是不是還有@Import註解
							processImports(configClass, currentSourceClass, importSourceClasses, false);
						}
					}
					// ......
				}
		// ......
	}

 

(Spring) context.annotation.ConfigurationClassParser.DefferedImportSelectorHandler私有類

deferredImportSelectors已被初始化為ArrayList<>(),因此全部走else分支。

private class DeferredImportSelectorHandler {
		@Nullable
		private List<DeferredImportSelectorHolder> deferredImportSelectors = new ArrayList<>();

		public void handle(ConfigurationClass configClass, DeferredImportSelector importSelector) {
			DeferredImportSelectorHolder holder = new DeferredImportSelectorHolder(
					configClass, importSelector); // 一個封裝後的pair
            // deferredImportSelectors被初始化為ArrayList<>(),所以if分支永遠不會執行到?
			if (this.deferredImportSelectors == null) { 
				DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
				handler.register(holder); // 註冊到DeferredImportSelectorGroupingHandler.configurationClasses中
				handler.processGroupImports(); // -> processImports()
			}
			else {
                // 所有的DeferredImportSelector類實例都掛到deferredImportSelectors列表上
				this.deferredImportSelectors.add(holder);
			}
		}
        // ......
}

  

那麼deferredImportSelectors上掛載的DefferedImportSelector類是何時處理的呢? doProcessConfigurationClass() 方法被processConfigurationClass() 調用,而processConfigurationClass() 被parse() 調用。可以看到在處理完所有的普通ImportSelector類後,即嵌套載入需要的被Import的類的實例之後,再統一處理DefferedImportSelector類。  

(Spring) context.annotation.ConfigurationClassParser.processConfigurationClass() 方法

    protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
        // ...

        // Recursively process the configuration class and its superclass hierarchy.
        SourceClass sourceClass = asSourceClass(configClass);
        do {
            // 如果sourceClass有父類會返回父類,否則返回null
            sourceClass = doProcessConfigurationClass(configClass, sourceClass);
        }
        while (sourceClass != null);

        this.configurationClasses.put(configClass, configClass);
    }

  

(Spring) context.annotation.ConfigurationClassParser.parse() 方法

        public void parse(Set<BeanDefinitionHolder> configCandidates) {
    	// 處理所有的ImportSelector類,其中DeferredImportSelector類只掛在deferredImportSelectorHandler列表上不處理,其他均處理,即嵌套遍歷被Import的類
		for (BeanDefinitionHolder holder : configCandidates) {
			BeanDefinition bd = holder.getBeanDefinition();
			try {
				if (bd instanceof AnnotatedBeanDefinition) {
					parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
				}
				else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
					parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
				}
				else {
					parse(bd.getBeanClassName(), holder.getBeanName());
				}
			}
			catch (BeanDefinitionStoreException ex) {
				throw ex;
			}
			catch (Throwable ex) {
				throw new BeanDefinitionStoreException(
						"Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
			}
		}
		
    	// 處理deferredImportSelectorHandler上掛著的DeferredImportSelector類
		this.deferredImportSelectorHandler.process();
	}

	protected final void parse(@Nullable String className, String beanName) throws IOException {
		Assert.notNull(className, "No bean class name for configuration class bean definition");
		MetadataReader reader = this.metadataReaderFactory.getMetadataReader(className);
		processConfigurationClass(new ConfigurationClass(reader, beanName));
	}

	protected final void parse(Class<?> clazz, String beanName) throws IOException {
		processConfigurationClass(new ConfigurationClass(clazz, beanName));
	}

	protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
		processConfigurationClass(new ConfigurationClass(metadata, beanName));
	}

  

再來看看DeferredImportSelector實例是如何被處理的。

 

(Spring) context.annotation.ConfigurationClassParser.DefferedImportSelectorHandler私有類

和普通ConfigurationClass一樣,DefferedImportSelector最後也是先註冊到列表中,再依次嵌套處理,只不過在import前多了一個根據order排序。

	// 對@Order(...)進行排序
	private static final Comparator<DeferredImportSelectorHolder> DEFERRED_IMPORT_COMPARATOR =
			(o1, o2) -> AnnotationAwareOrderComparator.INSTANCE.compare(o1.getImportSelector(), o2.getImportSelector());

	private class DeferredImportSelectorHandler {
    	// ...
    	public void process() {
			List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
			this.deferredImportSelectors = null;
			try {
				if (deferredImports != null) {
					DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
					deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);
                    // 添加到handler的configurationClasses列表中
					deferredImports.forEach(handler::register);
                    // 對handler中每個grouping的每個configClass,調用processImports()
					handler.processGroupImports();
				}
			}
			finally {
				this.deferredImportSelectors = new ArrayList<>();
			}
		}
	}

 

DONE.


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 在讀本篇文章之前如果你讀過這篇文章 "SpringBoot自動裝配原理解析" 應該會更加輕鬆 準備工作 我們知道SpringBoot的自動裝配的秘密在 包下的 文件中,而嵌入Tomcat的原理就在這個文件中載入的一個配置類: 首先看一下上方的幾個註解 1. 這個註解是決定配置類的載入順序的,當註解里 ...
  • 直接訪問redis的中國官網,在下載部分,可以看到安裝和使用的方式。wget http://download.redis.io/releases/redis-5.0.4.tar.gztar xzf redis-5.0.4.tar.gzcd redis-5.0.4make./src/redis-ser ...
  • xml即可擴展標記語言,它可以用來標記數據、定義數據類型,是一種允許用戶對自己的標記語言進行定義的源語言。 本文主要學習的ElementTree是python的XML處理模塊,它提供了一個輕量級的對象模型。在使用ElementTree模塊時,需要import xml.etree.ElementTre ...
  • 在學習Python的是後遇到點小問題,記錄下來以後忘了再來看看。 一. python2 和python3在繼承父類的時候是不同的。super() 是一個特殊的函數,幫助Python將父類和子類關聯起來。在Python3中,直接使用如下代碼: Python3 在Python2中有兩種方法: 第一種 第 ...
  • Python 入門 之 反射 通過字元串操作對象的屬性和方法 對象的角度使用反射 類的角度使用反射 當前模塊使用反射 其他模塊使用反射 反射的應用場景 ...
  • ``` include int main(void) { int arr[10]={5,4,7,9,2,3,1,6,10,8}; //定義一個位排序的數組 int i; //定義迴圈次數 int n = 0; //定義排序次數 int length=10; //定義數組長度 while(n arr[ ...
  • 一、random模塊 random模塊是用來生成隨機數的。 練習:生成隨機驗證碼 import random def v_code(): code = '' for i in range(5): num=random.randint(0,9) alf=chr(random.randint(65,90 ...
  • 介面表示一種能力,實現了一個介面,即擁有一種能力。 BeanDefinition與Bean的關係, 就好比類與對象的關係. 類在spring的數據結構就是BeanDefinition.根據BeanDefinition得到的對象就是我們需要的Bean. 我認為理解Bean與BeanDefinition ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...