精盡Spring Boot源碼分析 – SpringApplication 啟動類的啟動過程

該系列文章是筆者在學習 Spring Boot 過程中總結下來的,裡面涉及到相關源碼,可能對讀者不太友好,請結合我的源碼注釋 Spring Boot 源碼分析 GitHub 地址 進行閱讀

Spring Boot 版本:2.2.x

最好對 Spring 源碼有一定的了解,可以先查看我的 《死磕 Spring 之 IoC 篇 – 文章導讀》 系列文章

如果該篇內容對您有幫助,麻煩點擊一下「推薦」,也可以關注部落客,感激不盡~

該系列其他文章請查看:《精盡 Spring Boot 源碼分析 – 文章導讀》

概述

我們 Spring Boot 項目的啟動類通常有下面三種方式

// 方式一
@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}
// 方式二
@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        new SpringApplicationBuilder(Application.class).run(args);
    }
}
// 方式三
@SpringBootApplication
public class Application extends SpringBootServletInitializer {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        return builder.sources(Application.class);
    }
}

方式一方式二本質上都是通過調用 SpringApplication#run(..) 方法來啟動應用,不同的是方式二通過構建器模式,先構建一個 SpringApplication 實例對象,然後調用其 run(..) 方法啟動應用,這種方式可以對 SpringApplication 進行配置,更加的靈活。

我們再來看到方式三,它和方式一差不多,不同的是它繼承了 SpringBootServletInitializer 這個類,作用就是當你的 Spring Boot 項目打成 war 包時能夠放入外部的 Tomcat 容器中運行,如果是 war 包,那上面的 main(...) 方法自然是不需要的,當然,configure(..) 方法也可以不要。

在上篇 《精盡 Spring Boot 源碼分析 – Jar 包的啟動實現》 文章中講到,通過 java -jar 啟動 Spring Boot 應用時,最終會調用我們啟動類的 main(..) 方法,那麼本文主要就是對 SpringApplication 這個類進行分析。至於上面 @SpringBootApplication 註解和方式三的相關內容在後續的文章會講到。

SpringApplicationBuilder

org.springframework.boot.builder.SpringApplicationBuilder,SpringApplication 的構建器,如下:

public class SpringApplicationBuilder {

	private final SpringApplication application;

	private ConfigurableApplicationContext context;

	private final AtomicBoolean running = new AtomicBoolean(false);

	private final Set<Class<?>> sources = new LinkedHashSet<>();

	public SpringApplicationBuilder(Class<?>... sources) {
		this.application = createSpringApplication(sources);
	}
    
    protected SpringApplication createSpringApplication(Class<?>... sources) {
		return new SpringApplication(sources);
	}
    
    public ConfigurableApplicationContext run(String... args) {
		if (this.running.get()) {
			// If already created we just return the existing context
			return this.context;
		}
		configureAsChildIfNecessary(args);
		if (this.running.compareAndSet(false, true)) {
			synchronized (this.running) {
				// If not already running copy the sources over and then run.
				this.context = build().run(args);
			}
		}
		return this.context;
	}
    
    public SpringApplication build() {
		return build(new String[0]);
	}
    
    public SpringApplication build(String... args) {
		configureAsChildIfNecessary(args);
		this.application.addPrimarySources(this.sources);
		return this.application;
	}
}

上面僅列出了 SpringApplicationBuilder 的部分程式碼,它支援對 SpringApplication 進行配置,底層還是通過 SpringApplication 這個類來啟動應用的,不過多的講述,感興趣的可以去看看。

SpringApplication

org.springframework.boot.SpringApplication,Spring 應用啟動器。正如其程式碼上所添加的注釋,它來提供啟動 Spring 應用的功能。

Class that can be used to bootstrap and launch a Spring application from a Java main method.

相關屬性

public class SpringApplication {

	/**
	 * Spring 應用上下文(非 Web 場景)
	 * The class name of application context that will be used by default for non-web environments.
	 */
	public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."
			+ "annotation.AnnotationConfigApplicationContext";

	/**
	 * Spring 應用上下文(Web 場景)
	 * The class name of application context that will be used by default for web environments.
	 */
	public static final String DEFAULT_SERVLET_WEB_CONTEXT_CLASS = "org.springframework.boot."
			+ "web.servlet.context.AnnotationConfigServletWebServerApplicationContext";

	/**
	 * Spring 應用上下文(Reactive 場景)
	 * The class name of application context that will be used by default for reactive web environments.
	 */
	public static final String DEFAULT_REACTIVE_WEB_CONTEXT_CLASS = "org.springframework."
			+ "boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext";

