SpringBoot執行原理

1、執行原理:

每一個Spring Boot項目都有一個主程序啓動類,在主程序啓動類中有一個啓動項目的main()方法, 在該方法中經過執行SpringApplication.run()便可啓動整個Spring Boot程序。react

Q:web

那麼SpringApplication.run()方法究竟是如何作到啓動Spring Boot項目的呢?spring

@SpringBootApplication  //可以掃描Spring組件並自動配置SpringBoot
public class Springboot01DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(Springboot01DemoApplication.class, args);
    }
}

上述是一個SpringBoot的啓動類,進入SpringApplication.run()方法設計模式

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

如圖所示,進入了run方法後,緊接着,調用了重載方法,重載方法作了兩件事:數組

  1. 實例化SpringApplication對象app

  2. 調用run方法jvm

1. 實例化SpringApplication對象

public SpringApplication(Class<?>... primarySources) {
 this(null, primarySources);
}
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
 //......設置了一些參數....這裏省略,下面是重點
    //......設置了一些參數....這裏省略,下面是重點
    //......設置了一些參數....這裏省略,下面是重點

 //項目啓動類 SpringbootDemoApplication.class設置爲屬性存儲起來
 this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));

 //設置應用類型是SERVLET應用(Spring 5以前的傳統MVC應用)仍是REACTIVE應用(Spring 5開始出現的WebFlux交互式應用)
 this.webApplicationType = WebApplicationType.deduceFromClasspath();

 // 設置初始化器(Initializer),最後會調用這些初始化器
 //所謂的初始化器就是org.springframework.context.ApplicationContextInitializer的實現類,在Spring上下文被刷新以前進行初始化的操做
 setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));

 // 設置監聽器(Listener)
 setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

 // 初始化 mainApplicationClass 屬性:用於推斷並設置項目main()方法啓動的主程序啓動類
 this.mainApplicationClass = deduceMainApplicationClass();
}

SpringApplication的構造方法中,首先設置了一些參數,而後作了5件事:ide

1.1 項目啓動類 SpringbootDemoApplication.class設置爲屬性存儲起來

this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));

給這個成員變量賦值,把傳入的primarySources進行轉換,而後賦值,這個primarySources就是咱們Springboot啓動類的Main方法中傳入的:
watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=函數

1.2 設置應用類型是SERVLET應用(Spring 5以前的傳統MVC應用)仍是REACTIVE應用

this.webApplicationType = WebApplicationType.deduceFromClasspath();

判斷當前的web應用類型是servlet應用仍是reactive應用,那麼如何判斷的?進入.deduceFromClasspath()方法:
watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=字體

  1. 首先判斷類路徑下Reactive相關的class是否存在,若是存在就說明當前應用是內嵌的 Reactive Web 應用。例如說,Spring Webflux 。

  2. 判斷類路徑下是否存在Servlet類型的類。若是不存在,則返回NONE,表示當前應用是非內嵌的 Web 應用。

  3. 不然,表示當前應用是內嵌的 Servlet Web 應用。例如說,Spring MVC 。

1.3 設置初始化器(Initializer),最後會調用這些初始化器

所謂的初始化器就是org.springframework.context.ApplicationContextInitializer的實現類,在Spring上下文被刷新以前進行初始化的操做.

setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));

這裏傳入了一個ApplicationContextInitializer.class

進入getSpringFactoriesInstances()方法(下圖若是看不清請右鍵另存爲):
watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

這段代碼主要作了以下幾件事:

  1. SpringFactoriesLoader.loadFactoryNames(type, classLoader)
    這裏的type就是剛纔傳入的,ApplicationContextInitializer.class

  2. loadFactoryNames 調用了 loadSpringFactories方法

  3. loadSpringFactories方法作了以下的事:

    Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
    LinkedMultiValueMap result = new LinkedMultiValueMap();

    判斷classLoader是否爲空,若是不爲空加載META-INF下的spring.factories,如上圖所示,根據傳入的參數值(ApplicationContextInitializer.class)的類型,在spring.factories中進行查找,根據當前傳入的類型找到兩個類,這兩個類就是初始化器:

    org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
    org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener

獲得這兩個類後,把它們存入set去重,而後進行實例化,而後排序,最終返回,到此初始化器已經設置完成了。而後存入List<ApplicationContextInitializer<?>> initializers,等待以後使用

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

1.4 設置監聽器(Listener)

setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

和1.3同理,也是經過調用getSpringFactoriesInstances,只不過傳遞的參數發生了改變。變成了ApplicationListener.class ,因此它就是在spring.factories中根據ApplicationListener.class找,而後實例化,而後返回存入Listeners中。

1.5 初始化 mainApplicationClass 屬性

用於推斷並設置項目main()方法啓動的主程序啓動類

this.mainApplicationClass = deduceMainApplicationClass();
 private Class<?> deduceMainApplicationClass() {
  try {
      // 得到當前 StackTraceElement 數組
   StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
   // 判斷哪一個執行了 main 方法
   for (StackTraceElement stackTraceElement : stackTrace) {
    if ("main".equals(stackTraceElement.getMethodName())) {
     return Class.forName(stackTraceElement.getClassName());
    }
   }
  } catch (ClassNotFoundException ex) {
   // Swallow and continue
  }
  return null;
 }

判斷哪一個類執行了main方法,而後返回。

1.6 總結

