SpringBoot啟動流程分析(一):SpringApplication類初始化過程

SpringBoot系列文章簡介

SpringBoot源碼閱讀輔助篇:

  Spring IoC容器與應用上下文的設計與實現

SpringBoot啟動流程源碼分析:

  1. SpringBoot啟動流程分析(一):SpringApplication類初始化過程
  2. SpringBoot啟動流程分析(二):SpringApplication的run方法
  3. SpringBoot啟動流程分析(三):SpringApplication的run方法之prepareContext()方法
  4. SpringBoot啟動流程分析(四):IoC容器的初始化過程
  5. SpringBoot啟動流程分析(五):SpringBoot自動裝配原理實現
  6. SpringBoot啟動流程分析(六):IoC容器依賴注入

筆者註釋版Spring Framework與SpringBoot源碼git傳送門:請不要吝嗇小星星

  1. spring-framework-5.0.8.RELEASE
  2. SpringBoot-2.0.4.RELEASE

一、SpringApplication初始化過程

  1.1、SpringBoot項目的mian函數

  常規的這個主類如下圖所示,我們一般會這樣去寫。

 

  在這個類中需要關注的是

  • @SpringBootApplication
  • SpringApplication.run()

  關於 @SpringBootApplication 註解,在後面分析SpringBoot自動裝配的章節會展開去分析。

  本章節中我們需要關注的就是 SpringApplication.run() 方法。

  查看run()方法的實現,如下面代碼所示,我們發現其實其首先是創建了 SpringApplication 的實例,然後調用了 SpringApplication 的run()方法,那本章我們關注的就是 SpringApplication 創建實例的過程。

/**
     * Static helper that can be used to run a {@link SpringApplication} from the
     * specified sources using default settings and user supplied arguments.
     *
     * @param primarySources the primary sources to load
     * @param args           the application arguments (usually passed from a Java main method)
     * @return the running {@link ApplicationContext}
     */
    public static ConfigurableApplicationContext run(Class<?>[] primarySources,
                                                     String[] args) {
        return new SpringApplication(primarySources).run(args);
    }

   

  1.2、 SpringApplication() 構造方法

  繼續查看源碼, SpringApplication 實例化過程,首先是進入但參數的構造方法,最終回來到兩個參數的構造方法。

 1 public SpringApplication(Class<?>... primarySources) {
 2     this(null, primarySources);
 3 }
 4 
 5 @SuppressWarnings({"unchecked", "rawtypes"})
 6 public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
 7     this.resourceLoader = resourceLoader;
 8     Assert.notNull(primarySources, "PrimarySources must not be null");
 9     this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
10     //推斷應用類型,後面會根據類型初始化對應的環境。常用的一般都是servlet環境
11     this.webApplicationType = deduceWebApplicationType();//2.2.1
12     //初始化classpath下 META-INF/spring.factories中已配置的ApplicationContextInitializer
13     setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));//2.2.2
14     //初始化classpath下所有已配置的 ApplicationListener
15     setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));//2.2.3
16     //根據調用棧,推斷出 main 方法的類名
17     this.mainApplicationClass = deduceMainApplicationClass();
18 }

 

   1.2.1、deduceWebApplicationType();該方法推斷應用的類型。 SERVLET REACTIVE NONE 

 1 //常量值
 2 private static final String[] WEB_ENVIRONMENT_CLASSES = {"javax.servlet.Servlet",
 3             "org.springframework.web.context.ConfigurableWebApplicationContext"};
 4 
 5 private static final String REACTIVE_WEB_ENVIRONMENT_CLASS = "org.springframework."
 6         + "web.reactive.DispatcherHandler";
 7 
 8 private static final String MVC_WEB_ENVIRONMENT_CLASS = "org.springframework."
 9         + "web.servlet.DispatcherServlet";
