SpringBoot源碼閱讀(二)應用啓動過程跟蹤

項目搭建完成後我們就可以通過打斷點的方式追蹤到springboot啓動過程中被調用到的每一個方法。

1.創建SpringApplication實例

在run方法上打個斷點,啓動項目,開始根據項目的啓動

一直step into進去,發現在run方法裏面new了一個上下文對象SpringApplication。

SpringApplicaton是整個應用的管理中心,這裏創建了一個初始化了一些springboot基本的配置的對象

/**
 * Create a new {@link SpringApplication} instance. The application context will load
 * beans from the specified primary sources (see {@link SpringApplication class-level}
 * documentation for details. The instance can be customized before calling
 * {@link #run(String...)}.
 * @param resourceLoader the resource loader to use
 * @param primarySources the primary bean sources
 * @see #run(Class, String[])
 * @see #setSources(Set)
 */
@SuppressWarnings({ "unchecked", "rawtypes" })
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
	this.resourceLoader = resourceLoader;
	Assert.notNull(primarySources, "PrimarySources must not be null");
	this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
	this.webApplicationType = WebApplicationType.deduceFromClasspath();
	setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
	setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
	this.mainApplicationClass = deduceMainApplicationClass();
}

這裏要注意看構造方法,step into進去SpringApplication構造方法裏面可以看到構造方法做了一下基礎數據的設置:

  1. 設置資源加載器
  2. 設置主類
  3. 設置應用類型
  4. 設置初始化器
  5. 設置監聽器
  6. 設置啓動類(啓動方法mian所在類)

這裏我們主要關注設置初始化器和監聽器的過程。

這裏兩個過程調用的是同一個方法getSpringFactoriesInstances,兩次調用傳了不一樣的參數返回了兩個不一樣的工廠實例集合。

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
    ClassLoader classLoader = getClassLoader();
    // 這一步spring內置配置文件META-INF/spring.factories中讀取一個kv形式的集合
    // 其中key對應的就是調用getSpringFactoriesInstances方法時傳入的參數
    // Use names and ensure unique to protect against duplicates
    Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
}

這裏通過調用SpringFactoriesLoader.loadFactoryNames(type, classLoader)獲取到的names集合其實是從內置的資源文件中讀取出來的一個類名集合

* springboot內置文件 META-INF/spring.factories

springboot內置文件 META-INF/spring.factories

 

使用傳入參數搜索一下,原則上getSpringFactoriesInstances(ApplicationContextInitializer.class)應該返回了5個初始化器實例

但是要注意項目中可能多個包下都存在META-INF/spring.factories文件,所以這裏讀取到的是全部META-INF/spring.factories文件中org.springframework.context.ApplicationContextInitializer指向的值的並集,所以這裏getSpringFactoriesInstances有可能返回大於5個初始化器實例。

獲取到類名集合後通過構造方法實例化對象

List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);

監聽器設置過程原理類似

* 後面也會發現啓動過程中有多處都調用了getSpringFactoriesInstances方法獲取工廠實例

2.通過SpringApplication實例啓動應用

創建好SpringApplication實例後調用了該對象的run方法

/**
 * Run the Spring application, creating and refreshing a new
 * {@link ApplicationContext}.
 * @param args the application arguments (usually passed from a Java main method)
 * @return a running {@link ApplicationContext}
 */
public ConfigurableApplicationContext run(String... args) {
	StopWatch stopWatch = new StopWatch();
	stopWatch.start();
	ConfigurableApplicationContext context = null;
	Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
	configureHeadlessProperty();
	SpringApplicationRunListeners listeners = getRunListeners(args);
	listeners.starting();
	try {
		ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
		ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
		configureIgnoreBeanInfo(environment);
		Banner printedBanner = printBanner(environment);
		context = createApplicationContext();
		exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
				new Class[] { ConfigurableApplicationContext.class }, context);
		prepareContext(context, environment, listeners, applicationArguments, printedBanner);
		refreshContext(context);
		afterRefresh(context, applicationArguments);
		stopWatch.stop();
		if (this.logStartupInfo) {
			new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWa
		}
		listeners.started(context);
		callRunners(context, applicationArguments);
	}
	catch (Throwable ex) {
		handleRunFailure(context, ex, exceptionReporters, listeners);
		throw new IllegalStateException(ex);
	}
	try {
		listeners.running(context);
	}
	catch (Throwable ex) {
		handleRunFailure(context, ex, exceptionReporters, null);
		throw new IllegalStateException(ex);
	}
	return context;
}

