<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>

          Spring boot 2.5.x 啟動(dòng)流程源碼分析

          共 12817字,需瀏覽 26分鐘

           ·

          2021-09-14 01:15

          公眾號(hào)關(guān)注 “GitHub今日熱榜
          設(shè)為 “星標(biāo)”,帶你挖掘更多開(kāi)發(fā)神器!







          我想知道為什么 spring boot 啟動(dòng)就一行代碼,直接完成了那么多功能,今天就進(jìn)去看個(gè)究竟,順便記錄一下。


          第一個(gè) run 方法


          要想往進(jìn)去看,入口少不了,就是你了,這看著人畜無(wú)害的一行代碼,直接點(diǎn)進(jìn)去。



          然后便來(lái)到了這里,根據(jù)入?yún)⒅苯涌催@個(gè) run() ,發(fā)現(xiàn)這個(gè) run() 里就 new 了這個(gè) SpringApplication(primarySources) 的匿名對(duì)象,然后執(zhí)行了 run。



          new SpringApplication()


          那么就先來(lái)看看這個(gè) SpringApplication 的構(gòu)造方法吧。



          public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
              this.resourceLoader = resourceLoader;
              Assert.notNull(primarySources, "PrimarySources must not be null");
              //用LinkedHashSet將主類(lèi)給保存了起來(lái)
              this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
              //對(duì)項(xiàng)目的類(lèi)型做一個(gè)判斷,WebApplicationType枚舉類(lèi)中有三種,后邊這個(gè)方法就是一個(gè)判斷方法
              this.webApplicationType = WebApplicationType.deduceFromClasspath();
              //這里將對(duì)象注冊(cè)表初始化,然后保存起來(lái)
              this.bootstrapRegistryInitializers = getBootstrapRegistryInitializersFromSpringFactories();
              //這個(gè)是要通過(guò) SPI 機(jī)制去掃描 META-INF/spring.factories ,加載 ApplicationContextInitializer 接口的實(shí)例,這個(gè)類(lèi)當(dāng)springboot上下文Context初始化完成后會(huì)調(diào)用
              setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
              //同上,這個(gè) ApplicationListener 類(lèi)當(dāng)springboot啟動(dòng)時(shí)事件change后都會(huì)觸發(fā)
              setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
              //堆棧跟蹤判斷方法是否是 main
              this.mainApplicationClass = deduceMainApplicationClass();
            }


          簡(jiǎn)單說(shuō)一下 SPI 機(jī)制


          我對(duì)此機(jī)制的概念理解:提供給服務(wù)提供廠商與擴(kuò)展框架功能的開(kāi)發(fā)者使用的接口,約定如下:


          • 在META-INF/services/目錄中創(chuàng)建以接口全限定名命名的文件該文件內(nèi)容為Api具體實(shí)現(xiàn)類(lèi)的全限定名

          • 使用ServiceLoader類(lèi)動(dòng)態(tài)加載META-INF中的實(shí)現(xiàn)類(lèi)

          • 如SPI的實(shí)現(xiàn)類(lèi)為Jar則需要放在主程序classPath中

          • Api具體實(shí)現(xiàn)類(lèi)必須有一個(gè)不帶參數(shù)的構(gòu)造方法


          判斷項(xiàng)目類(lèi)型的那個(gè)方法




          對(duì)象注冊(cè)表初始化那個(gè)方法


          這里可以再往進(jìn)去看看。



          看看這個(gè)類(lèi) Bootstrapper.class,上邊 lambda 表達(dá)式里用的接口和方法就是這里,再可以看看這個(gè)入?yún)⑹莻€(gè)啥這個(gè) BootstrapRegistry 類(lèi)。



          可以發(fā)現(xiàn),這個(gè)類(lèi)里出現(xiàn)了熟悉的東西,單例和原的枚舉值。



          如何使用 SPI 機(jī)制掃描 META-INF/spring.factories 這個(gè)文件,并且加載接口實(shí)例


          這里我們可以自己實(shí)現(xiàn)一下 ApplicationContextInitializer.class 和 ApplicationListener.class 這倆個(gè)接口的實(shí)現(xiàn)類(lèi)。




          將這兩個(gè)實(shí)現(xiàn)類(lèi)寫(xiě)出來(lái)以后,然后在 resourse 目錄下寫(xiě)一個(gè) META-INF/spring.factories ,如上圖所示,然后配置以下自己實(shí)現(xiàn)的那兩個(gè)類(lèi),如下:


          org.springframework.context.ApplicationContextInitializer=\
          hasaki_w_c.demo.listener.MyApplicationContextInitializer

          org.springframework.context.ApplicationListener=\
          hasaki_w_c.demo.listener.MyApplicationListener


          這樣,就把我們自己寫(xiě)的接口實(shí)現(xiàn)類(lèi)集成到 spring boot 中去了,然后從 new SpringApplication() 那一段構(gòu)造代碼中的 getSpringFactoriesInstances() 這個(gè)方法進(jìn)去



          然后再進(jìn)到這個(gè)方法里邊,入?yún)鬟M(jìn)來(lái)的那兩個(gè)類(lèi)都會(huì)走這里的這兩個(gè)方法,所以我們直接在這個(gè) loadSpringFactories 這個(gè)方法里 debug



          然后打斷點(diǎn),看一下是不是添加進(jìn)去了,斷點(diǎn)的地方就打在返回值里



          然后調(diào)試啟動(dòng)這個(gè)項(xiàng)目,然后我這里把 result 的關(guān)于自己實(shí)現(xiàn)的那倆個(gè)接口拿出來(lái)監(jiān)視了,查看其里邊的值,會(huì)發(fā)現(xiàn),自己實(shí)現(xiàn)的那兩個(gè)接口也被添加了進(jìn)去。



          然后從控制臺(tái)也可以直觀的看到,自己實(shí)現(xiàn)的兩個(gè)接口和方法都被打印了出來(lái)。



          然后稍微詳細(xì)地看了一下 loadSpringFactories 這個(gè)方法,大概就是先使用 classLoader.getResources() 將 spring.factories 中的實(shí)現(xiàn)類(lèi)加載到Enumeration 接口中,接口如下:



          這個(gè)接口是有兩個(gè)方法,一個(gè)判斷里邊還有沒(méi)有元素了,另一個(gè)是返回下一個(gè)元素。

          然后通過(guò)兩三次的類(lèi)型轉(zhuǎn)換,轉(zhuǎn)換成 Properties 對(duì)象(Properties 類(lèi)繼承了Hashtable),然后遍歷,取到這個(gè)properties 的 key,然后通過(guò) StringUtils 里邊有個(gè)把逗號(hào)分隔的列表直接轉(zhuǎn)化成 String 數(shù)組的那個(gè)方法獲取到 properties 的 value,然后將這些key和value加入到返回值中,最后用包含唯一元素的不可修改列表替換所有列表,最后返回這個(gè)Map類(lèi)型的返回值;這里就完成了這兩個(gè)接口實(shí)例的設(shè)置。


          堆棧跟蹤判斷


          new SpringApplication() 的最后一行,就是將堆棧跟蹤返回值保存,堆棧跟蹤方法中通過(guò)判斷每一個(gè)元素的方法是不是equals “main”,如果是則通過(guò)Class.forName 返回這個(gè)元素的 class 對(duì)象,不是則返回 null。



          到這里這個(gè) SpringApplication 的方法就看完了。


          第二個(gè) run 方法


          有興趣的可以都點(diǎn)進(jìn)去看一看,我這里就不一一截圖了。


          public ConfigurableApplicationContext run(String... args) {
              //啟動(dòng)一個(gè) StopWatch 計(jì)時(shí)器(spring 5.2以后,運(yùn)行時(shí)間就以納秒進(jìn)行跟蹤了)
              StopWatch stopWatch = new StopWatch();
              //計(jì)時(shí)器啟動(dòng)
              stopWatch.start();
              DefaultBootstrapContext bootstrapContext = createBootstrapContext();
              ConfigurableApplicationContext context = null;
              //設(shè)置環(huán)境變量
              configureHeadlessProperty();
              //獲取事件監(jiān)聽(tīng)器SpringApplicationRunListener類(lèi)型
              SpringApplicationRunListeners listeners = getRunListeners(args);
              //入?yún)⑹巧舷挛膶?duì)象和前邊new SpringApplication最后那個(gè)堆棧跟蹤的返回值,這里就啟動(dòng)了監(jiān)聽(tīng)器
              listeners.starting(bootstrapContext, this.mainApplicationClass);
              try {
                //然后把參數(shù)封裝成提供運(yùn)行SpringApplication 的參數(shù)的訪問(wèn)的這個(gè)接口對(duì)象,通過(guò)這個(gè)接口的默認(rèn)實(shí)現(xiàn)類(lèi)進(jìn)行的
                ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
                //然后把環(huán)境和上下文以及上邊它剛剛封裝好的那個(gè)參數(shù)訪問(wèn)對(duì)象綁定
                ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
                //對(duì)環(huán)境的值判斷,以及設(shè)置
                configureIgnoreBeanInfo(environment);
                //然后打印 banner (就是那個(gè)啟動(dòng)后的控制臺(tái)比較大的那個(gè)spring boot)(終于直到那個(gè)圖形哪里來(lái)的了)
                Banner printedBanner = printBanner(environment);
                //創(chuàng)建上下文,根據(jù)項(xiàng)目類(lèi)型創(chuàng)建上下文,這個(gè)創(chuàng)建的方法里是要用到new 時(shí)候那個(gè) WebApplicationType
                context = createApplicationContext();
                //保存使用ApplicationStartup來(lái)標(biāo)記應(yīng)用程序啟動(dòng)期間的步驟,以及記錄一些收集到的執(zhí)行上下文或其處理時(shí)間的數(shù)據(jù)
                context.setApplicationStartup(this.applicationStartup);
                //準(zhǔn)備上下文,將前邊的幾個(gè)變量全丟進(jìn)這個(gè)方法的參數(shù)里
                prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
                //這個(gè)就是非常重要的東西了,是spring 的啟動(dòng)代碼,這里邊掃描并初始化 bean 了,然后 spring boot 內(nèi)嵌的web 容器也在這里邊被啟動(dòng),需要進(jìn)去細(xì)看了。
                refreshContext(context);
                //進(jìn)去看了以下發(fā)現(xiàn)啥也沒(méi)干
                afterRefresh(context, applicationArguments);
                //停止計(jì)時(shí)器
                stopWatch.stop();
                if (this.logStartupInfo) {
                  //啟動(dòng)時(shí)記錄程序的信息,并打印日志
                  new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
                }
                //執(zhí)行ApplicationRunListeners中的started()方法
                listeners.started(context);
                //執(zhí)行兩個(gè)Runner(ApplicationRunner和CommandLineRunner)
                callRunners(context, applicationArguments);
              }
              catch (Throwable ex) {
                //處理運(yùn)行失敗
                handleRunFailure(context, ex, listeners);
                throw new IllegalStateException(ex);
              }

              try {
                //執(zhí)行ApplicationRunListeners中的started()方法
                listeners.running(context);
              }
              catch (Throwable ex) {
                //處理運(yùn)行失敗
                handleRunFailure(context, ex, null);
                throw new IllegalStateException(ex);
              }
              return context;
            }


          refreshContext() 方法


          點(diǎn)進(jìn)去看看



          發(fā)現(xiàn)有一個(gè) refresh 方法,再點(diǎn)進(jìn)去看看



          然后又是一個(gè) refresh 方法,再干進(jìn)去



          找到這個(gè)方法的實(shí)現(xiàn),是一個(gè)抽象類(lèi)里邊的實(shí)現(xiàn)方法,發(fā)現(xiàn)這里就是 spring 包下的類(lèi)了,這個(gè)就是 spring 的啟動(dòng)類(lèi)了,容器就在這里初始化,每一行代碼也都有注釋?zhuān)a附上



          public void refresh() throws BeansException, IllegalStateException {
              synchronized (this.startupShutdownMonitor) {
                StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");

                // Prepare this context for refreshing.
                prepareRefresh();

                // Tell the subclass to refresh the internal bean factory.
                ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

                // Prepare the bean factory for use in this context.
                prepareBeanFactory(beanFactory);

                try {
                  // Allows post-processing of the bean factory in context subclasses.
                  postProcessBeanFactory(beanFactory);

                  StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
                  // Invoke factory processors registered as beans in the context.
                  invokeBeanFactoryPostProcessors(beanFactory);

                  // Register bean processors that intercept bean creation.
                  registerBeanPostProcessors(beanFactory);
                  beanPostProcess.end();

                  // Initialize message source for this context.
                  initMessageSource();

                  // Initialize event multicaster for this context.
                  initApplicationEventMulticaster();

                  // Initialize other special beans in specific context subclasses.
                  //這個(gè)重點(diǎn)看一下
                  onRefresh();

                  // Check for listener beans and register them.
                  registerListeners();

                  // Instantiate all remaining (non-lazy-init) singletons.
                  finishBeanFactoryInitialization(beanFactory);

                  // Last step: publish corresponding event.
                  finishRefresh();
                }

                catch (BeansException ex) {
                  if (logger.isWarnEnabled()) {
                    logger.warn("Exception encountered during context initialization - " +
                        "cancelling refresh attempt: " + ex);
                  }

                  // Destroy already created singletons to avoid dangling resources.
                  destroyBeans();

                  // Reset 'active' flag.
                  cancelRefresh(ex);

                  // Propagate exception to caller.
                  throw ex;
                }

                finally {
                  // Reset common introspection caches in Spring's core, since we
                  // might not ever need metadata for singleton beans anymore...
                  resetCommonCaches();
                  contextRefresh.end();
                }
              }
            }


          大概就是有這么些功能:


          設(shè)置上下文狀態(tài),獲取一些屬性,獲取并配置新的BeanFactory,實(shí)例化并調(diào)用所有注冊(cè)的beanFactory后置處理器,實(shí)例化和注冊(cè)beanFactory中擴(kuò)展了被注解修飾了的那些類(lèi)的bean,初始化事件廣播器,這里重點(diǎn)看一下onRefresh()這個(gè)方法,找到這個(gè)重寫(xiě)的方法,然后再繼續(xù)往里看,這時(shí)候就可以發(fā)現(xiàn)有個(gè)getWebServer()的接口方法了,然后它的實(shí)現(xiàn)類(lèi)就是tomcat、jetty了,內(nèi)嵌的 tomcat 也啟動(dòng)了,然后注冊(cè)監(jiān)聽(tīng)器,實(shí)例化所有剩余的(非懶加載)單例,最后finishRefresh,清除上下文資源緩存,初始化上下文的生命周期處理器。


          onRefresh() 方法


          這里看一下這個(gè)方法,從 spring 的 refresh() 中點(diǎn)進(jìn)去,是抽象類(lèi)的方法,然后找到它的重寫(xiě)方法,這里由于我們一般是 web 工程,所以找 springboot 包下的這個(gè)方法



          然后再進(jìn)去這個(gè) createWebServer ,它的名字告訴我 web 容器就從這里初始化的,直接點(diǎn)進(jìn)去看看



          先看一下這個(gè) getWebServerFactory() 方法,這個(gè)方法可以直到選擇了哪種類(lèi)型的 web 容器,點(diǎn)進(jìn)去在下圖地方打個(gè)斷點(diǎn)調(diào)試一下看看,可以看到熟悉的tomcat 了,這時(shí)候回到上邊 createWebServer() ,可以看到后邊這個(gè)工廠調(diào)用了 getWebServer() 這個(gè)方法。



          點(diǎn)進(jìn)去這個(gè) getWebServer() ,查看它的實(shí)現(xiàn)類(lèi),這里有三個(gè)實(shí)現(xiàn)類(lèi),



          終于發(fā)現(xiàn) web 容器在哪初始化的了,看一下 tomcat 的,明顯看出是在初始化設(shè)置參數(shù)的值。


          這里 web 容器就成功啟動(dòng)了,然后再就是執(zhí)行 onRefresh() 方法之后的那幾個(gè)方法了,再一步一步倒回去看看,好像大概的流程都看完了,從最初的 run,到最后跑到 spring 里,回去在再把第二個(gè) run 跑完,整個(gè) spring boot 的啟動(dòng)就完成了。


          此次對(duì) spring boot 框架的源碼學(xué)習(xí),讓我對(duì)它如何啟動(dòng)有了一個(gè)較為清晰的認(rèn)識(shí),雖還可能有遺漏,但大概流程應(yīng)該沒(méi)跑偏。


          出處:blog.csdn.net/qq_45456859/article/details/120142517










          關(guān)注GitHub今日熱榜,專(zhuān)注挖掘好用的開(kāi)發(fā)工具,致力于分享優(yōu)質(zhì)高效的工具、資源、插件等,助力開(kāi)發(fā)者成長(zhǎng)!







          點(diǎn)個(gè)在看,你最好看


          瀏覽 62
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  三级色图| 国产麻豆成人作品 | 免费操B视频 | 欧美乱伦第一页 | 亚洲人成色777777精品音频 |