	/**
	 * 主 Bean(通常為我們的啟動類,優先註冊)
	 */
	private Set<Class<?>> primarySources;
	/**
	 * 來源 Bean(優先註冊)
	 */
	private Set<String> sources = new LinkedHashSet<>();
	/**
	 * 啟動類
	 */
	private Class<?> mainApplicationClass;
	/**
	 * Banner 列印模式
	 */
	private Banner.Mode bannerMode = Banner.Mode.CONSOLE;
	/**
	 * 是否列印應用啟動耗時日誌
	 */
	private boolean logStartupInfo = true;
	/**
	 * 是否接收命令行中的參數
	 */
	private boolean addCommandLineProperties = true;
	/**
	 * 是否設置 ConversionService 類型轉換器
	 */
	private boolean addConversionService = true;
	/**
	 * Banner 對象(用於輸出橫幅)
	 */
	private Banner banner;
	/**
	 * 資源載入對象
	 */
	private ResourceLoader resourceLoader;
	/**
	 * Bean 名稱生成器
	 */
	private BeanNameGenerator beanNameGenerator;
	/**
	 * Spring 應用的環境對象
	 */
	private ConfigurableEnvironment environment;
	/**
	 * Spring 應用上下文的 Class 對象
	 */
	private Class<? extends ConfigurableApplicationContext> applicationContextClass;
	/**
	 * Web 應用的類型(Servlet、Reactive)
	 */
	private WebApplicationType webApplicationType;
	/**
	 * 是否註冊鉤子函數,用於 JVM 關閉時關閉 Spring 應用上下文
	 */
	private boolean registerShutdownHook = true;
	/**
	 * 保存 ApplicationContextInitializer 對象(主要是對 Spring 應用上下文做一些初始化工作)
	 */
	private List<ApplicationContextInitializer<?>> initializers;
	/**
	 * 保存 ApplicationListener 監聽器(支援在整個 Spring Boot 的多個時間點進行擴展)
	 */
	private List<ApplicationListener<?>> listeners;
	/**
	 * 默認的配置項
	 */
	private Map<String, Object> defaultProperties;
	/**
	 * 額外的 profile
	 */
	private Set<String> additionalProfiles = new HashSet<>();
	/**
	 * 是否允許覆蓋 BeanDefinition
	 */
	private boolean allowBeanDefinitionOverriding;
	/**
	 * 是否為自定義的 Environment 對象
	 */
	private boolean isCustomEnvironment = false;
	/**
	 * 是否支援延遲初始化(需要通過 {@link LazyInitializationExcludeFilter} 過濾)
	 */
	private boolean lazyInitialization = false;
}

上面基本上列出了 SpringApplication 的所有屬性,每個屬性都比較關鍵,大家先有一個印象,後面也可以回過頭來看

構造器

public SpringApplication(Class<?>... primarySources) {
    this(null, primarySources);
}

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    // <1> 設置資源載入器
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    // <2> 設置主要的 Class 類對象,通常是我們的啟動類
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    // <3> 通過 `classpath` 判斷是否存在相應的 Class 類對象,來決定當前 Web 應用的類型(REACTIVE、SERVLET、NONE),默認為 **SERVLET**
    // 不同的類型後續創建的 Environment 類型不同
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    /**
     * <4> 初始化所有 `ApplicationContextInitializer` 類型的對象,並保存至 `initializers` 集合中
     * 通過類載入器從 `META-INF/spring.factories` 文件中獲取 ApplicationContextInitializer 類型的類名稱,並進行實例化
     */
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    /**
     * <5> 初始化所有 `ApplicationListener` 類型的對象,並保存至 `listeners` 集合中
     * 通過類載入器從 `META-INF/spring.factories` 文件中獲取 ApplicationListener 類型的類名稱,並進行實例化
     * 例如有一個 {@link ConfigFileApplicationListener}
     */
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    // <6> 獲取當前被調用的 `main` 方法所屬的 Class 類對象,並設置(主要用於列印日誌)
    this.mainApplicationClass = deduceMainApplicationClass();
}

在我們自己的啟動類中不管是通過哪種方式都是會先創建一個 SpringApplication 實例對象的,可以先看下它的 run(Class<?>, String...) 方法:

public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
    return run(new Class<?>[] { primarySource }, args);
}
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
    // 同樣是先創建一個 SpringApplication 對象
    return new SpringApplication(primarySources).run(args);
}

實例化的過程中做了不少事情,如下:

  1. 設置資源載入器,默認為 null,可以通過 SpringApplicationBuilder 設置

  2. 設置 primarySources 為主要的 Class 類對象,通常是我們的啟動類

  3. 通過 classpath 判斷是否存在相應的 Class 類對象,來決定當前 Web 應用的類型(REACTIVE、SERVLET、NONE),默認為 SERVLET,不同的類型後續創建的 Environment 類型不同

    public enum WebApplicationType {
    
    	NONE, SERVLET, REACTIVE;
    
    	private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet",
    			"org.springframework.web.context.ConfigurableWebApplicationContext" };
    
    	private static final String WEBMVC_INDICATOR_CLASS = "org.springframework.web.servlet.DispatcherServlet";
    
    	private static final String WEBFLUX_INDICATOR_CLASS = "org.springframework.web.reactive.DispatcherHandler";
    
    	private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";
    
    	private static final String SERVLET_APPLICATION_CONTEXT_CLASS = "org.springframework.web.context.WebApplicationContext";
    
    	private static final String REACTIVE_APPLICATION_CONTEXT_CLASS = "org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext";
    
    	static WebApplicationType deduceFromClasspath() {
    		if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
    				&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
    			return WebApplicationType.REACTIVE;
    		}
    		for (String className : SERVLET_INDICATOR_CLASSES) {
    			if (!ClassUtils.isPresent(className, null)) {
    				return WebApplicationType.NONE;
    			}
    		}
    		return WebApplicationType.SERVLET;
    	}
    }
    

    很簡單,就是依次判斷當前 JVM 中是否存在相關的 Class 類對象,來決定使用哪種 Web 類型,默認是 SERVLET 類型

  4. 初始化所有 ApplicationContextInitializer 類型的對象,並保存至 initializers 集合中

  5. 初始化所有 ApplicationListener 類型的對象,並保存至 listeners 集合中,例如 ConfigFileApplicationListenerLoggingApplicationListener

  6. 獲取當前被調用的 main 方法所屬的 Class 類對象,並設置(主要用於列印日誌)

    private Class<?> deduceMainApplicationClass() {
        try {
            // 獲取當前的調用棧
            StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
            // 對調用棧進行遍歷,找到 `main` 方法所在的 Class 類對象並返回
            for (StackTraceElement stackTraceElement : stackTrace) {
                if ("main".equals(stackTraceElement.getMethodName())) {
                    return Class.forName(stackTraceElement.getClassName());
                }
            }
        } catch (ClassNotFoundException ex) {
            // Swallow and continue
        }
        return null;
    }
    

