所有文章
https://www.cnblogs.com/lay2017/p/11478237.html
啟動入口
本文是springboot啟動流程的第一篇,涉及的內容是SpringApplication這個對象的實例化過程。為什么從SpringApplication這個對象說起呢?我們先看一段很熟悉的代碼片段
@SpringBootApplication public class SpringBootLearnApplication { public static void main(String[] args) { SpringApplication.run(SpringBootLearnApplication.class, args); } }
springboot項目從一個main方法開始,main方法將會調用SpringApplication的run方法開始springboot的啟動流程。所以,本文即從構造SpringApplication對象開始。
我們跟進SpringApplication的run方法
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) { return run(new Class<?>[] { primarySource }, args); }
這是一個靜態方法,入參有兩個:
1)main方法所在的類,該類后續將被作為主要的資源來使用,比如通過該類獲取到basePackage;
2)main方法的命令行參數,命令行參數可以通過main傳入,也就意味着可以在springboot啟動的時候設置對應的參數,比如當前是dev環境、還是production環境等。
第2行代碼,run方法將調用另外一個內部run方法,並返回一個ConfigurableApplicationContext,預示着spring容器將在后續過程中創建。
跟進另一個run方法
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) { return new SpringApplication(primarySources).run(args); }
run方法中先是構造了一個SpringApplication實例對象,而后調用了SpringApplication的成員方法run,這個run方法將包含springboot啟動流程的核心邏輯。本文只討論SpringApplication的實例化過程。
構造函數
跟進SpringApplication的構造函數中
public SpringApplication(Class<?>... primarySources) { this(null, primarySources); }
構造函數調用了另外一個構造函數,繼續跟進
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) { // 設置資源加載器 this.resourceLoader = resourceLoader; // 設置主要資源類 this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); // 推斷當前應用的類型 this.webApplicationType = WebApplicationType.deduceFromClasspath(); // 設置ApplicationContext的初始化器 setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); // 設置Application監聽器 setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); // 推斷並設置主類 this.mainApplicationClass = deduceMainApplicationClass(); }
構造過程主要包含:
1)推斷當前應用類型
2)設置ApplicationContext初始化器、Application監聽器
3)根據堆棧來推斷當前main方法所在的主類
推斷當前應用類型
WebApplicationType是一個枚舉對象,枚舉了可能的應用類型
public enum WebApplicationType { /** * 非web應用類型,不啟動web容器 */ NONE, /** * 基於Servlet的web應用,將啟動Servlet的嵌入式web容器 */ SERVLET, /** * 基於reactive的web應用,將啟動reactive的嵌入式web容器 */ REACTIVE; // 省略... }
deduceFromClasspath方法將會推斷出當前應用屬於以上三個枚舉實例的哪一個,跟進方法
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; } // 如果Servlet或者ConfigurableWebApplicationContext不存在,那么就是非web應用 for (String className : SERVLET_INDICATOR_CLASSES) { if (!ClassUtils.isPresent(className, null)) { return WebApplicationType.NONE; } } // 否則都是Servlet的web應用 return WebApplicationType.SERVLET; }
推斷過程將根據類路徑中是否有指示性的類來判斷
設置ApplicationContext初始化器、Application監聽器
getSpringFactoriesInstances(ApplicationContextInitializer.class)
這個方法調用將會從META-INF/spring.factories配置文件中找到所有ApplicationContextInitializer接口對應的實現類配置,然后通過反射機制構造出對應的實例對象。
getSpringFactoriesInstances(ApplicationListener.class)
這個方法也是一樣的做法,將會獲取ApplicationListener接口的所有配置實例對象
有關於如何從spring.factories配置文件中獲取配置並構造出實例對象請看:spring.factories配置文件的工廠模式
根據堆棧來推斷當前main方法所在的主類
構造SpringApplication還有最后一步,推斷出main方法所在的主類。我們跟進deduceMainApplicationClass方法
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方法落在哪個棧幀上,並通過forName來獲取該類
總結
到這里本文就結束了,核心點就在於SpringApplication的實例化,可以看出最主要的就是做了應用類型的推斷,后面的Application創建、Environment創建也會基於該類型。