Spring源碼分析十一:Springboot 自動化配置原理

一、前言

本文是筆者閱讀Spring源碼的記錄文章,由於本人技術水平有限,在文章中難免出現錯誤,如有發現,感謝各位指正。在閱讀過程中也創建了一些衍生文章,衍生文章的意義是因爲自己在看源碼的過程中,部分知識點並不瞭解或者對某些知識點產生了興趣,所以爲了更好的閱讀源碼,所以開設了衍生篇的文章來更好的對這些知識點進行進一步的學習。


在使用SpringBoot的時候,我們引入其他功能的時候通過一個簡單的@EnableXXX 註解就可實現比如

  • @EnableAspectJAutoProxy(proxyTargetClass = true) : 啓用 AOP
  • @EnableTransactionManagement : 啓用事務

本文就是來分析Springboot自動裝配的源碼實現。


1. ImportSelector

之所以需要了解這個接口是因爲下面的講解離不開這個接口。
ImportSelector 見名知意,是一種引入選擇器。其中selectImports 方法返回的String[] 數組的元素是類的全路徑名,Spring 會調用 selectImports, 並按照其返回的結果數組的元素指向的類加載到Spring容器中。

public interface ImportSelector {

	/**
	 * Select and return the names of which class(es) should be imported based on
	 * the {@link AnnotationMetadata} of the importing @{@link Configuration} class.
	 * @return the class names, or an empty array if none
	 */
	String[] selectImports(AnnotationMetadata importingClassMetadata);

	/**
	 * Return a predicate for excluding classes from the import candidates, to be
	 * transitively applied to all classes found through this selector's imports.
	 * <p>If this predicate returns {@code true} for a given fully-qualified
	 * class name, said class will not be considered as an imported configuration
	 * class, bypassing class file loading as well as metadata introspection.
	 * @return the filter predicate for fully-qualified candidate class names
	 * of transitively imported configuration classes, or {@code null} if none
	 * @since 5.2.4
	 */
	@Nullable
	default Predicate<String> getExclusionFilter() {
		return null;
	}

}

具體的調用鏈路比較多,這裏就不具體分析,調用邏輯在 ConfigurationClassParser#processImports 中,見下圖。我們只需要知道Springboot 在啓動時候會加載引入(使用 @Import 註解引入)的 ImportSelector 實現類。但是需要注意的是,Spring自動裝配的類AutoConfigurationImportSelector 實現的接口並不是ImportSelector 接口,而是DeferredImportSelector接口。
在這裏插入圖片描述

2. DeferredImportSelector

AutoConfigurationImportSelector 實現的是 DeferredImportSelector 接口。
DeferredImportSelectorImportSelector 接口的子接口。

DeferredImportSelector 有兩個特點:

  • 繼承該接口的ImportSelector會在最後執行
  • 如果定義了1一個以上的DeferredImportSelector則使用Order接口來進行排序

3. spring.factories

這個僅需要知道,Springboot 強調約定大於配置,其中有一個約定就是 springboot啓動的時候會加載 META-INF/spring.factories 文件,至於有什麼用,後面會講到。

三、源碼解析

1. 原理概述

要分析Springboot自動化配置的原理,首先我們需要看他的啓動方式如下。下面的啓動方式基本就是Springboot的固定啓動方式。

@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

1.1 @EnableAutoConfiguration

這時候我們進入 @SpringBootApplication 註解,發現 @SpringBootApplication 註解上赫然寫着 @EnableAutoConfiguration 註解,我們再進去 @EnableAutoConfiguration 查看。這裏我們發現 @EnableAutoConfiguration 註解通過 @Import(AutoConfigurationImportSelector.class) 引入了一個類AutoConfigurationImportSelector
在這裏插入圖片描述

1.2 AutoConfigurationImportSelector

AutoConfigurationImportSelector 實現了 DeferredImportSelector 接口,DeferredImportSelector又是ImportSelector的子接口,這裏需要注意的是 AutoConfigurationImportSelector 由於實現的是 DeferredImportSelector 接口,所以在調用邏輯上並不會調用 AutoConfigurationImportSelector.selectImports 方法(後面會具體講解)。下面我們先來詳細分析一下 getAutoConfigurationEntry 方法。