上面的第 45 步都是通過類載入器從 META-INF/spring.factories 文件中分別獲取 ApplicationContextInitializerApplicationListener 類型的類名稱,然後進行實例化,這個兩種類型的對象都是對 Spring 的一種拓展,像很多框架整合 Spring Boot 都可以通過自定義的 ApplicationContextInitializer 對 ApplicationContext 進行一些初始化,通過 ApplicationListener 在 Spring 應用啟動的不同階段來織入一些功能

getSpringFactoriesInstances 方法

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
    return getSpringFactoriesInstances(type, new Class<?>[] {});
}

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
    // <1> 獲取類載入器
    ClassLoader classLoader = getClassLoader();
    // Use names and ensure unique to protect against duplicates
    // <2> 通過類載入器從 `META-INF/spring.factories` 文件中獲取類型為 `type` 的類名稱
    Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    // <3> 為上一步獲取到的所有類名稱創建對應的實例對象
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
    // <4> 通過 `@Order` 註解進行排序
    AnnotationAwareOrderComparator.sort(instances);
    // <5> 返回排序後的 `type` 類型的實例對象
    return instances;
}

過程比較簡單,如下:

  1. 獲取類載入器

  2. 通過類載入器從所有 META-INF/spring.factories 文件中獲取類型為 type 的類名稱,這裡的 SpringFactoriesLoader 是 Spring 中的一個類

  3. 為上一步獲取到的所有類名稱創建對應的實例對象

    private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,
            ClassLoader classLoader, Object[] args, Set<String> names) {
        List<T> instances = new ArrayList<>(names.size());
        // 遍歷所有的類名稱,依次創建實例對象,一併返回
        for (String name : names) {
            try {
                // 獲取對應的 Class 類對象
                Class<?> instanceClass = ClassUtils.forName(name, classLoader);
                // 對 Class 類對象進行校驗,判斷類型是否匹配
                Assert.isAssignable(type, instanceClass);
                // 獲取指定入參類型(ConfigurableApplicationContext)的構造器
                Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
                // 通過構造器創建一個實例對象
                T instance = (T) BeanUtils.instantiateClass(constructor, args);
                instances.add(instance);
            }
            catch (Throwable ex) {
                throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
            }
        }
        return instances;
    }
    
  4. 通過 @Order 註解進行排序

  5. 返回排序後的 type 類型的實例對象

SpringApplication#run 方法

上面已經講述了 SpringApplication 的實例化過程,那麼接下來就是調用它的 run(String... args) 方法來啟動 Spring 應用,該過程如下:

