Spring Boot構造流程淺析
什麼是Spring Boot的構造流程?即初始化類SpringApplication的例項化過程。
我們都知道Spring Boot專案的啟動非常簡單,只需要執行入口類的main方法即可,如下:
@SpringBootApplication
public class UserApplication {
public static void main(String[] args) {
SpringApplication.run(UserApplication.class);
}
}
可以看到main方法中只有一句程式碼:SpringApplication.run(xxxx.class),我們進入這個run方法,如下:
public static ConfigurableApplicationContext run(Class<?> primarySource,
String... args) {
return run(new Class<?>[] { primarySource }, args);
}
public static ConfigurableApplicationContext run(Class<?>[] primarySources,
String[] args) {
return new SpringApplication(primarySources).run(args);
}
仔細看這句程式碼:new SpringApplication(primarySources).run(args),發現居然new了一個SpringApplication去呼叫另外一個run方法,其實這句程式碼包含了兩個非常重要的內容,即Spring Boot的構造流程和執行流程,構造流程是指SpringApplication類的例項化過程,執行流程是指SpringApplication類的例項化物件呼叫run方法完成整個專案的初始化和啟動的過程,而本文的重點是前者。
到這一步,我們基本能夠明白一件事:入口類中主要通過SpringApplication的run方法進行SpringApplication類的例項化操作,然後這個例項化物件再去呼叫另外一個更牛逼的run方法來完成整個專案的初始化和啟動。
下面我們將正式進入SpringApplication類的例項化過程的探析,首先看一下SpringApplication兩個構造方法的原始碼:
public SpringApplication(Class<?>... primarySources) {
this(null, primarySources);
}
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
//賦值成員變數resourceLoader
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
//賦值成員變數primarySources
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//推斷Web應用型別
this.webApplicationType = WebApplicationType.deduceFromClasspath();
//載入並初始化ApplicationContextInitializer及相關實現類
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
//載入並初始化ApplicationListener及相關實現類
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
//推斷main方法
this.mainApplicationClass = deduceMainApplicationClass();
}
第一個構造方法其實是直接呼叫了第二個核心構造方法,核心業務邏輯便在其中,下面將詳細講解核心構造方法涉及到的業務邏輯。
一、賦值成員變數:resourceLoader、primarySources
可以看到核心構造方法包含兩個引數:ResourceLoader和Class<?>...primarySources。其中前者為資源載入的介面,在Spring Boot啟動時可以通過它來指定需要載入的檔案路徑;後者預設傳入的是Spring Boot入口類,作為專案的引導類。構造方法的第一個步驟非常簡單,就是將傳進來的這兩個引數賦值給對應的成員變數。
二、推斷Web應用型別
接著是呼叫了WebApplicationType的deduceFromClasspath方法來推斷Web應用型別,我們首先進入WebApplicationType類:
public enum WebApplicationType {
//非Web應用型別
NONE,
//基於Servlet的Web應用型別
SERVLET,
//基於Reactive的Web應用型別
REACTIVE;
...
}
可以知道WebApplicationType類只是一個列舉型別,包括:非Web應用型別,基於Servlet的Web應用型別,基於Reactive的Web應用型別。另外可以在WebApplicationType類中看到剛才提到的推斷方法deduceFromClasspath(),推斷方法以及用於推斷的常量原始碼如下。
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() {
//如果類路徑中包含DispatcherHandler且不包含DispatcherServlet,也不包含ServletContainer,則為Reactive應用
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) {
//如果類路徑下Servlet或者ConfigurableWebApplicationContext任何一個不存在,則為非Web應用
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
//否則都是Servlet應用型別
return WebApplicationType.SERVLET;
}
isPresent方法可以通過反射機制建立出指定類,根據在建立過程中是否丟擲異常來判斷指定類是否存在。分析該推斷方法可以得知核心邏輯是通過ClassUtils.isPresent()來判斷類路徑classpath下是否存在指定類,從而判斷出應用型別。推斷邏輯如下:
- 如果類路徑中包含DispatcherHandler且不包含DispatcherServlet,也不包含ServletContainer,則為Reactive應用。
- 如果類路徑下Servlet或者ConfigurableWebApplicationContext任何一個不存在,則為非Web應用。
- 其他情況則為Servlet應用型別。
三、載入並初始化ApplicationContextInitializer及相關實現類
ApplicationContextInitializer的作用:它是Spring IOC容器提供的一個回撥介面,通常用於應用程式上下文進行程式設計初始化的Web應用程式中。
在完成Web應用型別推斷之後,接著便開始ApplicationContextInitializer的載入工作,這裡將分成兩個步驟:即獲取相關例項和設定例項。對應的方法為:getSpringFactoriesInstances()和setInitializers()。我們首先來看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) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// Use names and ensure unique to protect against duplicates
//載入META-INF/spring.factories檔案中的對應配置
Set<String> names = new LinkedHashSet<>(
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
//建立例項
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
//排序
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
可以看到這裡是通過SpringFactoriesLoader.loadFactoryNames()方法來載入META-INF/spring.factories檔案中的對應配置,該檔案的內容如下:
看到這裡大家可能會覺得似曾相識,沒錯,這裡載入META-INF/spring.factories檔案的過程在之前講解Spring Boot自動配置時已經提到過,spring.factories檔案中的內容會被解析到Map<String,List<String>>中,最後loadFactoryNames通過傳遞過來的class名稱作為Key從Map中獲得該類的配置列表,而這個class名稱就是type的值,追溯type的值發現其實就是一開始傳入的ApplicationContextInitializer.class。
上面通過SpringFactoriesLoader.loadFactoryNames()方法獲取到了ApplicationContextInitializer介面具體的實現類的全限定名,下面就要呼叫createSpringFactoriesInstances()方法來建立這些例項,再將這些例項經過排序後返回,至此獲取相關例項結束,下一步是設定例項。
下面看設定例項的方法:setInitializers()
public void setInitializers(
Collection<? extends ApplicationContextInitializer<?>> initializers) {
this.initializers = new ArrayList<>();
this.initializers.addAll(initializers);
}
可以看到設定例項的步驟很簡單,將SpringApplication的initializers成員變數例項化為一個新的List,然後將剛才獲取到的例項放入其中即可。至此載入並初始化ApplicationContextInitializer及相關實現類結束。
四、載入並初始化ApplicationListener及相關實現類
ApplicationListener經常用於監聽容器初始化完成之後,執行資料載入、初始化快取等任務。
ApplicationListener的整個載入流程與ApplicationContextInitializer的載入流程完全相同,這裡就不再重複。
五、推斷main方法
最後一步是通過deduceMainApplicationClass()推斷main方法,來看原始碼:
private Class<?> deduceMainApplicationClass() {
try {
//通過新建一個執行時異常來獲得棧陣列
StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
//遍歷棧陣列
for (StackTraceElement stackTraceElement : stackTrace) {
//匹配出第一個main方法
if ("main".equals(stackTraceElement.getMethodName())) {
//返回該類的class物件
return Class.forName(stackTraceElement.getClassName());
}
}
}
catch (ClassNotFoundException ex) {
// Swallow and continue
}
return null;
}
該方法首先通過一個執行時異常來獲得棧陣列,接著遍歷這個陣列尋找出第一個“main”方法,如果找到了這個main方法,就通過返回這個類的物件,並最終將這個物件賦值給SpringApplication的成員變數mainApplicationClass。至此,SpringApplication的例項化過程結束!
現將SpringApplication類的例項化過程涉及到的核心操作總結如下:
- 賦值成員變數:resourceLoader、primarySources
- 推斷Web應用型別
- 載入並初始化ApplicationContextInitializer及相關實現類
- 載入並初始化ApplicationListener及相關實現類
- 推斷main方法
相關文章
- MyBatis(十一):MyBatis架構流程淺析MyBatis架構
- 淺析Spring之IoCSpring
- Spring Boot啟動流程Spring Boot
- 淺析 Spring 的IOC容器Spring
- 淺析Spring Security 核心元件Spring元件
- Spring-Security-OAuth2架構及原始碼淺析SpringOAuth架構原始碼
- Flutter Android 端啟動流程淺析FlutterAndroid
- Spring-IOC原始碼淺析Spring原始碼
- 淺析Spring的IoC和DISpring
- Spring Boot啟動流程簡述Spring Boot
- 電商架構淺析架構
- Webpack基本架構淺析Web架構
- 淺析 Laravel Session 元件的工作流程LaravelSession元件
- Flutter佈局和繪製流程淺析Flutter
- Spring MVC實現過程淺析SpringMVC
- 深入淺出,Spring 框架和 Spring Boot 的故事框架Spring Boot
- iOS MVC、MVVM、MVP架構模式淺淺析iOSMVCMVVMMVP架構模式
- Linux系統——架構淺析Linux架構
- OpenAI的結構化淺析OpenAI
- 淺析Vite本地構建原理Vite
- 淺析Kubernetes架構之workqueue架構
- SpringBoot淺析——專案構建Spring Boot
- 五、Spring Boot整合Spring Security之認證流程2Spring Boot
- Spring入門系列:淺析知識點Spring
- Spring Boot(三):Spring Boot中的事件的使用 與Spring Boot啟動流程(Event 事件 和 Listeners監聽器)Spring Boot事件
- Spring Boot 應用程式啟動流程分析Spring Boot
- 淺析HDFS架構和設計架構
- 微服務架構專案淺析微服務架構
- 淺析okHttp3的網路請求流程HTTP
- 課時41:魔法方法:構造和析構
- 淺析Spring Framework框架容器啟動過程SpringFramework框架
- 【Spring淺析】一、 BeanFactory 有啥可說的?SpringBean
- spring boot構建restful服務Spring BootREST
- 淺析Redis基礎資料結構Redis資料結構
- 淺析spring——IOC 之 分析 Bean 的生命週期SpringBean
- Spring Boot第二彈,配置檔案怎麼造?Spring Boot
- 淺析MyBatis(一):由一個快速案例剖析MyBatis的整體架構與執行流程MyBatis架構
- 【C++】 46_繼承中的構造與析構C++繼承