我們來看 AutoConfigurationImportSelector 的部分代碼如下:

	@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
		AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
				.loadMetadata(this.beanClassLoader);
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
				annotationMetadata);
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}

	/**
	 * Return the {@link AutoConfigurationEntry} based on the {@link AnnotationMetadata}
	 * of the importing {@link Configuration @Configuration} class.
	 * @param autoConfigurationMetadata the auto-configuration metadata
	 * @param annotationMetadata the annotation metadata of the configuration class
	 * @return the auto-configurations that should be imported
	 */
	 
	 /**
	 * 我們主要看這個方法
	 */
	protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
			AnnotationMetadata annotationMetadata) {
		// 判斷是否啓動自動裝配
		if (!isEnabled(annotationMetadata)) {
			return EMPTY_ENTRY;
		}
		// 獲取 @SpringBootApplication 的註解屬性exclude、excludeName。
		AnnotationAttributes attributes = getAttributes(annotationMetadata);
		// 獲取未篩選前的配置類
		List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
		// 去除重複的配置類
		configurations = removeDuplicates(configurations);
		// 獲取 需要排除的 配置類
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
		// 檢查需要排除的配置類
		checkExcludedClasses(configurations, exclusions);
		// 移除需要排除的配置類
		configurations.removeAll(exclusions);
		configurations = filter(configurations, autoConfigurationMetadata);
		// 發佈一個事件
		fireAutoConfigurationImportEvents(configurations, exclusions);
		return new AutoConfigurationEntry(configurations, exclusions);
	}
	
	// 這裏我們看到可以通過設置 EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY 屬性來 控制是否開啓自動化配置
	protected boolean isEnabled(AnnotationMetadata metadata) {
		if (getClass() == AutoConfigurationImportSelector.class) {
			return getEnvironment().getProperty(EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class, true);
		}
		return true;
	}

上面的代碼中 在 getAutoConfigurationEntry 方法中 還有一個關鍵方法 getCandidateConfigurations。我們再來看一下這個方法。這個方法我們僅僅通過他的斷言我們就可以發現,這個方法是去加載 META-INF/spring.factories 文件的。另外我們又發現 getSpringFactoriesLoaderFactoryClass 返回的就是 EnableAutoConfiguration.class

	/**
	 * Return the auto-configuration class names that should be considered. By default
	 * this method will load candidates using {@link SpringFactoriesLoader} with
	 * {@link #getSpringFactoriesLoaderFactoryClass()}.
	 * @param metadata the source metadata
	 * @param attributes the {@link #getAttributes(AnnotationMetadata) annotation
	 * attributes}
	 * @return a list of candidate configurations
	 */
	protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
		List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
				getBeanClassLoader());
		Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
				+ "are using a custom packaging, make sure that file is correct.");
		return configurations;
	}

	/**
	 * Return the class used by {@link SpringFactoriesLoader} to load configuration
	 * candidates.
	 * @return the factory class
	 */
	protected Class<?> getSpringFactoriesLoaderFactoryClass() {
		return EnableAutoConfiguration.class;
	}

再看一下 SpringFactoriesLoader.loadFactoryNames 方法的實現。這裏可以看到loadFactoryNames 方法其實上是根據類的全路徑類名去 spring.factories 中獲取值。這裏更細緻的代碼就不具體分析了

	public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
		String factoryTypeName = factoryType.getName();
		return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
	}

在這裏插入圖片描述

1.3 META-INF/spring.factories

我們這裏找到 META-INF/spring.factories 看一下。可以看到都是 key-value形式,並且可以是一些註解的全路徑名,value是需要加載的配置類。
在這裏插入圖片描述

1.4 DeferredImportSelector 的問題

經過上面的分析,我們可以確定 getAutoConfigurationEntry 方法就是整個解析的核心。那麼上面爲什麼說並不會調用 AutoConfigurationImportSelector.selectImports方法呢 ?

這裏我們着重觀察一下 ConfigurationClassParser#processImports 方法,