public ConfigurableApplicationContext run(String... args) {
    // <1> 創建 StopWatch 對象並啟動,主要用於統計當前方法執行過程的耗時
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    ConfigurableApplicationContext context = null;
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    // <2> 設置 `java.awt.headless` 屬性,和 AWT 相關,暫時忽略
    configureHeadlessProperty();
    // <3> 初始化所有 `SpringApplicationRunListener` 類型的對象,並全部封裝到 `SpringApplicationRunListeners` 對象中
    SpringApplicationRunListeners listeners = getRunListeners(args);
    // <4> 啟動所有的 `SpringApplicationRunListener` 監聽器
    // 例如 `EventPublishingRunListener` 會廣播 ApplicationEvent 應用正在啟動的事件,
    // 它裡面封裝了所有的 `ApplicationListener` 對象,那麼此時就可以通過它們做一些初始化工作,進行拓展
    listeners.starting();
    try {
        // <5> 創建一個應用參數對象,將 `main(String[] args)` 方法的 `args` 參數封裝起來,便於後續使用
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        // <6> 準備好當前應用 Environment 環境,這裡會載入出所有的配置資訊,包括 `application.yaml` 和外部的屬性配置
        ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);

        configureIgnoreBeanInfo(environment);
        // <7> 列印 banner 內容
        Banner printedBanner = printBanner(environment);
        // <8> 對 `context` (Spring 上下文)進行實例化
        // 例如 Servlet(默認)會創建一個 AnnotationConfigServletWebServerApplicationContext 實例對象
        context = createApplicationContext();
        // <9> 獲取異常報告器,通過類載入器從 `META-INF/spring.factories` 文件中獲取 SpringBootExceptionReporter 類型的類名稱,並進行實例化
        exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
                new Class[] { ConfigurableApplicationContext.class }, context);
        // <10> 對 Spring 應用上下文做一些初始化工作,例如執行 ApplicationContextInitializer#initialize(..) 方法
        prepareContext(context, environment, listeners, applicationArguments, printedBanner);
        /**
         * <11> 刷新 Spring 應用上下文,在這裡會完成所有 Spring Bean 的初始化,同時會初始化好 Servlet 容器,例如 Tomcat
         * 這一步涉及到 Spring IoC 的所有內容,參考[死磕Spring之IoC篇 - Spring 應用上下文 ApplicationContext](//www.cnblogs.com/lifullmoon/p/14453083.html)
         * 在 {@link ServletWebServerApplicationContext#onRefresh()} 方法中會創建一個 Servlet 容器(默認為 Tomcat),也就是當前 Spring Boot 應用所運行的 Web 環境
         */
        refreshContext(context);
        // <12> 完成刷新 Spring 應用上下文的後置操作,空實現,擴展點
        afterRefresh(context, applicationArguments);
        // <13> 停止 StopWatch,統計整個 Spring Boot 應用的啟動耗時,並列印
        stopWatch.stop();
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
        }
        // <14> 對所有的 SpringApplicationRunListener 監聽器進行廣播,發布 ApplicationStartedEvent 應用已啟動事件
        listeners.started(context);
        // <15> 回調 IoC 容器中所有 ApplicationRunner 和 CommandLineRunner 類型的啟動器
        callRunners(context, applicationArguments);
    }
    catch (Throwable ex) {
        // 處理異常,同時會將異常發送至上面第 `9` 步獲取到的異常報告器
        handleRunFailure(context, ex, exceptionReporters, listeners);
        throw new IllegalStateException(ex);
    }

    try {
        // <16> 對所有的 SpringApplicationRunListener 監聽器進行廣播,發布 ApplicationReadyEvent 應用已就緒事件
        listeners.running(context);
    }
    catch (Throwable ex) {
        // 處理異常,同時會將異常發送至上面第 `9` 步獲取到的異常報告器
        handleRunFailure(context, ex, exceptionReporters, null);
        throw new IllegalStateException(ex);
    }
    return context;
}

整個啟動過程做了很多事情,主要過程如下:

  1. 創建 StopWatch 對象並啟動,主要用於統計當前方法執行過程的耗時

  2. 設置 java.awt.headless 屬性,和 AWT 相關,暫時忽略

  3. 調用 getRunListeners(..) 方法,初始化所有 SpringApplicationRunListener 類型的對象,並全部封裝到 SpringApplicationRunListeners 對象中

  4. 啟動所有的 SpringApplicationRunListener 監聽器,例如 EventPublishingRunListener 會廣播 ApplicationEvent 應用正在啟動的事件,它裡面封裝了所有的 ApplicationListener 對象,那麼此時就可以通過它們做一些初始化工作,進行拓展

  5. 創建一個 ApplicationArguments 應用參數對象,將 main(String[] args) 方法的 args 參數封裝起來,便於後續使用

  6. 調用 prepareEnvironment(..) 方法,準備好當前應用 Environment 環境,這裡會載入出所有的配置資訊,包括 application.yaml 和外部的屬性配置

  7. 調用 printBanner(..) 方法,列印 banner 內容

  8. 調用 createApplicationContext() 方法, 對 context(Spring 上下文)進行實例化,例如 Servlet(默認)會創建一個 AnnotationConfigServletWebServerApplicationContext 實例對象

  9. 獲取異常報告器,通過類載入器從 META-INF/spring.factories 文件中獲取 SpringBootExceptionReporter 類型的類名稱,並進行實例化

  10. 調用 prepareContext(..) 方法,對 Spring 應用上下文做一些初始化工作,例如執行 ApplicationContextInitializer#initialize(..) 方法

  11. 調用 refreshContext(..) 方法,刷新 Spring 應用上下文,在這裡會完成所有 Spring Bean 的初始化,同時會初始化好 Servlet 容器,例如 Tomcat

    這一步涉及到 Spring IoC 的所有內容,參考 《死磕Spring之IoC篇 – Spring 應用上下文 ApplicationContext》

    ServletWebServerApplicationContext#onRefresh() 方法中會創建一個 Servlet 容器(默認為 Tomcat),也就是當前 Spring Boot 應用所運行的 Web 環境

  12. 調用 afterRefresh(..) 方法,完成刷新 Spring 應用上下文的後置操作,空實現,擴展點

  13. 停止 StopWatch,統計整個 Spring Boot 應用的啟動耗時,並列印

  14. 對所有的 SpringApplicationRunListener 監聽器進行廣播,發布 ApplicationStartedEvent 應用已啟動事件,通常只有一個 EventPublishingRunListener 對象

  15. 回調 IoC 容器中所有 ApplicationRunner 和 CommandLineRunner 類型的啟動器,默認情況下沒有,先暫時忽略

  16. 對所有的 SpringApplicationRunListener 監聽器進行廣播,發布 ApplicationReadyEvent 應用已就緒事件,通常只有一個 EventPublishingRunListener 對象

啟動 Spring 應用的整個主流程清晰明了,先準備好當前應用的 Environment 環境,然後創建 Spring ApplicationContext 應用上下文。

該方法的整個過程更多的細節在於上面每一步調用的方法,抽絲剝繭,對於非常複雜的地方會另起文章進行分析

3. getRunListeners 方法

