SpringBoot啟動流程總結

Yfeil發表於2024-05-31

1.導語

//以下是一個SpringBoot應用的標準入口:
public static void main(String[] args) {
    SpringApplication.run(TestApplication.class, args);
}

//SpringApplication.run 內部是這樣的:先建立一個 SpringApplication 物件,然後呼叫他的run方法。
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
    return new SpringApplication(primarySources).run(args);
}

所以想要了解清楚 SpringBoot 啟動流程,相當於瞭解 SpringApplication 的 構造方法 和 run方法。

2.構造方法詳解

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {

    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));

    // 判斷應用型別,會有三種情況:
    // NONE:非web應用
    // SERVLET:普通web應用
    // REACTIVE:響應式web應用
    this.webApplicationType = WebApplicationType.deduceFromClasspath();

    // 從 META-INF/spring.factories 檔案中獲取如下內容
    // 獲取 註冊初始化器:在 run 方法中的 createBootstrapContext(); 裡會被使用
    this.bootstrapRegistryInitializers = new ArrayList<>(getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
    // 獲取 容器初始化器:在 run 方法中的 prepareContext( ... ); 裡會被使用
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    // 獲取 監聽器:在 run 方法中 listeners.xxx 時被使用
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

    this.mainApplicationClass = deduceMainApplicationClass();
}

3.run方法詳解

public ConfigurableApplicationContext run(String... args) {
    long startTime = System.nanoTime();
    DefaultBootstrapContext bootstrapContext = createBootstrapContext();
    ConfigurableApplicationContext context = null;
    configureHeadlessProperty();

    // 第一步:建立監聽器
    SpringApplicationRunListeners listeners = getRunListeners(args);

    // 釋出事件:開始啟動
    listeners.starting(bootstrapContext, this.mainApplicationClass);

    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

        // 第二步:準備環境變數
        // 把一系列設定整合成 environment,包括 系統變數、JVM引數、啟動引數(args)、配置檔案(application、bootstrap等)
        ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);

        Banner printedBanner = printBanner(environment);

        // 第三步:建立空容器
        // 根據 new 時判斷的 web型別(webApplicationType),建立對應型別的容器
        context = createApplicationContext();

        context.setApplicationStartup(this.applicationStartup);

        // 第四步:準備容器
        // 把前面準備的東西注入進去,包括 環境變數(environment)、事件監聽器(listeners)、啟動引數(applicationArguments)
        prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);

        // 第五步:啟用容器
        // 解析@SpringBootApplication註解,載入自動配置類
        // 進行Bean的掃描、載入、例項化、依賴注入
        // 如果是Web應用,會啟動一個內嵌式的Web伺服器比如Tomcat
        refreshContext(context);

        // 容器初始化後的操作,是個空方法,要使用需要繼承並重寫
        afterRefresh(context, applicationArguments);

        Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
        }

        // 釋出事件:已啟動
        listeners.started(context, timeTakenToStartup);

        // 執行啟動後的自定方法:
        // 實現 ApplicationRunner、CommandLineRunner 介面
        callRunners(context, applicationArguments);
    }
    catch (Throwable ex) {
        if (ex instanceof AbandonedRunException) {
            throw ex; 
        }
        handleRunFailure(context, ex, listeners);
        throw new IllegalStateException(ex);
    }
    
    try {
        // 檢查應用是否還在執行
        if (context.isRunning()) {
            Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
            // 釋出事件:準備就緒
            listeners.ready(context, timeTakenToReady);
        }
    }
    catch (Throwable ex) {
        if (ex instanceof AbandonedRunException) {
            throw ex;
        }
        handleRunFailure(context, ex, null);
        throw new IllegalStateException(ex);
    }
    // 返回Bean容器
    return context;
}

相關文章