javascript
SpringBoot启动流程是怎样的
前言
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
@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的。
@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的類名@Overridepublic 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注解的作用其實就是開啟自動配置,自動配置主要則依靠這種加載方式來實現。
@SpringBootConfiguration
@SpringBootConfiguration繼承自@Configuration,二者功能也一致,標注當前類是配置類, 并會將當前類內聲明的一個或多個以@Bean注解標記的方法的實例納入到spring容器中,并且實例名就是方法名。
小結
我們在這里畫張圖把@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");//啟動類傳入的Classthis.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));//判斷當前項目類型,有三種:NONE、SERVLET、REACTIVEthis.webApplicationType = WebApplicationType.deduceFromClasspath();//設置ApplicationContextInitializersetInitializers((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實例后,接下來就調用實例方法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();//加載SpringBootExceptionReporterexceptionReporters = 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启动流程是怎样的的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: springmv的执行流程是什么
- 下一篇: 带你从源码了解SpringBoot启动流