getRunListeners(String[] args) 方法,初始化所有 SpringApplicationRunListener 類型的對象,並全部封裝到 SpringApplicationRunListeners 對象中,如下:

private SpringApplicationRunListeners getRunListeners(String[] args) {
    // 指定實例化對象所使用的構造器的入參類型
    Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
    // 通過類載入器從 `META-INF/spring.factories` 文件中獲取 SpringApplicationRunListener 類型的類名稱,並進行實例化
    return new SpringApplicationRunListeners(logger,
            getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
}

這裡同樣調用上面講過的 getSpringFactoriesInstances(..) 方法,通過類載入器從 META-INF/spring.factories 文件中獲取 SpringApplicationRunListener 類型的類名稱,並進行實例化

最後會將它們全部封裝到 SpringApplicationRunListeners 對象中,就是把一個 List 封裝到一個對象中,不過默認情況只有一個 EventPublishingRunListener 對象,其內部又封裝了 SpringApplication 中的所有 ApplicationListener 應用監聽器們,例如 ConfigFileApplicationListenerLoggingApplicationListener

6. prepareEnvironment 方法

prepareEnvironment(SpringApplicationRunListeners, ApplicationArguments) 方法,準備好當前應用 Environment 環境,載入出所有的配置資訊,包括 application.yaml 和外部的屬性配置,如下:

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments) {
    // Create and configure the environment
    // <1> 根據 Web 應用的類型創建一個 StandardEnvironment 對象 `environment`
    ConfigurableEnvironment environment = getOrCreateEnvironment();
    // <2> 為 `environment` 配置默認屬性(如果有)並設置需要激活的 `profile` 們
    configureEnvironment(environment, applicationArguments.getSourceArgs());
    // <3> 將當前 `environment` 的 MutablePropertySources 封裝成 SpringConfigurationPropertySources 添加到 MutablePropertySources 首部
    ConfigurationPropertySources.attach(environment);
    /**
     * <4> 對所有的 SpringApplicationRunListener 廣播 ApplicationEvent 應用環境已準備好的事件,這一步比較複雜
     * 例如 Spring Cloud 的 BootstrapApplicationListener 監聽到該事件會創建一個 ApplicationContext 作為當前 Spring 應用上下文的父容器,同時會讀取 `bootstrap.yml` 文件的資訊
     * {@link ConfigFileApplicationListener} 監聽到該事件然後去解析 `application.yml` 等應用配置文件的配置資訊
     */
    listeners.environmentPrepared(environment);
    // <5> 將 `environment` 綁定到當前 SpringApplication 上
    bindToSpringApplication(environment);
    // <6> 如果不是自定義的 Environment 則需要根據 Web 應用類型轉換成對應 Environment 類型
    if (!this.isCustomEnvironment) {
        environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
                deduceEnvironmentClass());
    }
    // <7> 再次進行上面第 `3` 步的處理過程,防止上面幾步對上面的 PropertySources 有修改
    ConfigurationPropertySources.attach(environment);
    // <8> 返回準備好的 `environment`
    return environment;
}

該過程如下:

  1. 根據 Web 應用的類型創建一個 StandardEnvironment 對象 environment

    private ConfigurableEnvironment getOrCreateEnvironment() {
        if (this.environment != null) {
            return this.environment;
        }
        switch (this.webApplicationType) {
        case SERVLET:
            return new StandardServletEnvironment();
        case REACTIVE:
            return new StandardReactiveWebEnvironment();
        default:
            return new StandardEnvironment();
        }
    }
    
  2. environment 配置默認屬性(如果有)並設置需要激活的 profile

    protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
        if (this.addConversionService) {
            // <1> 獲取 ConversionService 類型轉換器並設置到 Environment 對象中
            ConversionService conversionService = ApplicationConversionService.getSharedInstance();
            environment.setConversionService((ConfigurableConversionService) conversionService);
        }
        // <2> 配置屬性源裡面的屬性
        // 例如有默認屬性則將他們添加至最後,命令行中的參數則添加至最前面
        configurePropertySources(environment, args);
        // <3> 設置需要激活的 `profile` 們
        // 例如通過 `-Dspring.profiles.active=dev` 配置需要激活的環境
        configureProfiles(environment, args);
    }
    
    protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {
        MutablePropertySources sources = environment.getPropertySources();
        // 如果默認配置屬性不為空則添加至最後
        if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {
            sources.addLast(new MapPropertySource("defaultProperties", this.defaultProperties));
        }
        // 如果 `main` 方法有入參
        if (this.addCommandLineProperties && args.length > 0) {
            String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;
            if (sources.contains(name)) {
                PropertySource<?> source = sources.get(name);
                CompositePropertySource composite = new CompositePropertySource(name);
                composite.addPropertySource(
                        new SimpleCommandLinePropertySource("springApplicationCommandLineArgs", args));
                composite.addPropertySource(source);
                sources.replace(name, composite);
            }
            else {
                // 將命令行中的入參添加至最前面
                // 例如 'java -jar xxx.jar --spring.profiles.active=dev',那麼這裡就可以獲取到
                sources.addFirst(new SimpleCommandLinePropertySource(args));
            }
        }
    }
    

    可以看到會將 main(String[] args) 方法入參中的 -- 開頭的參數設置到 Environment 中

  3. 將當前 environment 的 MutablePropertySources 封裝成 SpringConfigurationPropertySources 添加到 MutablePropertySources 首部

  4. 對所有的 SpringApplicationRunListener 廣播 ApplicationEvent 應用環境已準備好的事件,這一步比較複雜,例如 Spring Cloud 的 BootstrapApplicationListener 監聽到該事件會創建一個 ApplicationContext 作為當前 Spring 應用上下文的父容器,同時會讀取 bootstrap.yml 文件的資訊

    這裡會有一個 ConfigFileApplicationListener 監聽到該事件然後去解析 application.yml 等應用配置文件的配置資訊

  5. environment 綁定到當前 SpringApplication 上

  6. 如果不是自定義的 Environment 則需要根據 Web 應用類型轉換成對應 Environment 類型

  7. 再次進行上面第 3 步的處理過程,防止上面幾步對上面的 PropertySources 有修改

  8. 返回準備好的 environment

