<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          SpringBoot執(zhí)行原理

          共 7016字,需瀏覽 15分鐘

           ·

          2020-12-24 00:46

          點擊上方藍(lán)色字體,選擇“標(biāo)星公眾號”

          優(yōu)質(zhì)文章,第一時間送達(dá)

          ? 作者?|??鄧曉暉

          來源 |? urlify.cn/2YfYBf

          66套java從入門到精通實戰(zhàn)課程分享

          一、執(zhí)行原理:

          每個Spring Boot項目都有一個主程序啟動類,在主程序啟動類中有一個啟動項目的main()方法, 在該方法中通過執(zhí)行SpringApplication.run()即可啟動整個Spring Boot程序。

          Q:

          那么SpringApplication.run()方法到底是如何做到啟動Spring Boot項目的呢?

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

          上述是一個SpringBoot的啟動類,進(jìn)入SpringApplication.run()方法

          如圖所示,進(jìn)入了run方法后,緊接著,調(diào)用了重載方法,重載方法做了兩件事:

          1. 實例化SpringApplication對象

          2. 調(diào)用run方法

          1. 實例化SpringApplication對象

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

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

          ?//設(shè)置應(yīng)用類型是SERVLET應(yīng)用(Spring?5之前的傳統(tǒng)MVC應(yīng)用)還是REACTIVE應(yīng)用(Spring?5開始出現(xiàn)的WebFlux交互式應(yīng)用)
          ?this.webApplicationType?=?WebApplicationType.deduceFromClasspath();

          ?//?設(shè)置初始化器(Initializer),最后會調(diào)用這些初始化器
          ?//所謂的初始化器就是org.springframework.context.ApplicationContextInitializer的實現(xiàn)類,在Spring上下文被刷新之前進(jìn)行初始化的操作
          ?setInitializers((Collection)?getSpringFactoriesInstances(ApplicationContextInitializer.class));

          ?//?設(shè)置監(jiān)聽器(Listener)
          ?setListeners((Collection)?getSpringFactoriesInstances(ApplicationListener.class));

          ?//?初始化?mainApplicationClass?屬性:用于推斷并設(shè)置項目main()方法啟動的主程序啟動類
          ?this.mainApplicationClass?=?deduceMainApplicationClass();
          }

          SpringApplication的構(gòu)造方法中,首先設(shè)置了一些參數(shù),然后做了5件事

          1.1 項目啟動類 SpringbootDemoApplication.class設(shè)置為屬性存儲起來

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

          給這個成員變量賦值,把傳入的primarySources進(jìn)行轉(zhuǎn)換,然后賦值,這個primarySources就是我們Springboot啟動類的Main方法中傳入的:

          1.2 設(shè)置應(yīng)用類型是SERVLET應(yīng)用(Spring 5之前的傳統(tǒng)MVC應(yīng)用)還是REACTIVE應(yīng)用

          this.webApplicationType?=?WebApplicationType.deduceFromClasspath();

          判斷當(dāng)前的web應(yīng)用類型是servlet應(yīng)用還是reactive應(yīng)用,那么如何判斷的?進(jìn)入.deduceFromClasspath()方法:

          1. 首先判斷類路徑下Reactive相關(guān)的class是否存在,如果存在就說明當(dāng)前應(yīng)用是內(nèi)嵌的 Reactive Web 應(yīng)用。例如說,Spring Webflux 。

          2. 判斷類路徑下是否存在Servlet類型的類。如果不存在,則返回NONE,表示當(dāng)前應(yīng)用是非內(nèi)嵌的 Web 應(yīng)用

          3. 否則,表示當(dāng)前應(yīng)用是內(nèi)嵌的 Servlet Web 應(yīng)用。例如說,Spring MVC 。

          1.3 設(shè)置初始化器(Initializer),最后會調(diào)用這些初始化器

          所謂的初始化器就是org.springframework.context.ApplicationContextInitializer的實現(xiàn)類,在Spring上下文被刷新之前進(jìn)行初始化的操作.

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

          這里傳入了一個ApplicationContextInitializer.class

          進(jìn)入getSpringFactoriesInstances()方法(下圖如果看不清請右鍵另存為):

          這段代碼主要做了如下幾件事:

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

          2. loadFactoryNames?調(diào)用了?loadSpringFactories方法

          3. loadSpringFactories方法做了如下的事:

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

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

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

          得到這兩個類后,把它們存入set去重,然后進(jìn)行實例化,然后排序,最終返回,到此初始化器已經(jīng)設(shè)置完成了。然后存入List> initializers,等待之后使用

          1.4 設(shè)置監(jiān)聽器(Listener)

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

          和1.3同理,也是通過調(diào)用getSpringFactoriesInstances,只不過傳遞的參數(shù)發(fā)生了改變。變成了ApplicationListener.class?,所以它就是在spring.factories中根據(jù)ApplicationListener.class找,然后實例化,然后返回存入Listeners中。

          1.5 初始化 mainApplicationClass 屬性

          用于推斷并設(shè)置項目main()方法啟動的主程序啟動類

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

          判斷哪個類執(zhí)行了main方法,然后返回。

          1.6 總結(jié)

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

          1. 項目啟動類 SpringbootDemoApplication.class設(shè)置為屬性存儲起來

          2. 設(shè)置應(yīng)用類型是SERVLET應(yīng)用(Spring 5之前的傳統(tǒng)MVC應(yīng)用)還是REACTIVE應(yīng)用(Spring 5開始出現(xiàn)的WebFlux交互式應(yīng)用)

          3. 設(shè)置初始化器(Initializer),最后會調(diào)用這些初始化器

          4. 設(shè)置監(jiān)聽器(Listener)

          5. 初始化 mainApplicationClass 屬性:用于推斷并設(shè)置項目main()方法啟動的主程序啟動類

          2. 調(diào)用run方法

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

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

          • 調(diào)用run方法

          進(jìn)入run方法:

          run方法大體上做了9件比較重要的事。

          2.1 獲取并啟動監(jiān)聽器

          SpringApplicationRunListeners?listeners?=?getRunListeners(args);
          listeners.starting();
          //args是啟動Spring應(yīng)用的命令行參數(shù),該參數(shù)可以在Spring應(yīng)用中被訪問。如:--server.port=9000
          ApplicationArguments?applicationArguments?=?new?DefaultApplicationArguments(args);

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

          那么本步驟就是通過getSpringFactoriesInstances()拿到了一個SpringApplicationRunListeners類型的監(jiān)聽器,然后調(diào)用.starting()啟動。

          2.2 項目運(yùn)行環(huán)境Environment的預(yù)配置

          創(chuàng)建并配置當(dāng)前SpringBoot應(yīng)用將要使用的Environment,并遍歷調(diào)用所有的SpringApplicationRunListener的environmentPrepared()方法

          ConfigurableEnvironment?environment?=?prepareEnvironment(listeners,?applicationArguments);

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

          進(jìn)入prepareEnvironment()方法:

          1. 查詢environment,有就返回,沒有的話創(chuàng)建后返回。

          2. 配置環(huán)境

            1. PropertySources:加載執(zhí)行的配置文件

            2. Profiles:多環(huán)境配置,針對不同的環(huán)境,加載不同的配置

          3. listeners環(huán)境準(zhǔn)備(就是廣播ApplicationEnvironmentPreparedEvent事件)。

          4. 將創(chuàng)建的環(huán)境綁定到SpringApplication對象上

          5. 是否是web環(huán)境,如果不是就轉(zhuǎn)換為標(biāo)準(zhǔn)環(huán)境

          6. 配置PropertySources對它自己的遞歸依賴

          7. 返回

          此時已經(jīng)拿到了ConfigurableEnvironment?環(huán)境對象,然后執(zhí)行configureIgnoreBeanInfo(environment),使其生效。

          2.3 創(chuàng)建Spring容器

          context?=?createApplicationContext();
          //?獲得異常報告器?SpringBootExceptionReporter?數(shù)組
          //這一步的邏輯和實例化初始化器和監(jiān)聽器的一樣,
          //?都是通過調(diào)用 getSpringFactoriesInstances 方法來獲取配置的異常類名稱并實例化所有的異常處理類。
          exceptionReporters?=?getSpringFactoriesInstances(
          ??????SpringBootExceptionReporter.class,
          ??????new?Class[]?{?ConfigurableApplicationContext.class?},?context);

          根據(jù)?webApplicationType?類型,獲得?ApplicationContext?類型,這里創(chuàng)建容器的類型 還是根據(jù)webApplicationType進(jìn)行判斷的,該類型為SERVLET類型,所以會通過反射裝載對應(yīng)的字節(jié)碼,也就是AnnotationConfigServletWebServerApplicationContext

          然后通過getSpringFactoriesInstances()獲得異常報告器。

          2.4 Spring容器前置處理

          這一步主要是在容器刷新之前的準(zhǔn)備動作。包含一個非常關(guān)鍵的操作:將啟動類注入容器,為后續(xù)開啟自動化配置奠定基礎(chǔ)。

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

          這塊會對整個上下文進(jìn)行一個預(yù)處理,比如觸發(fā)監(jiān)聽器的響應(yīng)事件、加載資源、設(shè)置上下文環(huán)境等等。

          2.5 刷新容器

          refreshContext(context);

          • IOC容器初始化

          • 向JVM運(yùn)行時注冊一個關(guān)機(jī)鉤子(函數(shù)),在JVM關(guān)機(jī)時關(guān)閉這個上下文,除非它當(dāng)時已經(jīng)關(guān)閉。(如果jvm變關(guān)閉了,當(dāng)前上下文對象也可以被關(guān)閉了)

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

          2.6 Spring容器后置處理

          afterRefresh(context, applicationArguments);

          擴(kuò)展接口,設(shè)計模式中的模板方法,默認(rèn)為空實現(xiàn)。
          如果有自定義需求,可以重寫該方法。比如打印一些啟動結(jié)束log,或者一些其它后置處理。

          2.7 發(fā)出結(jié)束執(zhí)行的事件通知

          listeners.started(context);

          2.8 執(zhí)行Runners運(yùn)行器

          callRunners(context, applicationArguments);

          用于調(diào)用項目中自定義的執(zhí)行器XxxRunner類,使得在項目啟動完成后立即執(zhí)行一些特定程序。

          Runner 運(yùn)行器用于在服務(wù)啟動時進(jìn)行一些業(yè)務(wù)初始化操作,這些操作只在服務(wù)啟動后執(zhí)行一次。

          Spring Boot提供了ApplicationRunnerCommandLineRunner兩種服務(wù)接口

          2.9 發(fā)布應(yīng)用上下文就緒事件

          listeners.running(context);

          表示在前面一切初始化啟動都沒有問題的情況下,使用運(yùn)行監(jiān)聽器SpringApplicationRunListener持續(xù)運(yùn)行配置好的應(yīng)用上下文ApplicationContext.

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

          2.10 返回容器

          return context;

          完成~

          總結(jié):

          1. 獲取并啟動監(jiān)聽器

          2. 項目運(yùn)行環(huán)境Environment的預(yù)配置

          3. 創(chuàng)建Spring容器

          4. Spring容器前置處理

          5. 刷新容器

          6. Spring容器后置處理

          7. 發(fā)出結(jié)束執(zhí)行的事件通知

          8. 執(zhí)行Runners運(yùn)行器

          9. 發(fā)布應(yīng)用上下文就緒事件

          10. 返回容器





          粉絲福利:Java從入門到入土學(xué)習(xí)路線圖

          ???

          ?長按上方微信二維碼?2 秒


          感謝點贊支持下哈?

          瀏覽 72
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  青青草视频免费在线看 | 欧美三级毛片 | 免费看一区二区三区 | 日本黄免费看 | 日韩国产无码1区2区3区4区 |