調用鏈路如下圖
在這裏插入圖片描述
代碼分析如下(這裏代碼分析源自 https://www.jianshu.com/p/480ebb1ecc8b):

    private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
            Collection<SourceClass> importCandidates, boolean checkForCircularImports) {
        //檢查包含有Import註解的集合是不是空的,空的則表示沒有
        if (importCandidates.isEmpty()) {
            return;
        }
        //檢查是否存在循環引入,通過deque的方式來檢查
        if (checkForCircularImports && isChainedImportOnStack(configClass)) {
            this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
        }
        else {
            //將當前bean放到importStack中,用於檢查循環引入
            this.importStack.push(configClass);
            try {
                for (SourceClass candidate : importCandidates) {
                    //import指定的Bean是ImportSelector類型
                    if (candidate.isAssignable(ImportSelector.class)) {
                        //實例化指定的ImportSelector子類
                        Class<?> candidateClass = candidate.loadClass();
                        ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,
                                this.environment, this.resourceLoader, this.registry);
                        //如果是ImportSelector的子類DeferredImportSelector的子類則按照DeferredImportSelectorHandler邏輯進行處理
                        if (selector instanceof DeferredImportSelector) {
                            this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
                        }
                        else {//如果不是則獲取指定的需要引入的class的ClassNames
                            String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
                            //根據ClassNames獲取並封裝成一個SourceClass
                            Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
                            //繼續調用processImports處理
                            processImports(configClass, currentSourceClass, importSourceClasses, false);
                        }
                    }//如果是ImportBeanDefinitionRegistrar的子類
                    else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
                        // Candidate class is an ImportBeanDefinitionRegistrar -> delegate to it to register additional bean definitions
                        //如果對應的ImportBeanDefinitionRegistrar子類對象,並放到configClass,這個是用來註冊額外的bean的
                        Class<?> candidateClass = candidate.loadClass();
                        ImportBeanDefinitionRegistrar registrar =
                                ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
                                        this.environment, this.resourceLoader, this.registry);
                        configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
                    }
                    else {//不是上面任何類的子類就可以進行處理了,將指定的需要引入的bean轉化爲ConfigurationClass,然後到processConfigurationClass方法中處理
                        this.importStack.registerImport(
                                currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
                        processConfigurationClass(candidate.asConfigClass(configClass));
                    }
                }
            }
            catch (BeanDefinitionStoreException ex) {
                throw ex;
            }
            catch (Throwable ex) {
                throw new BeanDefinitionStoreException(
                        "Failed to process import candidates for configuration class [" +
                        configClass.getMetadata().getClassName() + "]", ex);
            }
            finally {
                this.importStack.pop();
            }
        }
    }

這裏我們發現一個問題
在這裏插入圖片描述
我們發現,上面在講ImportSelector 接口的時候,我們明明說他們只有調用了580行的 selector.selectImports(currentSourceClass.getMetadata()); 語句才能而調用 ImportSelector 接口的 selectImports 方法。
AutoConfigurationImportSelector 實現的接口是 DeferredImportSelector 接口,所以這裏他會執行if分支的內容,也就是 this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector); 語句。

this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) 裏面的代碼就不具體貼出來了,在handle方法中關鍵代碼是 handler.processGroupImports(); 再繼續追溯 發現 grouping.getImports() 。下面貼出 getImports() 代碼如下:

		public Iterable<Group.Entry> getImports() {
			for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
				this.group.process(deferredImport.getConfigurationClass().getMetadata(),
						deferredImport.getImportSelector());
			}
			return this.group.selectImports();
		}