該方法準備好了當前應用 Environment 環境,主要在於上面第 4 步,是 ApplicationListener 監聽器的一個擴展點,在這裡會載入出所有的配置資訊,包括 application.yml 和外部配置,解析配置的過程比較複雜,在後面的文章中單獨分析

8. createApplicationContext 方法

createApplicationContext() 方法,對 context(Spring 上下文)進行實例化,如下:

protected ConfigurableApplicationContext createApplicationContext() {
    Class<?> contextClass = this.applicationContextClass;
    if (contextClass == null) {
        try {
            switch (this.webApplicationType) {
            case SERVLET:
                // org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext
                contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
                break;
            case REACTIVE:
                // org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext
                contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
                break;
            default:
                // org.springframework.context.annotation.AnnotationConfigApplicationContext
                contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
            }
        }
        catch (ClassNotFoundException ex) {
            throw new IllegalStateException(
                    "Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex);
        }
    }
    // 實例化這個 Class 對象
    return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}

不同的應用類型創建不同的 Spring 應用上下文對象:

  • SERVLET(默認是這個):AnnotationConfigServletWebServerApplicationContext
  • REACTIVEAnnotationConfigReactiveWebServerApplicationContext
  • DEFAULTAnnotationConfigApplicationContext

10. prepareContext 方法

prepareContext(ConfigurableApplicationContext, ConfigurableEnvironment, SpringApplicationRunListeners, ApplicationArguments, Banner) 方法,對 Spring 應用上下文做一些初始化工作,如下:

private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
        SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
    // <1> 為 Spring 應用上下文設置 Environment 環境
    context.setEnvironment(environment);
    // <2> 將一些工具 Bean 設置到 Spring 應用上下文中,供使用
    postProcessApplicationContext(context);
    // <3> 通知 ApplicationContextInitializer 對 Spring 應用上下文進行初始化工作
    // 參考 SpringApplication 構造方法
    applyInitializers(context);
    // <4> 對所有 SpringApplicationRunListener 進行廣播,發布 ApplicationContextInitializedEvent 初始化事件
    listeners.contextPrepared(context);
    if (this.logStartupInfo) {
        logStartupInfo(context.getParent() == null);
        logStartupProfileInfo(context);
    }
    // Add boot specific singleton beans
    // <5> 嚮應用上下文註冊 `main(String[])` 方法的參數 Bean 和 Banner 對象
    ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
    beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
    if (printedBanner != null) {
        beanFactory.registerSingleton("springBootBanner", printedBanner);
    }
    if (beanFactory instanceof DefaultListableBeanFactory) {
        ((DefaultListableBeanFactory) beanFactory)
                .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
    }
    if (this.lazyInitialization) {
        context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
    }
    // Load the sources
    // <6> 獲取 `primarySources`(例如你的啟動類)和 `sources`(例如 Spring Cloud 中的 @BootstrapConfiguration)源對象
    Set<Object> sources = getAllSources();
    // 應用上下文的源對象不能為空
    Assert.notEmpty(sources, "Sources must not be empty");
    // <7> 將上面的源對象載入成 BeanDefinition 並註冊
    load(context, sources.toArray(new Object[0]));
    // <8> 對所有的 SpringApplicationRunListener 廣播 ApplicationPreparedEvent 應用已準備事件
    // 會把 ApplicationListener 添加至 Spring 應用上下文中
    listeners.contextLoaded(context);
}

