1. 程式人生 > >Spring Boot 2.x 啟動全過程原始碼分析(全)

Spring Boot 2.x 啟動全過程原始碼分析(全)

上篇《Spring Boot 2.x 啟動全過程原始碼分析(一)入口類剖析》我們分析了 Spring Boot 入口類 SpringApplication 的原始碼,並知道了其構造原理,這篇我們繼續往下面分析其核心 run 方法。

SpringApplication 例項 run 方法執行過程

上面分析了 SpringApplication 例項物件構造方法初始化過程,下面繼續來看下這個 SpringApplication 物件的 run 方法的原始碼和執行流程。

public ConfigurableApplicationContext run(String... args) {
    // 1、建立並啟動計時監控類
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();

    // 2、初始化應用上下文和異常報告集合
    ConfigurableApplicationContext context = null;
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();

    // 3、設定系統屬性 `java.awt.headless` 的值,預設值為:true
    configureHeadlessProperty();

    // 4、建立所有 Spring 執行監聽器併發布應用啟動事件
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.starting();

    try {
        // 5、初始化預設應用引數類
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                args);

        // 6、根據執行監聽器和應用引數來準備 Spring 環境
        ConfigurableEnvironment environment = prepareEnvironment(listeners,
                applicationArguments);
        configureIgnoreBeanInfo(environment);

        // 7、建立 Banner 列印類
        Banner printedBanner = printBanner(environment);

        // 8、建立應用上下文
        context = createApplicationContext();

        // 9、準備異常報告器
        exceptionReporters = getSpringFactoriesInstances(
                SpringBootExceptionReporter.class,
                new Class[] { ConfigurableApplicationContext.class }, context);

        // 10、準備應用上下文
        prepareContext(context, environment, listeners, applicationArguments,
                printedBanner);

        // 11、重新整理應用上下文
        refreshContext(context);

        // 12、應用上下文重新整理後置處理
        afterRefresh(context, applicationArguments);

        // 13、停止計時監控類
        stopWatch.stop();

        // 14、輸出日誌記錄執行主類名、時間資訊
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass)
                    .logStarted(getApplicationLog(), stopWatch);
        }

        // 15、釋出應用上下文啟動完成事件
        listeners.started(context);

        // 16、執行所有 Runner 執行器
        callRunners(context, applicationArguments);
    }
    catch (Throwable ex) {
        handleRunFailure(context, ex, exceptionReporters, listeners);
        throw new IllegalStateException(ex);
    }

    try {
        // 17、釋出應用上下文就緒事件
        listeners.running(context);
    }
    catch (Throwable ex) {
        handleRunFailure(context, ex, exceptionReporters, null);
        throw new IllegalStateException(ex);
    }

    // 18、返回應用上下文
    return context;
}

所以,我們可以按以下幾步來分解 run 方法的啟動過程。

1、建立並啟動計時監控類

StopWatch stopWatch = new StopWatch();
stopWatch.start();

來看下這個計時監控類 StopWatch 的相關原始碼:

public void start() throws IllegalStateException {
    start("");
}

public void start(String taskName) throws IllegalStateException {
    if (this.currentTaskName != null) {
        throw new IllegalStateException("Can't start StopWatch: it's already running");
    }
    this.currentTaskName = taskName;
    this.startTimeMillis = System.currentTimeMillis();
}

首先記錄了當前任務的名稱,預設為空字串,然後記錄當前 Spring Boot 應用啟動的開始時間。

2、初始化應用上下文和異常報告集合

ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();

3、設定系統屬性 `java.awt.headless` 的值

configureHeadlessProperty();

設定該預設值為:true,Java.awt.headless = true 有什麼作用?

對於一個 Java 伺服器來說經常要處理一些圖形元素,例如地圖的建立或者圖形和圖表等。這些API基本上總是需要執行一個X-server以便能使用AWT(Abstract Window Toolkit,抽象視窗工具集)。然而執行一個不必要的 X-server 並不是一種好的管理方式。有時你甚至不能執行 X-server,因此最好的方案是執行 headless 伺服器,來進行簡單的影象處理。

