http://www.javashuo.com/article/p-uvudtich-bm.htmlhtml
本文是springboot啓動流程的第一篇,涉及的內容是SpringApplication這個對象的實例化過程。爲何從SpringApplication這個對象提及呢?咱們先看一段很熟悉的代碼片斷react
@SpringBootApplication public class SpringBootLearnApplication { public static void main(String[] args) { SpringApplication.run(SpringBootLearnApplication.class, args); } }
springboot項目從一個main方法開始,main方法將會調用SpringApplication的run方法開始springboot的啓動流程。因此,本文即從構造SpringApplication對象開始。web
咱們跟進SpringApplication的run方法spring
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) { return run(new Class<?>[] { primarySource }, args); }
這是一個靜態方法,入參有兩個:springboot
1)main方法所在的類,該類後續將被做爲主要的資源來使用,好比經過該類獲取到basePackage;函數
2)main方法的命令行參數,命令行參數能夠經過main傳入,也就意味着能夠在springboot啓動的時候設置對應的參數,好比當前是dev環境、仍是production環境等。this
第2行代碼,run方法將調用另一個內部run方法,並返回一個ConfigurableApplicationContext,預示着spring容器將在後續過程當中建立。spa
跟進另外一個run方法命令行
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) { return new SpringApplication(primarySources).run(args); }
run方法中先是構造了一個SpringApplication實例對象,然後調用了SpringApplication的成員方法run,這個run方法將包含springboot啓動流程的核心邏輯。本文只討論SpringApplication的實例化過程。code
跟進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; }
推斷過程將根據類路徑中是否有指示性的類來判斷
getSpringFactoriesInstances(ApplicationContextInitializer.class)
這個方法調用將會從META-INF/spring.factories配置文件中找到全部ApplicationContextInitializer接口對應的實現類配置,而後經過反射機制構造出對應的實例對象。
getSpringFactoriesInstances(ApplicationListener.class)
這個方法也是同樣的作法,將會獲取ApplicationListener接口的全部配置實例對象
有關於如何從spring.factories配置文件中獲取配置並構造出實例對象請看:spring.factories配置文件的工廠模式
構造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建立也會基於該類型。