這裏我們可以看到group調用了兩個方法 process和 selectImports(這兩個方法都是 AutoConfigurationImportSelector.AutoConfigurationGroup 的方法),如下:

  1. 我們可以看到 process 方法中調用了getAutoConfigurationEntry 方法,通過上面的解釋我們知道 getAutoConfigurationEntry 方法就是用來解析spring.factories 文件的。另外getAutoConfigurationEntry 方法的返回值保存在了 autoConfigurationEntries中。
  2. selectImports 方法則是對 autoConfigurationEntries 結果進行進一步篩選排序,最終返回。
		@Override
		public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
		// 省略斷言,在這裏我們可以看到他調用了 getAutoConfigurationEntry 方法。通過上面的解釋我們知道 getAutoConfigurationEntry 方法就是用來解析spring.factories 文件的。
						AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
					.getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);
			this.autoConfigurationEntries.add(autoConfigurationEntry);
			for (String importClassName : autoConfigurationEntry.getConfigurations()) {
				this.entries.putIfAbsent(importClassName, annotationMetadata);
			}
		}

		@Override
		public Iterable<Entry> selectImports() {
			if (this.autoConfigurationEntries.isEmpty()) {
				return Collections.emptyList();
			}
			Set<String> allExclusions = this.autoConfigurationEntries.stream()
					.map(AutoConfigurationEntry::getExclusions).flatMap(Collection::stream).collect(Collectors.toSet());
			Set<String> processedConfigurations = this.autoConfigurationEntries.stream()
					.map(AutoConfigurationEntry::getConfigurations).flatMap(Collection::stream)
					.collect(Collectors.toCollection(LinkedHashSet::new));
			processedConfigurations.removeAll(allExclusions);

			return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()).stream()
					.map((importClassName) -> new Entry(this.entries.get(importClassName), importClassName))
					.collect(Collectors.toList());
		}

所以AutoConfigurationImportSelector 並不會調用方法
AutoConfigurationImportSelector.#selectImports。而是會調用AutoConfigurationImportSelector.AutoConfigurationGroup#selectImports。注意二者的區別, AutoConfigurationGroupAutoConfigurationImportSelector 一個靜態內部類。

2. 總結

結合上述:

  1. 首先需要有 META-INF/spring.factories 文件,文件中的存儲是key-value形式
  2. @SpringBootApplication 註解 繼承了 @EnableAutoConfiguration 註解。而 @EnableAutoConfiguration 註解中引入了 AutoConfigurationImportSelector 類。
  3. AutoConfigurationImportSelector 類會根據 @EnableAutoConfiguration 註解的全路徑類名作爲 key值加載 META-INF/spring.factories 文件 的value值,value值是預先配置好的,即當 使用 @EnableAutoConfiguration 註解開啓自動裝配功能時就會加載對應value 所指向的配置類。
  4. 將value值對應的配置類信息返回,並作爲 AutoConfigurationImportSelector # selectImports 方法的返回值返回。
  5. Springboot會根據 selectImports 的返回值中的配置類全路徑名加載對應的配置類。這些配置類再完成相應的工作實現自動裝配功能。

四、關於ImportSelector的簡陋Demo

說他簡陋,他是真的簡陋,要問爲什麼這麼簡陋,因爲我着急喫飯,然鵝我還沒做。。。。

1. EnableDemo

自定義註解 @EnableDemo,作爲啓用註解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(MyImportSelector.class)
public @interface EnableDemo {
}

2. DemoImportSelector

看註釋看註釋。。。

public class DemoImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        // 本來應該在 META-INF/spring.factories 配置加載類並獲取返回,這裏偷個懶
        // 返回類的全路徑地址,會被Spring按照路徑加載到容器中
        return new String[]{"com.kingfish.auto.Demo"};
    }
}

3. Demo

public class Demo {
    public void getmsg(){
        System.out.println("Demo.getmsg");
    }
}

4. DemoSpringRunner

@Component
public class DemoSpringRunner  implements ApplicationRunner {
    @Autowired
    private Demo demo;
    @Override
    public void run(ApplicationArguments args) throws Exception {
        demo.getmsg();
    }
}

4. 測試

上面可以看到,我們並沒有使用常規的注入方式將 Demo類注入到容器中。我們啓動測試。可以看到Demo正常加載並輸出。大功告成!
在這裏插入圖片描述


以上:內容部分參考
《Spring源碼深度解析》、
https://blog.csdn.net/boling_cavalry/article/details/82555352
https://www.jianshu.com/p/480ebb1ecc8b
如有侵擾,聯繫刪除。 內容僅用於自我記錄學習使用。如有錯誤,歡迎指正

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