方法註釋翻譯過來意思是“啓動Spring應用,創建一個ApplicationContext上下文對象並刷新上下文”

方法一開始就是創建一個StopWatch對象並啓動,StopWatch是一個計時器,用於記錄應用的啓動耗時。

StopWatch stopWatch = new StopWatch();
stopWatch.start();

所以我們可以把關注點放在計時器開始到結束這個過程。

創建SpringApplicationRunListeners對象

SpringApplicationRunListeners listeners = getRunListeners(args);

getRunListeners方法事實上也是調用了getSpringFactoriesInstances方法得到一個工廠實例集合,把這個集合封裝到一個SpringApplicationRunListeners對象裏面,SpringApplicationRunListeners對象用於監聽run方法的執行過程

private SpringApplicationRunListeners getRunListeners(String[] args) {
	Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
	return new SpringApplicationRunListeners(logger,
			getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
}

啓動run監聽器

listeners.starting();
DefaultApplicationArguments用於解釋啓動應用時輸入的命令行參數,例如使用一下命令啓動
$ java -jar --spring.config.name=demoApplication --logging.path=/dev/logs --logging.level.root=info testArg

DefaultApplicationArguments可以將--spring.config.name=demoApplication”分解爲kv集合和v集合兩種形式,即“spring.config.name”=“demoApplication”

ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

 可以看到分解結果有三對kv值和一個v值

 

準備運行環境

ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);

prepareEnvironment方法創建一個新的運行環境,並設置好環境屬性、將監聽器綁定到該運行環境上、綁定到當前運行的應用SpringApplication。

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
		ApplicationArguments applicationArguments) {
	// Create and configure the environment
	ConfigurableEnvironment environment = getOrCreateEnvironment();//獲取或創建一個運行環境
	configureEnvironment(environment, applicationArguments.getSourceArgs());//配置運行環境
	ConfigurationPropertySources.attach(environment);//綁定動態解釋器,跟蹤配置的改動
	listeners.environmentPrepared(environment);//將監聽器綁定到運行環境上
	bindToSpringApplication(environment);//將當前應用綁定到該運行環境上
	if (!this.isCustomEnvironment) {
		environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
				deduceEnvironmentClass());
	}
	ConfigurationPropertySources.attach(environment);
	return environment;
}

configureIgnoreBeanInfo配置是否忽略java.beans.BeanInfo類的掃描

private void configureIgnoreBeanInfo(ConfigurableEnvironment environment) {
	if (System.getProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME) == null) {
		Boolean ignore = environment.getProperty("spring.beaninfo.ignore", Boolean.class, Boolean.TRUE);
		System.setProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME, ignore.toString());
	}
}

方法使用System.setProperty方式將屬性設置到全局變量中

這一行代碼是打印banner圖

Banner printedBanner = printBanner(environment);

例如springboot默認的banner圖

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.3.1.RELEASE)

創建一個上下文實例

context = createApplicationContext();

創建異常報告器實例,用於收集處理啓動過程中的異常報告

exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class
,new Class[] { ConfigurableApplicationContext.class }, context);

接下來的兩個方法prepareContextrefreshContext是應用啓動的核心過程,裏面包括運行環境的準備、各種監聽器的設置、bean工廠創建、包掃描、bean初始化、bean強化、依賴註釋等等 。在《SpringBoot源碼閱讀(三)》中再詳細解讀。

prepareContext(context, environment, listeners, applicationArguments, printedBanner);
refreshContext(context);

緊接着的是afterRefresh方法,springboot沒有對該方法進行任何實現,僅用於用戶拓展。就是說我們可以通過實現這個方法達到在springboot內部驅動過程執行完後接着執行我們自定一個的業務邏輯的目的。

afterRefresh(context, applicationArguments);

接着就是停止計時器、打印啓動耗時等日誌,啓動ApplicationRunnerCommandLineRunner兩種類型的runner

最後啓動監聽器,應用啓動完成

listeners.running(context);

 

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