實例化SpringApplication對象作了哪些事?

  1. 項目啓動類 SpringbootDemoApplication.class設置爲屬性存儲起來

  2. 設置應用類型是SERVLET應用(Spring 5以前的傳統MVC應用)仍是REACTIVE應用(Spring 5開始出現的WebFlux交互式應用)

  3. 設置初始化器(Initializer),最後會調用這些初始化器

  4. 設置監聽器(Listener)

  5. 初始化 mainApplicationClass 屬性:用於推斷並設置項目main()方法啓動的主程序啓動類

2. 調用run方法

回憶一下,在SpringBoot啓動類的Main方法中,執行了SpringApplication.run(Main方法所在的當前類.class, args);,這個方法主要作了兩件事:

  • 實例化SpringApplication對象 (已上述)

  • 調用run方法

進入run方法:
watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

run方法大致上作了9件比較重要的事。

2.1 獲取並啓動監聽器

SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
//args是啓動Spring應用的命令行參數,該參數能夠在Spring應用中被訪問。如:--server.port=9000
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

它其實仍是經過getSpringFactoriesInstances()這個方法來獲取,這個方法已經很熟悉了, 1.3,1.4都使用到了,再也不贅述。

那麼本步驟就是經過getSpringFactoriesInstances()拿到了一個SpringApplicationRunListeners類型的監聽器,而後調用.starting()啓動。

2.2 項目運行環境Environment的預配置

建立並配置當前SpringBoot應用將要使用的Environment,並遍歷調用全部的SpringApplicationRunListener的environmentPrepared()方法

ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);

configureIgnoreBeanInfo(environment);
// 準備Banner打印器 - 就是啓動Spring Boot的時候打印在console上的ASCII藝術字體
Banner printedBanner = printBanner(environment);

進入prepareEnvironment()方法:
watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

  1. 查詢environment,有就返回,沒有的話建立後返回。

  2. 配置環境

    1. PropertySources:加載執行的配置文件

    2. Profiles:多環境配置,針對不一樣的環境,加載不一樣的配置

  3. listeners環境準備(就是廣播ApplicationEnvironmentPreparedEvent事件)。

  4. 將建立的環境綁定到SpringApplication對象上

  5. 是不是web環境,若是不是就轉換爲標準環境

  6. 配置PropertySources對它本身的遞歸依賴

  7. 返回

此時已經拿到了ConfigurableEnvironment 環境對象,而後執行configureIgnoreBeanInfo(environment),使其生效。

2.3 建立Spring容器

context = createApplicationContext();
// 得到異常報告器 SpringBootExceptionReporter 數組
//這一步的邏輯和實例化初始化器和監聽器的同樣,
// 都是經過調用 getSpringFactoriesInstances 方法來獲取配置的異常類名稱並實例化全部的異常處理類。
exceptionReporters = getSpringFactoriesInstances(
      SpringBootExceptionReporter.class,
      new Class[] { ConfigurableApplicationContext.class }, context);

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

根據 webApplicationType 類型,得到 ApplicationContext 類型,這裏建立容器的類型 仍是根據webApplicationType進行判斷的,該類型爲SERVLET類型,因此會經過反射裝載對應的字節碼,也就是AnnotationConfigServletWebServerApplicationContext。

而後經過getSpringFactoriesInstances()得到異常報告器。

2.4 Spring容器前置處理

這一步主要是在容器刷新以前的準備動做。包含一個很是關鍵的操做:將啓動類注入容器,爲後續開啓自動化配置奠基基礎。

prepareContext(context, environment, listeners, applicationArguments,
      printedBanner);

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

這塊會對整個上下文進行一個預處理,好比觸發監聽器的響應事件、加載資源、設置上下文環境等等。

2.5 刷新容器

refreshContext(context);

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

  • IOC容器初始化

  • 向JVM運行時註冊一個關機鉤子(函數),在JVM關機時關閉這個上下文,除非它當時已經關閉。(若是jvm變關閉了,當前上下文對象也能夠被關閉了)

//TODO refresh方法在springioc章節中會有詳細說明(挖個坑- - )。

2.6 Spring容器後置處理

afterRefresh(context, applicationArguments);

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

擴展接口,設計模式中的模板方法,默認爲空實現。
若是有自定義需求,能夠重寫該方法。好比打印一些啓動結束log,或者一些其它後置處理。

2.7 發出結束執行的事件通知

listeners.started(context);

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

2.8 執行Runners運行器

callRunners(context, applicationArguments);

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

用於調用項目中自定義的執行器XxxRunner類,使得在項目啓動完成後當即執行一些特定程序。

Runner 運行器用於在服務啓動時進行一些業務初始化操做,這些操做只在服務啓動後執行一次。

Spring Boot提供了ApplicationRunner和CommandLineRunner兩種服務接口

2.9 發佈應用上下文就緒事件

listeners.running(context);

表示在前面一切初始化啓動都沒有問題的狀況下,使用運行監聽器SpringApplicationRunListener持續運行配置好的應用上下文ApplicationContext.

這樣整個Spring Boot項目就正式啓動完成了。

2.10 返回容器

return context;

完成~

總結:

  1. 獲取並啓動監聽器

  2. 項目運行環境Environment的預配置

  3. 建立Spring容器

  4. Spring容器前置處理

  5. 刷新容器

  6. Spring容器後置處理

  7. 發出結束執行的事件通知

  8. 執行Runners運行器

  9. 發佈應用上下文就緒事件

  10. 返回容器

相關文章
相關標籤/搜索