??SpringApplication是SpringBoot提供的幫助應(yīng)用程序啟動的引導(dǎo)類,它負責(zé)
- 創(chuàng)建合適的
ApplicationContext - 將命令行參數(shù)融入
Environment抽象 - 刷新
ApplicationContext,初始化所有的singleton bean - 觸發(fā)所有的
CommandLineRunner回調(diào)
本文中的源碼分析基于spring-boot-2.1.5.RELEASE,各位看官還請注意下版本。
構(gòu)造函數(shù)
??大多數(shù)時候,我們使用SpringApplication#run(...)工廠方法來創(chuàng)建SpringApplication實例,其實本質(zhì)上都是對構(gòu)造函數(shù)的調(diào)用。
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
// 資源加載器,一般為空,因為 ApplicationContext 本身實現(xiàn)了 ResourceLoader 接口
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
// 主配置類,一般情況下是標注了 @SpringBootApplication 注解的類
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 根據(jù) jar 包的引入情況判定應(yīng)用類型
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 獲取 spring.factories 中配置的 ApplicationContextInitializer
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 獲取 spring.factories 中配置的 ApplicationListener
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 獲取 main 函數(shù)入口類
this.mainApplicationClass = deduceMainApplicationClass();
}
primarySources是創(chuàng)建ApplicationContext時傳遞給它的主配置類,一般情況下是標注了@SpringBootApplication注解的類;webApplicationType則根據(jù)類路徑下jar包的引入情況來進行推斷,比如引入了spring-boot-starter-web就會被推斷為WebApplicationType.SERVLET;#setInitializers(...)和#setListeners(...)則使用之前講解過的Spring SPI機制來獲取對應(yīng)的實現(xiàn);最后mainApplicationClass則對應(yīng)調(diào)用棧中聲明了main函數(shù)的類。
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
return getSpringFactoriesInstances(type, new Class<?>[] {});
}
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = getClassLoader();
// 使用 Set 防止重復(fù)
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
// 反射創(chuàng)建實例,其中 parameterTypes 是構(gòu)造函數(shù)形參類型,args 是實參
// 具體代碼基本上就是 clazz.getDeclaredConstructor(parameterTypes).newInstance(args)
// 節(jié)約篇幅,就不放出來了
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
// 基于 @Order/Ordered 排序
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
這里對Spring SPI機制的使用是非常直接的,同時也告訴了我們?nèi)绾巫宰远x的ApplicationContextInitializer和ApplicationListener,比如在自定義starter的META-INF/spring.factories中加入:
org.springframework.context.ApplicationListener=example.MyApplicationListener
就完成了對MyApplicationListener的注冊。
啟動過程
public ConfigurableApplicationContext run(String... args) {
// 用于記錄啟動時長
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
// 容納 SPI SpringBootExceptionReporter 的實現(xiàn)
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
// 服務(wù)端程序一般都是沒有UI的
configureHeadlessProperty();
// 獲取 SPI SpringApplicationRunListener 的實現(xiàn)
SpringApplicationRunListeners listeners = getRunListeners(args);
// 回調(diào) SpringApplicationRunListener#starting(...),此時 #run(...) 剛開始運行
listeners.starting();
try {
// 封裝命令行參數(shù)
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 1. 創(chuàng)建 Environment 并進行配置
// 2. 回調(diào) SpringApplicationRunListener#environmentPrepared(...),此時 Environment 已創(chuàng)建
// 3. 為 ConfigurationProperties binding 做準備
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
// 打印 banner
Banner printedBanner = printBanner(environment);
// 根據(jù) webApplicationType 來創(chuàng)建 ApplicationContext
context = createApplicationContext();
// 獲取 SPI SpringBootExceptionReporter 的實現(xiàn)
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// 1. 配置 ApplicationContext
// 2. 運行 ApplicationInitializer(s)
// 3. 回調(diào) SpringApplicationRunListener#contextPrepared(...),
// 此時 ApplicationContext 已經(jīng)創(chuàng)建,但 BeanDefinition 還未加載
// 4. 加載 Bean 定義信息,但不刷新容器,此時已經(jīng)有了創(chuàng)建 Bean 的藍圖 BeanDefinition
// 5. 回調(diào) SpringApplicationRunListener#contextLoaded(...),此時 BeanDefinition 已經(jīng)
// 悉數(shù)加載完畢,但容器未刷新,對應(yīng)的 bean 實例還未創(chuàng)建
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// 刷新容器,初始化所有 non-lazy-init 的 bean
refreshContext(context);
// 鉤子函數(shù)
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
// 回調(diào) SpringApplicationRunListener#started(...),此時容器已刷新,bean 也初始化完畢
listeners.started(context);
// 回調(diào)容器中所有的 ApplicationRunner 和 CommandLineRunner
callRunners(context, applicationArguments);
} catch (Throwable ex) {
// 1. 回調(diào) SpringApplicationRunListener#failed(...)
// 2. 使用 SpringBootExceptionReporter 來處理異常
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
// 回調(diào) SpringApplicationRunListener#running(...),此時不僅容器已刷新,各種 *Runner 也已回調(diào)
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
#run(...)方法雖然略長,邏輯卻是很清晰的:
- 創(chuàng)建
Environment并進行配置 - 創(chuàng)建
ApplicationContext并進行配置 - 加載配置類并解析出對應(yīng)的
BeanDefinition - 刷新容器,根據(jù)
BeanDefinition創(chuàng)建Bean - 回調(diào)
*Runner接口
SpringApplicationRunListener則提供了對#run(...)過程中關(guān)鍵節(jié)點的回調(diào),默認注冊有EventPublishingRunListener——它使用ApplicationEventMulticaster發(fā)出對應(yīng)的事件。其中比較重要的有ApplicationEnvironmentPreparedEvent和ApplicationReadyEvent,SpringCloud中的很多特性,比如Bootstrap Context的創(chuàng)建和Environment的動態(tài)刷新就是基于這些事件來觸發(fā)的。SpringBootExceptionReporter則提供了對#run(...)過程中異常的處理,默認注冊的FailureAnalyzers會打印異常信息并提供一個解決方案,類似下面的輸出相信大家都不會陌生(笑)。
***************************
APPLICATION FAILED TO START
***************************
Description:
Field restTemplate in example.TestController required a single bean, but 2 were found:
- restTemplate1: defined by method 'restTemplate1' in class path resource [example/Config.class]
- restTemplate2: defined by method 'restTemplate2' in class path resource [example/Config.class]
Action:
Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed
創(chuàng)建Environment并進行配置
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// 根據(jù) webApplicationType 來創(chuàng)建 Environment
ConfigurableEnvironment environment = getOrCreateEnvironment();
// 1. 添加 ConversionService 用于類型轉(zhuǎn)換
// 2. 添加 defaultProperties 和 commandLineArgs 兩個 PropertySource
// 3. 合并 additionalProfiles 到 activeProfiles
configureEnvironment(environment, applicationArguments.getSourceArgs());
// 回調(diào),發(fā)出 ApplicationEnvironmentPreparedEvent 事件
listeners.environmentPrepared(environment);
// 將 spring.main 前綴的配置項應(yīng)用到 SpringApplication,比如 allowBeanDefinitionOverriding
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
deduceEnvironmentClass());
}
// 將 PropertySource 轉(zhuǎn)換成 ConfigurationPropertySource
// 這一步是為了支持 ConfigurationProperties binding
ConfigurationPropertySources.attach(environment);
return environment;
}
創(chuàng)建ApplicationContext并進行配置
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
switch (this.webApplicationType) {
case SERVLET:
contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
break;
case REACTIVE:
contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
break;
default:
contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
}
} catch (ClassNotFoundException ex) {
throw new IllegalStateException(
"Unable create a default ApplicationContext, " + "please specify an ApplicationContextClass",
ex);
}
}
return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}
根據(jù)webApplicationType創(chuàng)建對應(yīng)的ApplicationContext,比如引入了spring-boot-starter-web就會創(chuàng)建AnnotationConfigServletWebServerApplicationContext,進而啟動內(nèi)嵌的Tomcat容器,這部分內(nèi)容之前已經(jīng)聊過了,大家可以翻翻看。
加載配置類并解析出對應(yīng)的BeanDefinition
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
// 設(shè)置使用的 Environment
context.setEnvironment(environment);
// 設(shè)置 ResourceLoader、ClassLoader和ConversionService
postProcessApplicationContext(context);
// 應(yīng)用上前面加載好的 ApplicationContextInitializer
applyInitializers(context);
// 回調(diào),發(fā)出 ApplicationPreparedEvent 事件
listeners.contextPrepared(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// 單獨注冊一下幾個比較特殊的 Bean
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
// 將 allowBeanDefinitionOverriding 配置項應(yīng)用到 ApplicationContext 底層的 BeanFactory
if (beanFactory instanceof DefaultListableBeanFactory) {
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
// primarySources 和以編程方式加入的其它配置源
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
// 從以上配置源中解析出 BeanDefinition
// 這部分屬于 spring-context 包的內(nèi)容了,無非是使用 AnnotatedBeanDefinitionReader
// ClassPathBeanDefinitionScanner 等組件來加載并解析
load(context, sources.toArray(new Object[0]));
// 回調(diào),發(fā)出 ApplicationPreparedEvent 事件
listeners.contextLoaded(context);
}
大家很熟悉的自動裝配就發(fā)生在這一階段,@EnableAutoConfiguration注解一般是標注在primarySource上的,看一下它的定義:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
/**
* 待排除的自動配置類
*/
Class<?>[] exclude() default {};
/**
* 待排除的自動配置類名
*/
String[] excludeName() default {};
}
它通過@Import注解向容器中導(dǎo)入了一個ImportSelector接口的實現(xiàn)——AutoConfigurationImportSelector,并在這里實現(xiàn)了自動裝配邏輯。鑒于這部分內(nèi)容比較多,咱們下次可以展開聊聊。
刷新容器,根據(jù)BeanDefinition創(chuàng)建Bean
protected void refresh(ApplicationContext applicationContext) {
Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
((AbstractApplicationContext) applicationContext).refresh();
}
刷新容器直接調(diào)用ApplicationContext#refresh()方法即可,這將導(dǎo)致所有non-lazy-init的singleton bean得到初始化。
回調(diào)*Runner接口
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List<Object> runners = new ArrayList<>();
// 獲取容器中所有類型為 ApplicationRunner 的 bean
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
// 獲取容器中所有類型為 CommandLineRunner 的 bean
runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
// 基于 @Order/Ordered 排序
AnnotationAwareOrderComparator.sort(runners);
// 接著就是逐個調(diào)用了
for (Object runner : new LinkedHashSet<>(runners)) {
if (runner instanceof ApplicationRunner) {
callRunner((ApplicationRunner) runner, args);
}
if (runner instanceof CommandLineRunner) {
callRunner((CommandLineRunner) runner, args);
}
}
}
這部分代碼是自解釋的,不多提了。
結(jié)語
??今天我們一起分析了SpringBoot應(yīng)用的啟動流程,也僅僅是主體的啟動流程。Spring本身是完全開放、極易擴展的,大家也看到了伴隨著應(yīng)用啟動的各種回調(diào),眾多的SpringBoot特性都是在這些擴展點中得到實現(xiàn)的,這也是接下來的文章要分析的內(nèi)容。