參考:www.cnblogs.com/princessd8251/p/4000016.html

4、建立所有 Spring 執行監聽器併發布應用啟動事件

SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();

來看下建立 Spring 執行監聽器相關的原始碼:

private SpringApplicationRunListeners getRunListeners(String[] args) {
    Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
    return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
            SpringApplicationRunListener.class, types, this, args));
}

SpringApplicationRunListeners(Log log,
        Collection<? extends SpringApplicationRunListener> listeners) {
    this.log = log;
    this.listeners = new ArrayList<>(listeners);
}

建立邏輯和之前例項化初始化器和監聽器的一樣,一樣呼叫的是 getSpringFactoriesInstances 方法來獲取配置的監聽器名稱並例項化所有的類。

SpringApplicationRunListener 所有監聽器配置在 spring-boot-2.0.3.RELEASE.jar!/META-INF/spring.factories 這個配置檔案裡面。

# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener

5、初始化預設應用引數類

ApplicationArguments applicationArguments = new DefaultApplicationArguments(
        args);

6、根據執行監聽器和應用引數來準備 Spring 環境

ConfigurableEnvironment environment = prepareEnvironment(listeners,
        applicationArguments);
configureIgnoreBeanInfo(environment);

下面我們主要來看下準備環境的 prepareEnvironment 原始碼:

private ConfigurableEnvironment prepareEnvironment(
        SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments) {
    // 6.1) 獲取(或者建立)應用環境
    ConfigurableEnvironment environment = getOrCreateEnvironment();

    // 6.2) 配置應用環境
    configureEnvironment(environment, applicationArguments.getSourceArgs());
    listeners.environmentPrepared(environment);
    bindToSpringApplication(environment);
    if (this.webApplicationType == WebApplicationType.NONE) {
        environment = new EnvironmentConverter(getClassLoader())
                .convertToStandardEnvironmentIfNecessary(environment);
    }
    ConfigurationPropertySources.attach(environment);
    return environment;
}

6.1) 獲取(或者建立)應用環境

private ConfigurableEnvironment getOrCreateEnvironment() {
    if (this.environment != null) {
        return this.environment;
    }
    if (this.webApplicationType == WebApplicationType.SERVLET) {
        return new StandardServletEnvironment();
    }
    return new StandardEnvironment();
}

這裡分為標準 Servlet 環境和標準環境。

6.2) 配置應用環境

protected void configureEnvironment(ConfigurableEnvironment environment,
        String[] args) {
    configurePropertySources(environment, args);
    configureProfiles(environment, args);
}

這裡分為以下兩步來配置應用環境。

  • 配置 property sources

  • 配置 Profiles

這裡主要處理所有 property sources 配置和 profiles 配置。

7、建立 Banner 列印類

Banner printedBanner = printBanner(environment);

這是用來列印 Banner 的處理類,這個沒什麼好說的。

8、建立應用上下文

context = createApplicationContext();

來看下 createApplicationContext() 方法的原始碼:

protected ConfigurableApplicationContext createApplicationContext() {
    Class<?> contextClass = this.applicationContextClass;
    if (contextClass == null) {
        try {
            switch (this.webApplicationType) {
            case SERVLET:
                contextClass = Class.forName(DEFAULT_WEB_CONTEXT_CLASS);
                break;
            case REACTIVE:
                contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
                break;
            default:
                contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
            }
        }
        catch (ClassNotFoundException ex) {
            throw new IllegalStateException(
                    "Unable create a default ApplicationContext, "
                            + "please specify an ApplicationContextClass",
                    ex);
        }
    }
    return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}

其實就是根據不同的應用型別初始化不同的上下文應用類。

9、準備異常報告器

exceptionReporters = getSpringFactoriesInstances(
        SpringBootExceptionReporter.class,
        new Class[] { ConfigurableApplicationContext.class }, context);

邏輯和之前例項化初始化器和監聽器的一樣,一樣呼叫的是 getSpringFactoriesInstances 方法來獲取配置的異常類名稱並例項化所有的異常處理類。

