SpringBoot成長記2:從HelloWorld開始分析SpringBoot

SpringBoot成長記2:從HelloWorld開始分析SpringBoot

上一節我們提到過,認識一個新技術的時候,通常是從一個入門的HelloWorld開始,之後閱讀它的一些入門文件和書籍、影片,從而掌握它的基本使用。

這一節我就來帶大家從HelloWorld開始,先摸清楚SpringBoot的核心脈絡,之後再來逐步分析透徹SpringBoot,從而精通它。

從搭建HelloWorld入口開始分析SpringBoot

首先我們從官方的文件中搭建出一個2。2。2 版本的SpringBoot,增加了兩個starter,mybatis-plus-boot-starter、spring-boot-starter-web,使用Maven進行專案和依賴管理,配置一個本地的mysql。相信這個對你們來說,都比較簡單,我就不一一進行贅述了。

經過上面的基本搭建,你就會有類似一個下面的一個SpringBoot HelloWorld級別 的入口。

package org。mfm。learn。springboot;import org。mybatis。spring。annotation。MapperScan;import org。springframework。boot。SpringApplication;import org。springframework。boot。autoconfigure。SpringBootApplication;@MapperScan(“org。mfm。learn。springboot。mapper”)@SpringBootApplicationpublic class LearnSpringBootApplication { public static void main(String[] args) { SpringApplication。run(LearnSpringBootApplication。class, args); }}

透過上一節你知道SpringBoot定義了一個SpringApplication的web應用啟動流程,入口透過一個java -jar的命令,執行main函式啟動一個JVM程序,執行內部的tomcat監聽一個預設8080的埠,提供web服務。

SpringBoot成長記2:從HelloWorld開始分析SpringBoot

整個過程中第一個最關鍵的就是SpringBoot定義的SpringApplication,我們一起先來看下它是怎麼建立new的。

SpringApplication的建立時核心元件圖

SpringApplication的建立時的程式碼分析

在上面的示例程式碼中,main方法執行了 SpringApplication的run方法,如下:

public static ConfigurableApplicationContext run(Class<?> primarySource, String。。。 args) { return run(new Class<?>[] { primarySource }, args);}public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) { return new SpringApplication(primarySources)。run(args);}

run方法核心入參就是main函式所在類+main函式args的引數,之後就直接建立了一個SpringApplication物件。讓我們一起來看看這個SpringBoot定義的概念怎麼建立的,建立時的核心元件又有哪些呢?

public SpringApplication(Class<?>。。。 primarySources) { this(null, primarySources);}public SpringApplication(ResourceLoader resourceLoader, Class<?>。。。 primarySources) { this。resourceLoader = resourceLoader; Assert。notNull(primarySources, “PrimarySources must not be null”); this。primarySources = new LinkedHashSet<>(Arrays。asList(primarySources)); this。webApplicationType = WebApplicationType。deduceFromClasspath(); setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer。class)); setListeners((Collection) getSpringFactoriesInstances(ApplicationListener。class)); this。mainApplicationClass = deduceMainApplicationClass();}

SpringApplication的建立時核心脈絡

SpringApplication的建立核心脈絡比較簡單:

1)ResourceLoader 指明資源載入器,這個暫時不清楚是啥東西,預設是個null。

2)webApplicationType 推斷當前web應用型別,透過一個deduceFromClasspath方法推斷出的。

3)之後設定了setInitializers、setListeners兩個列表,分別是一堆Initializer和Listener,都是透過getSpringFactoriesInstances方法獲取的。

4)透過primarySources、mainApplicationClass記錄了啟動主要資源類,也就是之前HelloWorld中的LearnSpringBootApplication.class。

上面是我第一次看這個類的一個脈絡後,腦中得到的結果。

SpringBoot成長記2:從HelloWorld開始分析SpringBoot

你第一次看這裡,肯定什麼都不清楚,不知道每個變數有什麼用,是幹嘛的,沒關係的,

