前幾天,面試的時候被問到了SpringBoot的自動裝配的原理,趁著五一的假期,就來整理一下這個流程,
我這里使用的是idea創建的最簡單的SpringBoot專案,
我們都知道,main方法是java的啟動入口,我們在開發SpringBoot專案的時候,他的啟動類如下所示:
/**
* @SpringBootApplication用來標注一個主程式類,說明這是一個springboot應用
*/
@SpringBootApplication
public class SpringbootdemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootdemoApplication.class, args);
}
}
從上面代碼可以看出,SpringBoot的啟動類中最主要的就是:@SpringBootApplication和SpringApplication.run()方法,但是SpringBoot是如何找到當前程式的主類,并且獲取類上的注解的呢?
1.加載main主類
private Class<?> deduceMainApplicationClass() {
try {
StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
for (StackTraceElement stackTraceElement : stackTrace) {
if ("main".equals(stackTraceElement.getMethodName())) {
return Class.forName(stackTraceElement.getClassName());
}
}
}
catch (ClassNotFoundException ex) {
// Swallow and continue
}
return null;
}
從上面的代碼可以看出,推斷出主應用程式的類,通過跟蹤執行程序中的堆疊資訊,找到main函式之后,通過Class.forName的方式反射生成對應的物件(SpringbootdemoApplication),
2.執行run方法
從run方法開始,一直到prepareContext方法之前,都是在做準備作業
public ConfigurableApplicationContext run(String... args) {
// 創建StopWatch實體,用來記錄SpringBoot的啟動時間
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// 創建為null的背景關系物件
ConfigurableApplicationContext context = null;
// 創建例外報告器
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
// 獲取監聽器,讀取META-INF/spring.factories檔案中SpringApplicationRunListeners型別存入到集合中
SpringApplicationRunListeners listeners = getRunListeners(args);
// 回圈呼叫監聽starting的方法
listeners.starting();
try {
// 獲取啟動時傳入的引數
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 構建當前環境
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
// 根據環境資訊配置要忽略的bean
configureIgnoreBeanInfo(environment);
// 列印SpringBoot的版本和banner,如果需要修改banner,可以在resources下面新建一個banner.txt檔案,將內容拷貝進去
Banner printedBanner = printBanner(environment);
// 使用策略方法創建背景關系物件,分為以下三種型別的:
// 1.SERVLET:基于servlet的web應用程式運行,并啟動嵌入式的servlet web服務器
// 2.REACTIVE:基于反應式web應用程式運行,并啟動嵌入式的反應式web服務器
// 3.NONE:不以web應用程式運行,也不以嵌入式的web應用程式運行
// 這一步會創建5個bean實體,放在下圖中的beanDefinitionMap中
context = createApplicationContext();
// 獲取例外報告器
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// 完成整個容器的創建和啟動以及bean的注入功能,
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// 最侄訓呼叫AbstractApplicationContext#refresh的方法,實際上就是SpringIOC容器的創建程序,并且會進行自動裝配,在沒有使用外部Tomcat專案中,還會在這里創建內置Tomcat WebServer 并啟動
refreshContext(context);
// 在背景關系重繪后呼叫,定義一個空的模板方法,給其他子類實作重寫
afterRefresh(context, applicationArguments);
// StopWatch停止計時,列印springboot啟動花費的時間
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
// 發布應用背景關系啟動完成時間,觸發所有Listener監聽器的start方法
listeners.started(context);
// 執行所有的Runner運行器
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
// 發布應用背景關系就緒事件,觸發監聽器的running方法
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
prepareContext這個方法前面也是在給應用程式的創建做準備作業(環境變數,初始化引數,發布監聽事件,創建bean工廠);
// Load the sources;sources可以是xml檔案,也可以是配置類,在這里是使用的配置類(SpringbootDemoApplication)
Set<Object> sources = getAllSources();
// 加載各種bean物件到當前應用程式背景關系,即 將SpringbootdemoApplication加載到ApplicationContext中
load(context, sources.toArray(new Object[0]));
listeners.contextLoaded(context);
如圖所示,在load方法執行前后的beanDefinitionMap的對比,load方法執行后,將SpringbootdemoApplication加載到了bean工廠中了,

3.自動裝配
到這里,SpringBoot已經將SpringbootdemoApplication這個啟動類加載進bean容器中了,但是SpringBoot是如何引入專案需要的starter呢?
接著前面run方法中的refreshContext(context);這個方法,一路往下找,會發現呼叫了ConfigurableApplicationContext.refresh()方法,而這個方法是一個模板方法(如下圖),因為我們這里并不是使用的SERVLET和嵌入式的方式運行程式,所以呼叫的是第一個類中的方法,即 AbstractApplicationContext.refresh();在這個方法中,invokeBeanFactoryPostProcessors(beanFactory);這個方法就會執行自動裝配的邏輯,

順著下面的程序:
1.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors()); AbstractApplicationContext#746行
2.invokeBeanDefinitionRegistryPostProcessors(); PostProcessorRegistrationDelegate#112行
3.parser.parse(candidates); ConfigurationClassPostProcessor#331行
4.因為主類是使用注解的方式注冊的bean,所有會走ConfigurationClassParser的第175行的parse方法,
5.processImports(configClass, sourceClass, getImports(sourceClass), filter, true); ConfigurationClassParser#311
? 在這個方法中,通過getImports方法,其內部的 collectImports(sourceClass, imports, visited) 通過遞回的方式收集所有宣告的@Import匯入的類,在這里有一下兩個結果值:
? 1. AutoConfigurationPackage注解匯入的AutoConfigurationPackages.Registrar.class
? 2. EnableAutoConfiguration注解匯入的AutoConfigurationImportSelector.class

? 這樣就找到了我們常說的AutoConfigurationImportSelector這個類了,
6.第5步決議configration完之后,回到ConfigurationClassParser#193行
? this.deferredImportSelectorHandler.process();在這個方法里面實作自動匯入的邏輯,點擊往下,會找到下面的這一行代碼,ConfigurationClassParser#809行,通過getImports()獲取需要自動裝配的類,
7.在第6步的getImports()方法中,點擊往下,會找到AutoConfigurationImportSelector#433行,
AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector).getAutoConfigurationEntry(annotationMetadata);
進入到getAutoConfigurationEntry()方法中找到123行的 getCandidateConfigurations():

這個方法里面的 loadFactoryNames()方法,能發現它讀取META-INF/spring.factories下的@EnableAutoConfiguration的配置類,這個方法在run方法之中多次呼叫,根據傳參不一樣,從META-INF/spring.factories中獲取的內容也不一樣,

從檔案中得到了133個配置類,緊接著在進行去重,排除與過濾(并不是所有的都需要)之后,就得到了需要裝配的類,我這里最后還剩26個,
到這里就將需要裝配的類都已經識別到了,
參考文章:
https://blog.csdn.net/j080624/article/details/80764031
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/282815.html
標籤:其他
上一篇:Django中自定義過濾器步驟
下一篇:自定義mybatis持久層框架
