該系列文章是筆者在學習 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);
}
例項化的過程中做了不少事情,如下:
-
設定資源載入器,預設為
null
,可以通過SpringApplicationBuilder
設定 -
設定
primarySources
為主要的 Class 類物件,通常是我們的啟動類 -
通過
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 型別
-
初始化所有
ApplicationContextInitializer
型別的物件,並儲存至initializers
集合中 -
初始化所有
ApplicationListener
型別的物件,並儲存至listeners
集合中,例如 ConfigFileApplicationListener 和 LoggingApplicationListener -
獲取當前被呼叫的
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; }
上面的第 4
和 5
步都是通過類載入器從 META-INF/spring.factories
檔案中分別獲取 ApplicationContextInitializer
和 ApplicationListener
型別的類名稱,然後進行例項化,這個兩種型別的物件都是對 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;
}
過程比較簡單,如下:
-
獲取類載入器
-
通過類載入器從所有
META-INF/spring.factories
檔案中獲取型別為type
的類名稱,這裡的 SpringFactoriesLoader 是 Spring 中的一個類 -
為上一步獲取到的所有類名稱建立對應的例項物件
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; }
-
通過
@Order
註解進行排序 -
返回排序後的
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](https://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;
}
整個啟動過程做了很多事情,主要過程如下:
-
建立 StopWatch 物件並啟動,主要用於統計當前方法執行過程的耗時
-
設定
java.awt.headless
屬性,和 AWT 相關,暫時忽略 -
呼叫
getRunListeners(..)
方法,初始化所有SpringApplicationRunListener
型別的物件,並全部封裝到SpringApplicationRunListeners
物件中 -
啟動所有的
SpringApplicationRunListener
監聽器,例如EventPublishingRunListener
會廣播 ApplicationEvent 應用正在啟動的事件,它裡面封裝了所有的ApplicationListener
物件,那麼此時就可以通過它們做一些初始化工作,進行擴充 -
建立一個 ApplicationArguments 應用引數物件,將
main(String[] args)
方法的args
引數封裝起來,便於後續使用 -
呼叫
prepareEnvironment(..)
方法,準備好當前應用 Environment 環境,這裡會載入出所有的配置資訊,包括application.yaml
和外部的屬性配置 -
呼叫
printBanner(..)
方法,列印 banner 內容 -
呼叫
createApplicationContext()
方法, 對context
(Spring 上下文)進行例項化,例如 Servlet(預設)會建立一個 AnnotationConfigServletWebServerApplicationContext 例項物件 -
獲取異常報告器,通過類載入器從
META-INF/spring.factories
檔案中獲取 SpringBootExceptionReporter 型別的類名稱,並進行例項化 -
呼叫
prepareContext(..)
方法,對 Spring 應用上下文做一些初始化工作,例如執行ApplicationContextInitializer#initialize(..)
方法 -
呼叫
refreshContext(..)
方法,重新整理 Spring 應用上下文,在這裡會完成所有 Spring Bean 的初始化,同時會初始化好 Servlet 容器,例如 Tomcat這一步涉及到 Spring IoC 的所有內容,參考 《死磕Spring之IoC篇 - Spring 應用上下文 ApplicationContext》
在
ServletWebServerApplicationContext#onRefresh()
方法中會建立一個 Servlet 容器(預設為 Tomcat),也就是當前 Spring Boot 應用所執行的 Web 環境 -
呼叫
afterRefresh(..)
方法,完成重新整理 Spring 應用上下文的後置操作,空實現,擴充套件點 -
停止 StopWatch,統計整個 Spring Boot 應用的啟動耗時,並列印
-
對所有的
SpringApplicationRunListener
監聽器進行廣播,釋出 ApplicationStartedEvent 應用已啟動事件,通常只有一個EventPublishingRunListener
物件 -
回撥 IoC 容器中所有 ApplicationRunner 和 CommandLineRunner 型別的啟動器,預設情況下沒有,先暫時忽略
-
對所有的
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
應用監聽器們,例如 ConfigFileApplicationListener 和 LoggingApplicationListener
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;
}
該過程如下:
-
根據 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(); } }
-
為
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 中 -
將當前
environment
的 MutablePropertySources 封裝成 SpringConfigurationPropertySources 新增到 MutablePropertySources 首部 -
對所有的 SpringApplicationRunListener 廣播 ApplicationEvent 應用環境已準備好的事件,這一步比較複雜,例如 Spring Cloud 的
BootstrapApplicationListener
監聽到該事件會建立一個 ApplicationContext 作為當前 Spring 應用上下文的父容器,同時會讀取bootstrap.yml
檔案的資訊這裡會有一個
ConfigFileApplicationListener
監聽到該事件然後去解析application.yml
等應用配置檔案的配置資訊 -
將
environment
繫結到當前 SpringApplication 上 -
如果不是自定義的 Environment 則需要根據 Web 應用型別轉換成對應 Environment 型別
-
再次進行上面第
3
步的處理過程,防止上面幾步對上面的 PropertySources 有修改 -
返回準備好的
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
- REACTIVE:
AnnotationConfigReactiveWebServerApplicationContext
- DEFAULT:
AnnotationConfigApplicationContext
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);
}
該過程如下:
-
為 Spring 應用上下文設定 Environment 環境
-
將一些工具 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()); } }
-
通知 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); } }
-
對所有 SpringApplicationRunListener 進行廣播,釋出 ApplicationContextInitializedEvent 初始化事件
-
向 Spring 應用上下文註冊
main(String[])
方法的引數 Bean 和 Banner 物件 -
獲取
primarySources
(例如你的啟動類)和sources
(例如 Spring Cloud 中的@BootstrapConfiguration
)源物件,沒有的話會丟擲異常 -
將上面的源物件載入成 BeanDefinition 並註冊
-
對所有的 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 應用的,流程大致如下:
- 從
META-INF/spring.factories
檔案中載入出相關 Class 物件,並進行例項化,例如ApplicationContextInitializer
,SpringApplicationRunListener
和ApplicationListener
物件 - 準備好當前 Spring 應用的 Environment 環境,這裡會解析
application.yml
以及外部配置 - 建立一個 ApplicationContext 應用上下文物件,預設 SERVLET 型別下建立
AnnotationConfigServletWebServerApplicationContext
物件 - 呼叫
AbstractApplication#refresh()
方法,重新整理 Spring 應用上下文,也就是之前一系列 Spring 相關的文章所講述的內容
整個過程有許多個擴充套件點是通過監聽器機制實現的,在不同階段廣播不同型別的事件,此時 ApplicationListener
就可進行相關的操作
在上面第 4
步中,SERVLET 應用型別下的 Spring 應用上下文會建立一個 Servlet 容器(預設為 Tomcat)
更多的細節在後續文章依次進行分析