第一次,你只要熟悉它的脈絡就可以。知道這裡設定了兩個集合變數Initializer和Listener,可以設定esourceLoader ,標記了一些型別和類,這就夠了。

之後你有時間,再挨個去了解每個變數或者元件的作用就可以了,這個不還是

先脈絡後細節的思想

,是吧?

SpringApplication的建立時的細節分析

你可以慢慢拆解上面的每一步,單獨看看每一個元件大體是作什麼的,這個就是細節的研究,可以一步一步來。

你可以研究下ResourceLoader 是個啥? 你可以看它的類註解後可以發現,這個

ResourceLoader 類負責使用ClassLoader載入ClassPath下的class和各種配置檔案的。

(如果你不知道JVM的ClassLoader機制,主要載入什麼,可以自己去baidu、google瞭解下)。這裡你可以進一步思考下,它設計成了一個介面,可以實現不同的類載入器來載入資源。

webApplicationType 如何被推斷的?就是根據幾個靜態變數定義的類全限定名稱,根據classPath下是否存在對應的類,來推斷出型別,使用了web-starter。預設推斷出為Servlet型別的應用。

至於primarySources、mainApplicationClass這個兩個變數記錄了LearnSpringBootApplication。class, 大體是為了之後掃描自動配置等考慮的,表示從什麼包名的哪一個類下啟動的。

最後兩個集合變數Initializer和Listener如何設定的,這塊比較值得研究下。

基本原理是透過ClassLoader掃描了classPath下所有META-INF/spring。factories這個目錄中的檔案,透過指定的factoryType,也就是介面名稱,獲取對應的所有實現類,並且例項化成物件,返回成一個list列表。

比如factoryType=ApplicationContextInitializer 就返回這個介面在META-INF/spring。factories定義的所有的實現類,並例項化為一個列表List ApplicationContextInitializer 。

ApplicationListener同理,獲取到了List ApplicationListener一個集合。

這裡面其實有很多細節,使用了類載入器、快取機制,反射機制等,有興趣的同學可以仔細研究下。

這裡以我們

抓大放小思想

,概括成一句話:

透過工具方法透過classLoader獲取classPath指定位置某個介面所有實現類的例項物件列表。

這裡獲取的是ApplicationContextInitializer、ApplicationListener這兩個介面的例項物件列表。

細節中可以學到知識,脈絡中一樣可以學到知識,這個思想你一定要慢慢有。抓大放小的意思,更多的是讓你知道重點和關鍵點,而不是讓你丟棄細節,這兩者並不衝突,這個一定要注意。

最後這裡細節分析,畫一個簡單元件圖小結下:

SpringBoot成長記2:從HelloWorld開始分析SpringBoot

SpringApplication Run方法的脈絡分析

熟悉了SpringApplication 的建立,接著我們該分析它的run方法了。

其實之前一節,我們介紹過SpringApplication 的啟動流程。就是高度概括了run方法的核心脈絡,run方法的核心其實核心就是下圖藍色的部分:

SpringBoot成長記2:從HelloWorld開始分析SpringBoot

run方法脈絡可以主要概括為:

1)自動裝配配置

2)Spring容器的建立

3)web容器啟動(Tomcat的啟動)

然而在run方法的執行過程,肯定不會這麼簡單,過程中還摻雜了很多雜七雜八的邏輯,其中有意思的擴充套件點,也有值得吐槽的坑。這是每個框架都會有的優勢劣勢吧。我們先大體摸一下run方法的脈絡,給大家介紹幾個術語,不然之後可能會看不懂程式碼細節。

SpringApplication Run方法的脈絡進一步分析

要想進一步分析run方法的脈絡,首先需要熟悉幾個術語,就有點像DDD的通用語言似的,懂了這些語言,理解SpringBoot和Spring才會更得心應手。

術語普及Context/BeanFactory/Environment

ConfigurableApplicationContext

,容器通常稱為ApplicationContext或者BeanFactory,context也簡稱為容器。ApplicationContext包裝了BeanFactory,封裝更高階的API而已。