該異常報告處理類配置在 spring-boot-2.0.3.RELEASE.jar!/META-INF/spring.factories 這個配置檔案裡面。

# Error Reporters
org.springframework.boot.SpringBootExceptionReporter=\
org.springframework.boot.diagnostics.FailureAnalyzers

10、準備應用上下文

prepareContext(context, environment, listeners, applicationArguments,
        printedBanner);

來看下 prepareContext() 方法的原始碼:

private void prepareContext(ConfigurableApplicationContext context,
        ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments, Banner printedBanner) {
    // 10.1)繫結環境到上下文
    context.setEnvironment(environment);

    // 10.2)配置上下文的 bean 生成器及資源載入器
    postProcessApplicationContext(context);

    // 10.3)為上下文應用所有初始化器
    applyInitializers(context);

    // 10.4)觸發所有 SpringApplicationRunListener 監聽器的 contextPrepared 事件方法
    listeners.contextPrepared(context);

    // 10.5)記錄啟動日誌
    if (this.logStartupInfo) {
        logStartupInfo(context.getParent() == null);
        logStartupProfileInfo(context);
    }

    // 10.6)註冊兩個特殊的單例bean
    context.getBeanFactory().registerSingleton("springApplicationArguments",
            applicationArguments);
    if (printedBanner != null) {
        context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
    }

    // 10.7)載入所有資源
    Set<Object> sources = getAllSources();
    Assert.notEmpty(sources, "Sources must not be empty");
    load(context, sources.toArray(new Object[0]));

    // 10.8)觸發所有 SpringApplicationRunListener 監聽器的 contextLoaded 事件方法
    listeners.contextLoaded(context);
}

11、重新整理應用上下文

refreshContext(context);

這個主要是重新整理 Spring 的應用上下文,原始碼如下,不詳細說明。

private void refreshContext(ConfigurableApplicationContext context) {
    refresh(context);
    if (this.registerShutdownHook) {
        try {
            context.registerShutdownHook();
        }
        catch (AccessControlException ex) {
            // Not allowed in some environments.
        }
    }
}

12、應用上下文重新整理後置處理

afterRefresh(context, applicationArguments);

看了下這個方法的原始碼是空的,目前可以做一些自定義的後置處理操作。

/**
 * Called after the context has been refreshed.
 * @param context the application context
 * @param args the application arguments
 */
protected void afterRefresh(ConfigurableApplicationContext context,
        ApplicationArguments args) {
}

13、停止計時監控類

stopWatch.stop();
public void stop() throws IllegalStateException {
    if (this.currentTaskName == null) {
        throw new IllegalStateException("Can't stop StopWatch: it's not running");
    }
    long lastTime = System.currentTimeMillis() - this.startTimeMillis;
    this.totalTimeMillis += lastTime;
    this.lastTaskInfo = new TaskInfo(this.currentTaskName, lastTime);
    if (this.keepTaskList) {
        this.taskList.add(this.lastTaskInfo);
    }
    ++this.taskCount;
    this.currentTaskName = null;
}

計時監聽器停止,並統計一些任務執行資訊。

14、輸出日誌記錄執行主類名、時間資訊

if (this.logStartupInfo) {
    new StartupInfoLogger(this.mainApplicationClass)
            .logStarted(getApplicationLog(), stopWatch);
}

15、釋出應用上下文啟動完成事件

listeners.started(context);

觸發所有 SpringApplicationRunListener 監聽器的 started 事件方法。

16、執行所有 Runner 執行器

callRunners(context, applicationArguments);
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);
        }
    }
}

執行所有 ApplicationRunner 和 CommandLineRunner 這兩種執行器,不詳細展開了。

17、釋出應用上下文就緒事件

listeners.running(context);

觸發所有 SpringApplicationRunListener 監聽器的 running 事件方法。

18、返回應用上下文

return context;

總結

Spring Boot 的啟動全過程原始碼分析至此,分析 Spring 原始碼真是一個痛苦的過程,希望能給大家提供一點參考和思路,也希望能給正在 Spring Boot 學習路上的朋友一點收穫。