項目搭建完成後我們就可以通過打斷點的方式追蹤到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構造方法裏面可以看到構造方法做了一下基礎數據的設置:
- 設置資源加載器
- 設置主類
- 設置應用類型
- 設置初始化器
- 設置監聽器
- 設置啓動類(啓動方法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
使用傳入參數搜索一下,原則上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);
接下來的兩個方法prepareContext和refreshContext是應用啓動的核心過程,裏面包括運行環境的準備、各種監聽器的設置、bean工廠創建、包掃描、bean初始化、bean強化、依賴註釋等等 。在《SpringBoot源碼閱讀(三)》中再詳細解讀。
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
refreshContext(context);
緊接着的是afterRefresh方法,springboot沒有對該方法進行任何實現,僅用於用戶拓展。就是說我們可以通過實現這個方法達到在springboot內部驅動過程執行完後接着執行我們自定一個的業務邏輯的目的。
afterRefresh(context, applicationArguments);
接着就是停止計時器、打印啓動耗時等日誌,啓動ApplicationRunner和CommandLineRunner兩種類型的runner
最後啓動監聽器,應用啓動完成
listeners.running(context);