SpringBoot自動裝配源碼


前幾天,面試的時候被問到了SpringBoot的自動裝配的原理。趁着五一的假期,就來整理一下這個流程。

我這里使用的是idea創建的最簡單的SpringBoot項目。

我們都知道,main方法是java的啟動入口,我們在開發SpringBoot項目的時候,他的啟動類如下所示:

/**
 * @SpringBootApplication用來標注一個主程序類,說明這是一個springboot應用
 */
@SpringBootApplication
public class SpringbootdemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringbootdemoApplication.class, args);
    }
}

從上面代碼可以看出,SpringBoot的啟動類中最主要的就是:@SpringBootApplicationSpringApplication.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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM