文章目錄
一、前言
本文是筆者閱讀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
接口。
DeferredImportSelector
是 ImportSelector
接口的子接口。
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 的方法),如下:
- 我們可以看到 process 方法中調用了getAutoConfigurationEntry 方法,通過上面的解釋我們知道 getAutoConfigurationEntry 方法就是用來解析spring.factories 文件的。另外getAutoConfigurationEntry 方法的返回值保存在了 autoConfigurationEntries中。
- 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
。注意二者的區別, AutoConfigurationGroup
是 AutoConfigurationImportSelector
一個靜態內部類。
2. 總結
結合上述:
- 首先需要有 META-INF/spring.factories 文件,文件中的存儲是key-value形式
- @SpringBootApplication 註解 繼承了 @EnableAutoConfiguration 註解。而 @EnableAutoConfiguration 註解中引入了 AutoConfigurationImportSelector 類。
- AutoConfigurationImportSelector 類會根據 @EnableAutoConfiguration 註解的全路徑類名作爲 key值加載 META-INF/spring.factories 文件 的value值,value值是預先配置好的,即當 使用 @EnableAutoConfiguration 註解開啓自動裝配功能時就會加載對應value 所指向的配置類。
- 將value值對應的配置類信息返回,並作爲 AutoConfigurationImportSelector # selectImports 方法的返回值返回。
- 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
如有侵擾,聯繫刪除。 內容僅用於自我記錄學習使用。如有錯誤,歡迎指正