該過程如下:

  1. 為 Spring 應用上下文設置 Environment 環境

  2. 將一些工具 Bean 設置到 Spring 應用上下文中,供使用

    protected void postProcessApplicationContext(ConfigurableApplicationContext context) {
        if (this.beanNameGenerator != null) {
            // 註冊 Bean 名稱生成器
            context.getBeanFactory().registerSingleton(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR,
                    this.beanNameGenerator);
        }
        if (this.resourceLoader != null) {
            if (context instanceof GenericApplicationContext) {
                // 設置資源載入器
                ((GenericApplicationContext) context).setResourceLoader(this.resourceLoader);
            }
            if (context instanceof DefaultResourceLoader) {
                ((DefaultResourceLoader) context).setClassLoader(this.resourceLoader.getClassLoader());
            }
        }
        if (this.addConversionService) {
            // 設置類型轉換器
            context.getBeanFactory().setConversionService(ApplicationConversionService.getSharedInstance());
        }
    }
    
  3. 通知 ApplicationContextInitializer 對 Spring 應用上下文進行初始化工作

    protected void applyInitializers(ConfigurableApplicationContext context) {
        for (ApplicationContextInitializer initializer : getInitializers()) {
            Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(),
                    ApplicationContextInitializer.class);
            Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
            // 執行 Spring 應用上下文初始器
            // 例如 ContextIdApplicationContextInitializer 會向 Spring 應用上下文註冊一個 ContextId 對象
            initializer.initialize(context);
        }
    }
    
  4. 對所有 SpringApplicationRunListener 進行廣播,發布 ApplicationContextInitializedEvent 初始化事件

  5. 向 Spring 應用上下文註冊 main(String[]) 方法的參數 Bean 和 Banner 對象

  6. 獲取 primarySources(例如你的啟動類)和 sources(例如 Spring Cloud 中的 @BootstrapConfiguration)源對象,沒有的話會拋出異常

  7. 將上面的源對象載入成 BeanDefinition 並註冊

  8. 對所有的 SpringApplicationRunListener 廣播 ApplicationPreparedEvent 應用已準備事件,會把 ApplicationListener 添加至 Spring 應用上下文中

通過上面的第 6 步你就知道為什麼我們的啟動類裡面一定得有一個入參為啟動類的 Class 對象了

11. refreshContext 方法

refreshContext(ConfigurableApplicationContext) 方法,刷新 Spring 應用上下文,在這裡會完成所有 Spring Bean 的初始化,同時會初始化好 Servlet 容器,例如 Tomcat,該方法涉及到 Spring IoC 的所有內容,參考 《死磕Spring之IoC篇 – Spring 應用上下文 ApplicationContext》

private void refreshContext(ConfigurableApplicationContext context) {
    if (this.registerShutdownHook) {
        try {
            // 為當前 Spring 應用上下文註冊一個鉤子函數
            // 在 JVM 關閉時先關閉 Spring 應用上下文
            context.registerShutdownHook();
        }
        catch (AccessControlException ex) {
            // Not allowed in some environments.
        }
    }
    // 調用 AbstractApplicationContext#refresh() 方法,刷新 Spring 上下文
    refresh(context);
}

protected void refresh(ApplicationContext applicationContext) {
    Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
    ((AbstractApplicationContext) applicationContext).refresh();
}

該方法主要是調用 AbstractApplicationContext#refresh() 方法,刷新 Spring 應用上下文,整個過程牽涉到 Spring 的所有內容,之前的一系列文章已經分析過,關於更多的細節這裡不展開談論,當然,這個過程會有對 @SpingBootApplication 註解的解析

根據 8. createApplicationContext 方法 方法中講到,我們默認情況下是 SERVLET 應用類型,也就是創建一個 AnnotationConfigServletWebServerApplicationContext 對象,在其父類 ServletWebServerApplicationContext 中重寫了 onRefresh() 方法,會創建一個 Servlet 容器(默認為 Tomcat),也就是當前 Spring Boot 應用所運行的 Web 環境,這部分內容放在後面的文章單獨分析。

SpringApplicationRunListeners

org.springframework.boot.SpringApplicationRunListeners,對 SpringApplicationRunListener 數組的封裝

class SpringApplicationRunListeners {

	private final Log log;

	/**
	 * 封裝的所有 SpringApplicationRunListener
	 * Spring Boot 在 META-INF/spring.factories 只配置 {@link EventPublishingRunListener} 監聽器
	 */
	private final List<SpringApplicationRunListener> listeners;

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

	void starting() {
		for (SpringApplicationRunListener listener : this.listeners) {
			listener.starting();
		}
	}

	void environmentPrepared(ConfigurableEnvironment environment) {
		for (SpringApplicationRunListener listener : this.listeners) {
			listener.environmentPrepared(environment);
		}
	}

	void contextPrepared(ConfigurableApplicationContext context) {
		for (SpringApplicationRunListener listener : this.listeners) {
			listener.contextPrepared(context);
		}
	}

	void contextLoaded(ConfigurableApplicationContext context) {
		for (SpringApplicationRunListener listener : this.listeners) {
			listener.contextLoaded(context);
		}
	}

	void started(ConfigurableApplicationContext context) {
		for (SpringApplicationRunListener listener : this.listeners) {
			listener.started(context);
		}
	}

	void running(ConfigurableApplicationContext context) {
		for (SpringApplicationRunListener listener : this.listeners) {
			listener.running(context);
		}
	}

	void failed(ConfigurableApplicationContext context, Throwable exception) {
		for (SpringApplicationRunListener listener : this.listeners) {
			callFailedListener(listener, context, exception);
		}
	}

	private void callFailedListener(SpringApplicationRunListener listener, ConfigurableApplicationContext context,
			Throwable exception) {
		try {
			listener.failed(context, exception);
		}
		catch (Throwable ex) {
			if (exception == null) {
				ReflectionUtils.rethrowRuntimeException(ex);
			}
			if (this.log.isDebugEnabled()) {
				this.log.error("Error handling failed", ex);
			}
			else {
				String message = ex.getMessage();
				message = (message != null) ? message : "no error message";
				this.log.warn("Error handling failed (" + message + ")");
			}
		}
	}
}

比較簡單,就是封裝了多個 SpringApplicationRunListener 對象,對於不同類型的事件,調用其不同的方法

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

