天天看點

源碼分析SpringBoot啟動流程思維導圖前言一、啟動類二、@SpringBootApplication三、SpringApplication類總結

思維導圖

源碼分析SpringBoot啟動流程思維導圖前言一、啟動類二、@SpringBootApplication三、SpringApplication類總結
文章已收錄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中的配置類。

源碼分析SpringBoot啟動流程思維導圖前言一、啟動類二、@SpringBootApplication三、SpringApplication類總結
源碼分析SpringBoot啟動流程思維導圖前言一、啟動類二、@SpringBootApplication三、SpringApplication類總結

這裡使用了spring.factories檔案的方式加載配置類,提供了很好的擴充性。

是以@EnableAutoConfiguration注解的作用其實就是開啟自動配置,自動配置主要則依靠這種加載方式來實作。

2.3 @SpringBootConfiguration

@SpringBootConfiguration繼承自@Configuration,二者功能也一緻,标注目前類是配置類,

并會将目前類内聲明的一個或多個以@Bean注解标記的方法的執行個體納入到spring容器中,并且執行個體名就是方法名。

2.4 小結

我們在這裡畫張圖把@SpringBootApplication注解包含的三個注解分别解釋一下。

源碼分析SpringBoot啟動流程思維導圖前言一、啟動類二、@SpringBootApplication三、SpringApplication類總結

三、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執行個體做的事情,下面用張圖來表示一下。

源碼分析SpringBoot啟動流程思維導圖前言一、啟動類二、@SpringBootApplication三、SpringApplication類總結

建立了SpringApplication執行個體之後,就完成了SpringApplication類的初始化工作,這個執行個體裡包括監聽器、初始化器,項目應用類型,啟動類集合,類加載器。如圖所示。

源碼分析SpringBoot啟動流程思維導圖前言一、啟動類二、@SpringBootApplication三、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;
}           

結合注釋和源碼,其實很清晰了,為了加深印象,畫張圖看一下整個流程。

源碼分析SpringBoot啟動流程思維導圖前言一、啟動類二、@SpringBootApplication三、SpringApplication類總結

總結

表面啟動類看起來就一個@SpringBootApplication注解,一個run()方法。其實是經過高度封裝後的結果。我們可以從這個分析中學到很多東西。比如使用了spring.factories檔案來完成自動配置,提高了擴充性。在啟動時使用觀察者模式,以事件釋出的形式通知,降低耦合,易于擴充等等。

那麼SpringBoot的啟動類分析就講到這裡了,感謝大家的閱讀。

覺得有用就點個贊吧,你的點贊是我創作的最大動力~

我是一個努力讓大家記住的程式員。我們下期再見!!!

源碼分析SpringBoot啟動流程思維導圖前言一、啟動類二、@SpringBootApplication三、SpringApplication類總結
能力有限,如果有什麼錯誤或者不當之處,請大家批評指正,一起學習交流!