思維導圖

文章已收錄Github精選,歡迎Star: https://github.com/yehongzhi/learningSummary
前言
SpringBoot一開始最讓我印象深刻的就是通過一個啟動類就能啟動應用。在SpringBoot以前,啟動應用雖然也不麻煩,但是還是有點繁瑣,要打包成war包,又要配置tomcat,tomcat又有一個server.xml檔案去配置。
然而SpringBoot則内置了tomcat,通過啟動類啟動,配置也集中在一個application.yml中,簡直不要太舒服。好奇心驅動,于是我很想搞清楚啟動類的啟動過程,那麼開始吧。
一、啟動類
首先我們看最常見的啟動類寫法。
@SpringBootApplication
public class SpringmvcApplication {
public static void main(String[] args) {
SpringApplication.run(SpringmvcApplication.class, args);
}
}
把啟動類分解一下,實際上就是兩部分:
- @SpringBootApplication注解
- 一個main()方法,裡面調用SpringApplication.run()方法。
二、@SpringBootApplication
首先看@SpringBootApplication注解的源碼。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
}
很明顯,@SpringBootApplication注解由三個注解組合而成,分别是:
- @ComponentScan
- @EnableAutoConfiguration
- @SpringBootConfiguration
2.1 @ComponentScan
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
}
這個注解的作用是告訴Spring掃描哪個包下面類,加載符合條件的元件(比如貼有@Component和@Repository等的類)或者bean的定義。
是以有一個basePackages的屬性,如果預設不寫,則從聲明@ComponentScan所在類的package進行掃描。
是以啟動類最好定義在Root package下,因為一般我們在使用@SpringBootApplication時,都不指定basePackages的。
2.2 @EnableAutoConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
}
這是一個複合注解,看起來很多注解,實際上關鍵在@Import注解,它會加載AutoConfigurationImportSelector類,然後就會觸發這個類的selectImports()方法。根據傳回的String數組(配置類的Class的名稱)加載配置類。
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
//傳回的String[]數組,是配置類Class的類名
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
//傳回配置類的類名
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
我們一直點下去,就可以找到最後的幕後英雄,就是SpringFactoriesLoader類,通過loadSpringFactories()方法加載META-INF/spring.factories中的配置類。
這裡使用了spring.factories檔案的方式加載配置類,提供了很好的擴充性。
是以@EnableAutoConfiguration注解的作用其實就是開啟自動配置,自動配置主要則依靠這種加載方式來實作。
2.3 @SpringBootConfiguration
@SpringBootConfiguration繼承自@Configuration,二者功能也一緻,标注目前類是配置類,
并會将目前類内聲明的一個或多個以@Bean注解标記的方法的執行個體納入到spring容器中,并且執行個體名就是方法名。
2.4 小結
我們在這裡畫張圖把@SpringBootApplication注解包含的三個注解分别解釋一下。
三、SpringApplication類
接下來講main方法裡執行的這句代碼,這是SpringApplication類的靜态方法run()。
//啟動類的main方法
public static void main(String[] args) {
SpringApplication.run(SpringmvcApplication.class, args);
}
//啟動類調的run方法
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
//調的是下面的,參數是數組的run方法
return run(new Class<?>[] { primarySource }, args);
}
//和上面的方法差別在于第一個參數是一個數組
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
//實際上new一個SpringApplication執行個體,調的是一個執行個體方法run()
return new SpringApplication(primarySources).run(args);
}
通過上面的源碼,發現實際上最後調的并不是靜态方法,而是執行個體方法,需要new一個SpringApplication執行個體,這個構造器還帶有一個primarySources的參數。是以我們直接定位到構造器。
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
//斷言primarySources不能為null,如果為null,抛出異常提示
Assert.notNull(primarySources, "PrimarySources must not be null");
//啟動類傳入的Class
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//判斷目前項目類型,有三種:NONE、SERVLET、REACTIVE
this.webApplicationType = WebApplicationType.deduceFromClasspath();
//設定ApplicationContextInitializer
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
//設定監聽器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
//判斷主類,初始化入口類
this.mainApplicationClass = deduceMainApplicationClass();
}
//判斷主類
private Class<?> deduceMainApplicationClass() {
try {
StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
for (StackTraceElement stackTraceElement : stackTrace) {
if ("main".equals(stackTraceElement.getMethodName())) {
return Class.forName(stackTraceElement.getClassName());
}
}
}
catch (ClassNotFoundException ex) {
// Swallow and continue
}
return null;
}
以上就是建立SpringApplication執行個體做的事情,下面用張圖來表示一下。
建立了SpringApplication執行個體之後,就完成了SpringApplication類的初始化工作,這個執行個體裡包括監聽器、初始化器,項目應用類型,啟動類集合,類加載器。如圖所示。
得到SpringApplication執行個體後,接下來就調用執行個體方法run()。繼續看。
public ConfigurableApplicationContext run(String... args) {
//建立計時器
StopWatch stopWatch = new StopWatch();
//開始計時
stopWatch.start();
//定義上下文對象
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
//Headless模式設定
configureHeadlessProperty();
//加載SpringApplicationRunListeners監聽器
SpringApplicationRunListeners listeners = getRunListeners(args);
//發送ApplicationStartingEvent事件
listeners.starting();
try {
//封裝ApplicationArguments對象
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//配置環境子產品
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
//根據環境資訊配置要忽略的bean資訊
configureIgnoreBeanInfo(environment);
//列印Banner标志
Banner printedBanner = printBanner(environment);
//建立ApplicationContext應用上下文
context = createApplicationContext();
//加載SpringBootExceptionReporter
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
//ApplicationContext基本屬性配置
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
//重新整理上下文
refreshContext(context);
//重新整理後的操作,由子類去擴充
afterRefresh(context, applicationArguments);
//計時結束
stopWatch.stop();
//列印日志
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
//發送ApplicationStartedEvent事件,标志spring容器已經重新整理,此時所有的bean執行個體都已經加載完畢
listeners.started(context);
//查找容器中注冊有CommandLineRunner或者ApplicationRunner的bean,周遊并執行run方法
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
//發送ApplicationFailedEvent事件,标志SpringBoot啟動失敗
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
//發送ApplicationReadyEvent事件,标志SpringApplication已經正在運作,即已經成功啟動,可以接收服務請求。
listeners.running(context);
}
catch (Throwable ex) {
//報告異常,但是不發送任何事件
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
結合注釋和源碼,其實很清晰了,為了加深印象,畫張圖看一下整個流程。
總結
表面啟動類看起來就一個@SpringBootApplication注解,一個run()方法。其實是經過高度封裝後的結果。我們可以從這個分析中學到很多東西。比如使用了spring.factories檔案來完成自動配置,提高了擴充性。在啟動時使用觀察者模式,以事件釋出的形式通知,降低耦合,易于擴充等等。
那麼SpringBoot的啟動類分析就講到這裡了,感謝大家的閱讀。
覺得有用就點個贊吧,你的點贊是我創作的最大動力~
我是一個努力讓大家記住的程式員。我們下期再見!!!
能力有限,如果有什麼錯誤或者不當之處,請大家批評指正,一起學習交流!