可以在 META-INF/spring.factories 文件中看到,只有一個 EventPublishingRunListener 對象

EventPublishingRunListener

org.springframework.boot.context.event.EventPublishingRunListener,實現了 SpringApplicationRunListener 介面,事件廣播器,發布不同類型的事件

public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {

	/**
	 * Spring Boot 應用的啟動類
	 */
	private final SpringApplication application;

	/**
	 * 啟動類 `main(String[])` 方法的入參
	 */
	private final String[] args;

	/**
	 * 事件廣播器,包含了所有的 `META-INF/spring.factories` 文件中配置的 {@link ApplicationListener} 監聽器
	 */
	private final SimpleApplicationEventMulticaster initialMulticaster;

	public EventPublishingRunListener(SpringApplication application, String[] args) {
		this.application = application;
		this.args = args;
		this.initialMulticaster = new SimpleApplicationEventMulticaster();
		// 在實例化 SpringApplication 的過程中會從 `META-INF/spring.factories` 文件中獲取 ApplicationListener 類型的類名稱,並進行實例化
		// 這裡會將他們添加至廣播器中
		for (ApplicationListener<?> listener : application.getListeners()) {
			this.initialMulticaster.addApplicationListener(listener);
		}
	}

	@Override
	public int getOrder() {
		return 0;
	}

	@Override
	public void starting() {
		// 廣播 Spring Boot 應用正在啟動事件
		this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
	}

	@Override
	public void environmentPrepared(ConfigurableEnvironment environment) {
		// 廣播 Spring Boot 應用的 Environment 環境已準備事件
		this.initialMulticaster
				.multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
	}

	@Override
	public void contextPrepared(ConfigurableApplicationContext context) {
		// 廣播 Spring Boot 應用的 Spring 上下文已初始化事件
		this.initialMulticaster
				.multicastEvent(new ApplicationContextInitializedEvent(this.application, this.args, context));
	}

	@Override
	public void contextLoaded(ConfigurableApplicationContext context) {
		// 將所有的 ApplicationListener 添加至 Spring 應用上下文
		for (ApplicationListener<?> listener : this.application.getListeners()) {
			if (listener instanceof ApplicationContextAware) {
				((ApplicationContextAware) listener).setApplicationContext(context);
			}
			context.addApplicationListener(listener);
		}
		// 廣播 Spring Boot 應用的 Spring 上下文已準備事件
		this.initialMulticaster.multicastEvent(new ApplicationPreparedEvent(this.application, this.args, context));
	}

	@Override
	public void started(ConfigurableApplicationContext context) {
		// 廣播 Spring Boot 應用的 Spring 上下文已啟動事件
		context.publishEvent(new ApplicationStartedEvent(this.application, this.args, context));
	}

	@Override
	public void running(ConfigurableApplicationContext context) {
		// 廣播 Spring Boot 應用的 Spring 上下文準備就緒事件
		context.publishEvent(new ApplicationReadyEvent(this.application, this.args, context));
	}

	@Override
	public void failed(ConfigurableApplicationContext context, Throwable exception) {
		ApplicationFailedEvent event = new ApplicationFailedEvent(this.application, this.args, context, exception);
		if (context != null && context.isActive()) {
			// Listeners have been registered to the application context so we should
			// use it at this point if we can
			context.publishEvent(event);
		}
		else {
			// An inactive context may not have a multicaster so we use our multicaster to
			// call all of the context's listeners instead
			if (context instanceof AbstractApplicationContext) {
				for (ApplicationListener<?> listener : ((AbstractApplicationContext) context)
						.getApplicationListeners()) {
					this.initialMulticaster.addApplicationListener(listener);
				}
			}
			this.initialMulticaster.setErrorHandler(new LoggingErrorHandler());
			this.initialMulticaster.multicastEvent(event);
		}
	}
}

比較簡單,關鍵在於內部的 SimpleApplicationEventMulticaster 事件廣播器,裡面包含了所有的 META-INF/spring.factories 文件中配置的 ApplicationListener 監聽器,不同的方法發布不同的事件,進行廣播

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener

可以看到 Spring Boot 配置了許多個 ApplicationListener,後續文章會對 ConfigFileApplicationListener 和 LoggingApplicationListener 進行簡單的分析

總結

Spring Boot 應用打成 jar 包後的啟動都是通過 SpringApplication#run(String... args) 這個方法來啟動整個 Spring 應用的,流程大致如下:

  1. META-INF/spring.factories 文件中載入出相關 Class 對象,並進行實例化,例如 ApplicationContextInitializerSpringApplicationRunListenerApplicationListener 對象
  2. 準備好當前 Spring 應用的 Environment 環境,這裡會解析 application.yml 以及外部配置
  3. 創建一個 ApplicationContext 應用上下文對象,默認 SERVLET 類型下創建 AnnotationConfigServletWebServerApplicationContext 對象
  4. 調用 AbstractApplication#refresh() 方法,刷新 Spring 應用上下文,也就是之前一系列 Spring 相關的文章所講述的內容

整個過程有許多個擴展點是通過監聽器機制實現的,在不同階段廣播不同類型的事件,此時 ApplicationListener 就可進行相關的操作

在上面第 4 步中,SERVLET 應用類型下的 Spring 應用上下文會創建一個 Servlet 容器(默認為 Tomcat)

更多的細節在後續文章依次進行分析