在上一章我們分析了SpingBoot啟動流程中實例化SpingApplication的過程。
return new SpringApplication(primarySources).run(args);
這篇文章咱么說下run()
方法開始之后都做了那些事情。
繼續往下跟着源碼進入到run()
這個是比較核心的一個方法了
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
// 計時器開始
stopWatch.start();
// 創建啟動上下文對象
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
ConfigurableApplicationContext context = null;
// 配置Handless模式,是在缺少顯示屏、鍵盤或鼠標時的系統配置
// 默認為true
configureHeadlessProperty();
//獲取並啟動監聽器
SpringApplicationRunListeners listeners = getRunListeners(args);
// 啟動監聽器
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 准備環境
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
// 忽略配置的bean
configureIgnoreBeanInfo(environment);
// 打印banner,就是啟動的時候在控制台的spring圖案
Banner printedBanner = printBanner(environment);
// 創建容器
context = createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
// 准備應用上下文(spring容器前置處理)
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
// 刷新容器
refreshContext(context);
// 刷新容器后的擴展接口(spring容器后置處理)
afterRefresh(context, applicationArguments);
// 結束計時器並打印,這就是我們啟動后console的顯示的時間
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
// 發布監聽應用上下文啟動完成(發出啟動結束事件)
listeners.started(context);
// 執行runner
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
// 異常處理,如果run過程發生異常
handleRunFailure(context, ex, listeners);
throw new IllegalStateException(ex);
}
try {
// 監聽應用上下文運行中
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, null);
throw new IllegalStateException(ex);
}
// 返回最終構建的容器對象
return context;
}
接下來就對上面的關鍵步驟一一解釋
1. 獲取所有的監聽器
這段代碼我們比較熟悉了,上一篇咱么詳細介紹過,它的主要作用就是去META-INFO/spring.factories
中加載配置SpringApplicationRunListener的監聽器如下
顯然只有一個事件發布監聽器類,拿到了EventPublishingRunListener
啟動事件發布監聽器,下一步就是開始啟動了listeners.starting()
;我們往下跟源碼看
@Override
public void starting(ConfigurableBootstrapContext bootstrapContext) {
this.initialMulticaster
.multicastEvent(new ApplicationStartingEvent(bootstrapContext, this.application, this.args));
}
啟動的時候實際上是又創建了一個ApplicationStartingEvent
對象,其實就是監聽應用啟動事件。
其中 initialMulticaster
是一個SimpleApplicationEventMuticaster
public void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = eventType != null ? eventType : this.resolveDefaultEventType(event);
// 獲取線程池,為每個監聽事件創建一個線程
Executor executor = this.getTaskExecutor();
// 根據ApplicationStartingEvent事件類型找到對應的監聽器,並迭代
Iterator var5 = this.getApplicationListeners(event, type).iterator();
while(var5.hasNext()) {
ApplicationListener<?> listener = (ApplicationListener)var5.next();
if (executor != null) {
//
executor.execute(() -> {
this.invokeListener(listener, event);
});
} else {
this.invokeListener(listener, event);
}
}
}
2.准備環境
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
// Create and configure the environment
// 這里我們加入了web依賴所以是一個servlet容器
ConfigurableEnvironment environment = getOrCreateEnvironment();
// 配置環境
configureEnvironment(environment, applicationArguments.getSourceArgs());
// 環境准備完成
ConfigurationPropertySources.attach(environment);
listeners.environmentPrepared(bootstrapContext, environment);
DefaultPropertiesPropertySource.moveToEnd(environment);
configureAdditionalProfiles(environment);
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
deduceEnvironmentClass());
}
ConfigurationPropertySources.attach(environment);
return environment;
}
由於我們是添加了web的依賴 getOrCreateEnvironment()
返回的是一個standardservletEnviroment
標准的servlet環境
2.1 配置環境
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
if (this.addConversionService) {
// 嵌入式的轉換器
ConversionService conversionService = ApplicationConversionService.getSharedInstance();
environment.setConversionService((ConfigurableConversionService) conversionService);
}
// 配置屬性資源文件
configurePropertySources(environment, args);
// 配置文件
configureProfiles(environment, args);
}
應用嵌入的轉換器ApplicationConversionService
public static void configure(FormatterRegistry registry) {
DefaultConversionService.addDefaultConverters(registry);
DefaultFormattingConversionService.addDefaultFormatters(registry);
// 格式轉換
addApplicationFormatters(registry);
// 類型轉換
addApplicationConverters(registry);
}
===============格式轉換=================
public static void addApplicationFormatters(FormatterRegistry registry) {
registry.addFormatter(new CharArrayFormatter());
registry.addFormatter(new InetAddressFormatter());
registry.addFormatter(new IsoOffsetFormatter());
}
========================類型轉換===================
public static void addApplicationConverters(ConverterRegistry registry) {
addDelimitedStringConverters(registry);
registry.addConverter(new StringToDurationConverter());
registry.addConverter(new DurationToStringConverter());
registry.addConverter(new NumberToDurationConverter());
registry.addConverter(new DurationToNumberConverter());
registry.addConverter(new StringToPeriodConverter());
registry.addConverter(new PeriodToStringConverter());
registry.addConverter(new NumberToPeriodConverter());
registry.addConverter(new StringToDataSizeConverter());
registry.addConverter(new NumberToDataSizeConverter());
registry.addConverter(new StringToFileConverter());
registry.addConverter(new InputStreamSourceToByteArrayConverter());
registry.addConverterFactory(new LenientStringToEnumConverterFactory());
registry.addConverterFactory(new LenientBooleanToEnumConverterFactory());
if (registry instanceof ConversionService) {
addApplicationConverters(registry, (ConversionService) registry);
}
}
2.2 環境准備完成
同上面啟動監聽事件,這次的環境准備也是同樣的代碼
@Override
public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext,
ConfigurableEnvironment environment) {
this.initialMulticaster.multicastEvent(
// 創建一個應用環境准備事件對象
new ApplicationEnvironmentPreparedEvent(bootstrapContext, this.application, this.args, environment));
}
debug進去之后代碼跟AppLicationstrigevent 事件對象是一樣的。不再贅述。
不過這里是7個監聽器對象
3.配置忽略的bean
configureIgnoreBeanInfo(environment);
4.打印banner
這是SpringBoot默認的啟動時的圖標
Banner printedBanner = printBanner(environment);
這個是可以自定義的,也可以是圖篇或是文本文件中的圖形
5.創建容器
緊接着上一篇,接下來就是創建容器
protected ConfigurableApplicationContext createApplicationContext() {
return this.applicationContextFactory.create(this.webApplicationType);
}
6.准備應用上下文
private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
// 設置環境參數
context.setEnvironment(environment);
// 設置后處理應用上下文
postProcessApplicationContext(context);
//把從spring.factories中加載的org.springframework.bt.context.ConfigurationwarningsApplicationContextIitiaLizer,進行初始化操作
applyInitializers(context);
//EventPubLishingRunListener發布應用上下文事件
listeners.contextPrepared(context);
// 打印啟動日志
bootstrapContext.close(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
//注冊一個字是springAppLicationArguments單例的bean
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof DefaultListableBeanFactory) {
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
if (this.lazyInitialization) {
context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}
// Load the sources 獲取所有資源
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
// 創建BeanDefinitionLoader加載器加載注冊所有的資源
load(context, sources.toArray(new Object[0]));
// 同之前,發布應用上下文 加載事件
listeners.contextLoaded(context);
}
7.刷新應用上下文
刷新應用上下文就進入了spring的源碼了
public void refresh() throws BeansException, IllegalStateException {
synchronized(this.startupShutdownMonitor) {
StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
// Prepare this context for refreshing.
//准備刷新上下文
this.prepareRefresh();
// Tetl the subclass to refresh the internal bean facto
// 通知子類刷新內部工廠
ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
// 准備Bean工廠
this.prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in contex t subc lasses.
// 允許在上下文子類中對bean工廠進行后處理。
// Invoke factory processors registered as beans in the context,
this.postProcessBeanFactory(beanFactory);
StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
this.invokeBeanFactoryPostProcessors(beanFactory);
// 注冊后置處理器。
this.registerBeanPostProcessors(beanFactory);
beanPostProcess.end();
// 初始化信息源
this.initMessageSource();
// 初始化上下文事件發布器
this.initApplicationEventMulticaster();
// 初始化其他自定義bean
this.onRefresh();
// 注冊監聽器
this.registerListeners();
this.finishBeanFactoryInitialization(beanFactory);
//完成刷新,清緩存,初始化生命周期,事件發布等
this.finishRefresh();
} catch (BeansException var10) {
if (this.logger.isWarnEnabled()) {
this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var10);
}
// 銷毀bean
this.destroyBeans();
// Reset 'active'flag.
this.cancelRefresh(var10);
throw var10;
} finally {
this.resetCommonCaches();
contextRefresh.end();
}
}
}
刷新的代碼有點深,也是在這時創建了Tomcat對象,這也是SpringBoot
** 一鍵啟動**web工程的關鍵
創建了Tomcat對象,並設置參數
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
if (this.disableMBeanRegistry) {
Registry.disableRegistry();
}
Tomcat tomcat = new Tomcat();
File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
Connector connector = new Connector(this.protocol);
connector.setThrowOnFailure(true);
tomcat.getService().addConnector(connector);
customizeConnector(connector);
tomcat.setConnector(connector);
tomcat.getHost().setAutoDeploy(false);
configureEngine(tomcat.getEngine());
for (Connector additionalConnector : this.additionalTomcatConnectors) {
tomcat.getService().addConnector(additionalConnector);
}
prepareContext(tomcat.getHost(), initializers);
// 返回TomcatWebServer服務
return getTomcatWebServer(tomcat);
}
8.刷新后處理
afterReftesh();
//是個一空實現,留着后期擴展
/**
* Called after the context has been refreshed.
* @param context the application context
* @param args the application arguments
*/
protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) {
}
9.發布監聽應用啟動事件
@Override
public void started(ConfigurableApplicationContext context) {
context.publishEvent(new ApplicationStartedEvent(this.application, this.args, context));
AvailabilityChangeEvent.publish(context, LivenessState.CORRECT);
}
這里是調用context.publishEvent()方法,發布應用啟動事件ApplicationStartedEvent.
10.執行Runner
獲取所有的ApplicationRuner和CommandLineRunner來初始化一些參數,callRuner(是一個回調函數)
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List<Object> runners = new ArrayList<>();
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
AnnotationAwareOrderComparator.sort(runners);
for (Object runner : new LinkedHashSet<>(runners)) {
if (runner instanceof ApplicationRunner) {
callRunner((ApplicationRunner) runner, args);
}
if (runner instanceof CommandLineRunner) {
callRunner((CommandLineRunner) runner, args);
}
}
}
11.發布上下文准備完成的事件
listeners.running(context);
@Override
public void running(ConfigurableApplicationContext context) {
context.publishEvent(new ApplicationReadyEvent(this.application, this.args, context));
AvailabilityChangeEvent.publish(context, ReadinessState.ACCEPTING_TRAFFIC);
}
這段代碼看上去似成相識,前面有很多類似的代碼,不同的是這里上下文准備完成之后發布了一個ApplicationReadyEvent事件,聲明一下應用上下文准備完成。
小結
這篇主要是介紹了SpringBoot啟動過程中run()
的這個過程。從中我們也可以發現一些非常好的編碼習慣,大家可以在日常的工作中從模仿到內化,慢慢變成自己的東西。