ConfigurableEnvironment

,是配置檔案的抽象,有關什麼properties或者yml等配置檔案的key-value值,都會封裝成這個類的某個實現類。

熟悉了這些術語後,我們看一起看下SpringApplication 的run方法程式碼。

public ConfigurableApplicationContext run(String。。。 args) { StopWatch stopWatch = new StopWatch(); stopWatch。start(); ConfigurableApplicationContext context = null; Collection exceptionReporters = new ArrayList<>(); configureHeadlessProperty(); SpringApplicationRunListeners listeners = getRunListeners(args); listeners。starting(); try { ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); configureIgnoreBeanInfo(environment); Banner printedBanner = printBanner(environment); context = createApplicationContext(); exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter。class, new Class[] { ConfigurableApplicationContext。class }, context); prepareContext(context, environment, listeners, applicationArguments, printedBanner); refreshContext(context); afterRefresh(context, applicationArguments); stopWatch。stop(); if (this。logStartupInfo) { new StartupInfoLogger(this。mainApplicationClass)。logStarted(getApplicationLog(), stopWatch); } listeners。started(context); callRunners(context, applicationArguments); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, listeners); throw new IllegalStateException(ex); } try { listeners。running(context); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, null); throw new IllegalStateException(ex); } return context; }

上面的程式碼,主要就是執行了一堆方法,可以從方法名字看出,都是圍繞Context、Environment這些術語。也就是圍繞容器和配置檔案組織的邏輯。

在整個Spring容器的建立,重新整理,重新整理之後穿插了很多邏輯。

另外,SpringBoot整個run方法中有幾個很關鍵擴充套件點,設計SpringApplicationRunListeners、Runners等擴充套件入口。容器建立、重新整理等也要各自的擴充套件點,對容器的增強擴充套件,如beanFactoryPostProcessor,對Bean的增加擴充套件,如beanPostProcessor。然而這些都是後話了。

我直接用一張圖給大家概括了,上面run方法脈絡:

(*黑色是直觀的看出來的擴充套件邏輯,白色是run方法每個方法的字面理解,只是每一步有很多擴充套件點和做的事情比較多,讓你感覺會有點雲裡霧裡的。藍色部分,概括了核心邏輯。也就是SpringBoot啟動,說白了我們核心就是要找到這三點:自動裝配配置、Spring容器的建立、web容器啟動。)

SpringBoot成長記2:從HelloWorld開始分析SpringBoot

這時候你一定要學會

抓大放小的思想

,之後帶著這3個關鍵步驟,去理解SpringBoot,其他的實現可以單獨來研究分析它的設計思路,比如各個擴充套件點的設計是如何考慮的,我們可以參考借鑑哪一些。這才是學習SpringBoot最最該學習的。

概括下就是,

當一個技術看著比較複雜時,你應該順著核心脈絡理解原理,學習各個細節的亮點設計思想。

不要陷入某一個細節,多思考才最重要。大家一定要記住這一點,在後續的成長記中,我會逐步帶大家體驗這一點的。

小結

好了,簡單小結下。

主要思想學習了:

1)先脈絡後細節的思想,抓大放小的思想,排除不重要的,分析最主要的。

2)細節中可以學到知識,脈絡中一樣可以學到知識,這個思想你一定要慢慢有。抓大放小的意思,更多的是讓你知道重點和關鍵點,而不是讓你丟棄細節,這兩者並不衝突,這個一定要注意。

3)多思考才最重要。順著核心脈絡理解原理,學習各個細節的亮點設計思想,千萬不能陷入知識本身。

主要知識學習了:

今天我們主要看了下SpringApplication的建立,它的核心元件有哪些,建立後執行的run方法,到底做了些什麼,脈絡是怎麼樣的。

熟悉了這些脈絡,剩下的就簡單了,逐步分析每個細節,看看每個細節有些值得我們學習的點,又有哪一些不太適合的點。

我們下期再見!

本文由部落格一文多發平臺 OpenWrite 釋出!