10 
11 private static final String JERSEY_WEB_ENVIRONMENT_CLASS = "org.glassfish.jersey.server.ResourceConfig";
12 
13 /**
14  * 判斷 應用的類型
15  * NONE: 應用程序不是web應用,也不應該用web服務器去啟動
16  * SERVLET: 應用程序應作為基於servlet的web應用程序運行,並應啟動嵌入式servlet web(tomcat)服務器。
17  * REACTIVE: 應用程序應作為 reactive web應用程序運行,並應啟動嵌入式 reactive web服務器。
18  * @return
19  */
20 private WebApplicationType deduceWebApplicationType() {
21     //classpath下必須存在org.springframework.web.reactive.DispatcherHandler
22     if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)
23             && !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)
24             && !ClassUtils.isPresent(JERSEY_WEB_ENVIRONMENT_CLASS, null)) {
25         return WebApplicationType.REACTIVE;
26     }
27     for (String className : WEB_ENVIRONMENT_CLASSES) {
28         if (!ClassUtils.isPresent(className, null)) {
29             return WebApplicationType.NONE;
30         }
31     }
32     //classpath環境下存在javax.servlet.Servlet或者org.springframework.web.context.ConfigurableWebApplicationContext
33     return WebApplicationType.SERVLET;
34 }

  返回類型是WebApplicationType的枚舉類型, WebApplicationType 有三個枚舉,三個枚舉的解釋如其中註釋

  具體的判斷邏輯如下:

  • WebApplicationType.REACTIVE  classpath下存在org.springframework.web.reactive.DispatcherHandler

  • WebApplicationType.SERVLET classpath下存在javax.servlet.Servlet或者org.springframework.web.context.ConfigurableWebApplicationContext

  • WebApplicationType.NONE 不滿足以上條件。

  

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

  初始化classpath下 META-INF/spring.factories中已配置的ApplicationContextInitializer。

 1 private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
 2     return getSpringFactoriesInstances(type, new Class<?>[]{});
 3 }
 4 
 5 /**
 6  * 通過指定的classloader 從META-INF/spring.factories獲取指定的Spring的工廠實例
 7  * @param type
 8  * @param parameterTypes
 9  * @param args
10  * @param <T>
11  * @return
12  */
13 private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
14                                                       Class<?>[] parameterTypes, Object... args) {
15     ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
16     // Use names and ensure unique to protect against duplicates
17     //通過指定的classLoader從 META-INF/spring.factories 的資源文件中,
18     //讀取 key 為 type.getName() 的 value
19     Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
20     //創建Spring工廠實例
21     List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
22             classLoader, args, names);
23     //對Spring工廠實例排序(org.springframework.core.annotation.Order註解指定的順序)
24     AnnotationAwareOrderComparator.sort(instances);
25     return instances;
26 }

 

  看看 getSpringFactoriesInstances 都幹了什麼,看源碼,有一個方法很重要 loadFactoryNames() 這個方法很重要,這個方法是spring-core中提供的從META-INF/spring.factories中獲取指定的類(key)的同一入口方法。

在這裏,獲取的是key為 org.springframework.context.ApplicationContextInitializer 的類。

  debug看看都獲取到了哪些

 

  上面說了,是從classpath下 META-INF/spring.factories中獲取,我們驗證一下:

  發現在上圖所示的兩個工程中找到了debug中看到的6條結果。 ApplicationContextInitializer 是Spring框架的類, 這個類的主要目的就是在   ConfigurableApplicationContext 調用refresh()方法之前,回調這個類的initialize方法。通過  ConfigurableApplicationContext 的實例獲取容器的環境Environment,從而實現對配置文件的修改完善等工作。

  關於怎麼實現自定義的 ApplicationContextInitializer 請看我的另一篇專門介紹該類的博客。

 

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

  初始化classpath下 META-INF/spring.factories中已配置的 ApplicationListener。

   ApplicationListener 的加載過程和上面的 ApplicationContextInitializer 類的加載過程是一樣的。不多說了,至於 ApplicationListener 是spring的事件監聽器,典型的觀察者模式,通過 ApplicationEvent 類和 ApplicationListener 接口,可以實現對spring容器全生命周期的監聽,當然也可以自定義監聽事件。為了梳理springboot的啟動流程在這裏先不說這個了。後面有時間的話再介紹。

   關於ApplicationContextInitializer的詳細介紹請看<SpringBoot之ApplicationContextInitializer的理解和使用>

 二、總結

  關於 SpringApplication 類的構造過程,到這裏我們就梳理完了。縱觀 SpringApplication 類的實例化過程,我們可以看到,合理的利用該類,我們能在spring容器創建之前做一些預備工作,和定製化的需求。

比如,自定義SpringBoot的Banner,比如自定義事件監聽器,再比如在容器refresh之前通過自定義 ApplicationContextInitializer 修改配置一些配置或者獲取指定的bean都是可以的。。。

  下一節開始分析SpringBoot容器的構建過程,也就是那個大家多少都看過的run();方法。

   

  原創不易,轉載請註明出處。

  如有錯誤的地方還請留言指正。

【精選推薦文章】

智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

想知道網站建置、網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計及後台網頁設計

帶您來看台北網站建置台北網頁設計,各種案例分享

廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

您可能也會喜歡…