從 java main 方法啟動
SpringApplication 構(gòu)造方法
@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 = deduceWebApplicationType(); // 推斷 web 應(yīng)用類型
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class)); // 獲取 spring 工廠實例
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
getSpringFactoriesInstances 源碼
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// Use names and ensure unique to protect against duplicates
Set<String> names = new LinkedHashSet<>(
SpringFactoriesLoader.loadFactoryNames(type, classLoader)); // 獲取指定類型的工廠名字
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, // 根據(jù)名字、類型創(chuàng)建工廠實例
classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
從源碼我們看出主要做了三件事:
- loadFactoryNames,加載指定類型的工廠名稱
loadSpringFactories:
a. 查找類路徑下全部的META-INF/spring.factories的URL
b. 根據(jù)url加載全部的spring.factories中的屬性
c. 將所有spring.factories中的值緩存到 SpringFactoriesLoader 的 cache 中,方便下次調(diào)用 - createSpringFactoriesInstances,創(chuàng)建指定類型的工廠實例,根據(jù)上面獲取的指定類型的工廠名稱列表來實例化工廠 bean,我們可以簡單的認(rèn)為通過反射來實例化
- 對工廠實例進(jìn)行排序,然后返回排序后的實例列表
構(gòu)造總結(jié):
- 構(gòu)造自身實例
- 推測 web 應(yīng)用類型,并賦值到屬性 webApplicationType
- 設(shè)置屬性 initializers 和 listeners 中途讀取了類路徑下所有 META-INF/spring.factories 的屬性,并緩存到了 SpringFactoriesLoader 的 cache 緩存中
- 推斷主類,并賦值到屬性 mainApplicationClass
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) {
// 秒表,用于記錄啟動時間;記錄每個任務(wù)的時間,最后會輸出每個任務(wù)的總費時
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// spring應(yīng)用上下文,也就是我們所說的spring根容器
ConfigurableApplicationContext context = null;
// 自定義SpringApplication啟動錯誤的回調(diào)接口
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
// 設(shè)置jdk系統(tǒng)屬性java.awt.headless,默認(rèn)情況為true即開啟
configureHeadlessProperty();
// KEY 1 - 獲取啟動時監(jiān)聽器
SpringApplicationRunListeners listeners = getRunListeners(args)
// 觸發(fā)啟動事件,啟動監(jiān)聽器會被調(diào)用,一共5個監(jiān)聽器被調(diào)用
listeners.starting();
try {
// 參數(shù)封裝,也就是在命令行下啟動應(yīng)用帶的參數(shù),如--server.port=9000
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
// KEY 2 - 準(zhǔn)備環(huán)境 1、加載外部化配置的資源到environment;2、觸發(fā)ApplicationEnvironmentPreparedEvent事件
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
// 配置spring.beaninfo.ignore,并添加到名叫systemProperties的PropertySource中;默認(rèn)為true即開啟
configureIgnoreBeanInfo(environment);
// 打印banner圖
Banner printedBanner = printBanner(environment);
// KEY 3 - 創(chuàng)建應(yīng)用上下文,并實例化了其三個屬性:reader、scanner和beanFactory
context = createApplicationContext();
// 獲取異常報道器,即加載spring.factories中的SpringBootExceptionReporter實現(xiàn)類
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// KEY 4 - 準(zhǔn)備上下文前置處理
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
// KEY 5 - Spring上下文刷新
refreshContext(context);
// KEY 6 - Spring上下文后置處理
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
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;
}
getRunListeners() 方法:返回一個新的SpringApplicationRunListeners實例對象
細(xì)看的話,這次是從SpringFactoriesLoader的cache中取SpringApplicationRunListener類型的類(全限定名),然后實例化后返回。說的簡單點,getRunListeners就是準(zhǔn)備好了運(yùn)行時監(jiān)聽器EventPublishingRunListener。
listeners.starting() 方法:構(gòu)建了一個ApplicationStartingEvent事件,并將其發(fā)布出去,對每個listener進(jìn)行invokeListener,調(diào)用過濾出的監(jiān)聽器
prepareEnvironment() 方法:
// 準(zhǔn)備環(huán)境
private ConfigurableEnvironment prepareEnvironment(
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// Create and configure the environment 創(chuàng)建和配置環(huán)境
// 獲取或創(chuàng)建環(huán)境
ConfigurableEnvironment environment = getOrCreateEnvironment();
// 配置環(huán)境:配置PropertySources和activeProfiles
configureEnvironment(environment, applicationArguments.getSourceArgs());
// listeners環(huán)境準(zhǔn)備(就是廣播ApplicationEnvironmentPreparedEvent事件)
listeners.environmentPrepared(environment);
// 將環(huán)境綁定到SpringApplication
bindToSpringApplication(environment);
// 如果是非web環(huán)境,將環(huán)境轉(zhuǎn)換成StandardEnvironment
if (this.webApplicationType == WebApplicationType.NONE) {
environment = new EnvironmentConverter(getClassLoader())
.convertToStandardEnvironmentIfNecessary(environment);
}
// 配置PropertySources對它自己的遞歸依賴
ConfigurationPropertySources.attach(environment);
return environment;
}
configureEnvironment() 方法:將配置任務(wù)按順序委托給 configurePropertySources 和 configureProfiles
configurePropertySources:注釋說明是增加、移除或者重排序應(yīng)用環(huán)境中的 PropertySource。就目前而言,如果有命令行參數(shù)則新增封裝命令行參數(shù)的PropertySource,并將它放到sources的第一位置。
configureProfiles:配置應(yīng)用環(huán)境中的哪些配置文件處于激活狀態(tài)(或默認(rèn)激活)??梢酝ㄟ^spring.profiles.active屬性在配置文件處理期間激活其他配置文件。說的簡單點就是設(shè)置哪些Profiles是激活的。
listeners.environmentPrepared(environment):這其中會初始化 PropertiesPropertySourceLoader 和 YamlPropertySourceLoader 這兩個加載器從 file:./config/,file:./,classpath:/config/,classpath:/ 路徑下加載配置文件,PropertiesPropertySourceLoader 加載配置文件 application.xml 和application.properties,YamlPropertySourceLoader 加載配置文件 application.yml 和application.yaml。目前我們之后 classpath:/ 路徑下有個 application.yml 配置文件,將其屬性配置封裝進(jìn)了一個名叫 applicationConfig:[classpath:/application.yml] 的 OriginTrackedMapPropertySource 中,并將此對象放到了 propertySourceList 的最后。
environmentPrepared 方法會觸發(fā)所有監(jiān)聽了 ApplicationEnvironmentPreparedEvent 事件的監(jiān)聽器,加載外部化配置資源到 environment,包括命令行參數(shù)、servletConfigInitParams、servletContextInitParams、systemProperties、sytemEnvironment、random、application.yml(.yaml/.xml/.properties) 等;
bindToSpringApplication(environment):就是將 environment 綁定到 SpringApplication
createApplicationContext:根據(jù)SpringApplication的webApplicationType來實例化對應(yīng)的上下文,對其部分屬性:reader、scanner、beanFactory進(jìn)行了實例化;reader中實例化了屬性conditionEvaluator;scanner中添加了兩個AnnotationTypeFilter:一個針對@Component,一個針對@ManagedBean;beanFactory中注冊了8個注解配置處理器。
prepareContext:
1、將 context 中的 environment 替換成 SpringApplication 中創(chuàng)建的 environment
2、將SpringApplication中的 initializers 應(yīng)用到 context 中
設(shè)置application id,并將application id封裝成ContextId對象,注冊到beanFactory中
向context的beanFactoryPostProcessors中注冊了一個ConfigurationWarningsPostProcessor實例
向context的applicationListeners中注冊了一個ServerPortInfoApplicationContextInitializer實例
向context的beanFactoryPostProcessors中注冊了一個CachingMetadataReaderFactoryPostProcessor 實例
向context的applicationListeners中注冊了一個ConditionEvaluationReportListener實例
3、加載兩個單例bean到beanFactory中
向beanFactory中注冊了一個名叫springApplicationArguments的單例bean,該bean封裝了我們的命令行參數(shù);
向beanFactory中注冊了一個名叫springBootBanner的單例bean。
4、加載bean定義資源
5、將 SpringApplication 中的 listeners 注冊到 context 中,并廣播 ApplicationPreparedEvent 事件
總共11個 ApplicationListener 注冊到了 context 的 applicationListeners 中;
ApplicationPreparedEvent 事件的監(jiān)聽器一共做了兩件事:
+ 向 context 的 beanFactoryPostProcessors 中注冊了一個 PropertySourceOrderingPostProcessor 實例
+ 向 beanFactory 中注冊了一個名叫 springBootLoggingSystem 的單例 bean,也就是我們的日志系統(tǒng) bean
context 中主要是三個屬性增加了內(nèi)容:beanFactory、beanFactoryPostProcessors 和 applicationListeners
load:就是加載 bean 定義資源,支持4種方式:Class、Resource、Package和CharSequence。
Class:注解形式的 Bean 定義;AnnotatedBeanDefinitionReader 負(fù)責(zé)處理。
Resource:一般而言指的是 xml bean 配置文件,也就是我們在 spring 中常用的 xml 配置。說的簡單點就是:將 xml 的 bean 定義封裝成 BeanDefinition 并注冊到 beanFactory 的 BeanDefinitionMap 中;XmlBeanDefinitionReader 負(fù)責(zé)處理。
Package:以掃包的方式掃描bean定義; ClassPathBeanDefinitionScanner 負(fù)責(zé)處理。
CharSequence:以先后順序進(jìn)行匹配 Class、Resource 或 Package 進(jìn)行加載,誰匹配上了就用誰的處理方式處理。