該系列文章是筆者在學習 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 物件
environmentprivate 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)
更多的細節在后續文章依次進行分析
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/288758.html
標籤:其他
