服務啟動堪稱Spring原始碼設計的答案;
初學SpringBoot框架時,第一次啟動服務,直呼什麼鬼?只需要簡單的幾步設定,幾個核心的註解,就可以快速實現工程的搭建和執行;
雖然從Spring框架遷移到SpringBoot框架,在初期會有很多的不適應,但是更好用的框架會快速得到認可,從而成為主流的技術選型;
對於大多數的框架或者元件來說,如果使用起來越是簡便,那麼其內部的封裝策略就越是複雜;
比如在Spring框架更新到SpringBoot版本時,其用法的簡便與內部封裝的複雜性已經形成強烈的對比;再到SpringCloud微服務架構時,其封裝邏輯複雜到離譜;
對於伺服器端的開發來說,繞不開對Spring框架的深度學習,如果單純站在原始碼閱讀的角度,建議先熟讀SpringBoot啟動流程,然後再適當擴充套件其他原始碼塊;
首先聊一聊閱讀原始碼的基本思路,從一個極簡的案例開始,圍繞案例中的核心API作為切入點,通過對原始碼邏輯的斷點偵錯,從而體會其設計的原理;
閱讀SpringBoot的原始碼,可以從服務啟動方法作為切入點,然後不斷的分析啟動過程涉及到的核心API和設計原理,再基於具體的啟動紀錄檔去分析抽象的載入邏輯;
在看具體的原始碼之前,還需要說下分析思路,Spring專案中,要注意每個API所屬工程與層級,然後再去分析API之間關係,核心的構造、屬性、方法等;
在SpringBoot的啟動類中,有兩個核心的切入點,一個是類的構造方法,完成一列的初始化動作;一個是啟動方法,實現應用上下文的建立和裝載;
構造方法:
啟動方法:
需要說明的是,由於SpringBoot服務啟動過程涉及原始碼過多,所以上面的原始碼中只是羅列部分的核心切入點,然後圍繞這些關鍵流程展開,分析一些常見的原始碼設計;
另外說明一點,以下原始碼的核心版本:JDK-1.8
,spring-5.2.4
,spring-boot-2.2.5
,在不同的版本下原始碼會存在差異;
服務啟動時,根據應用型別判斷建立的上下文,此處啟動的是基於servlet的web應用,所以也依賴相應的web伺服器,預設為Tomcat;
啟動方法的核心在於對應用上下文的建立、準備、重新整理,應用上下文是一個十分抽象的描述,可以理解為應用執行的整體環境,其中涉及到資源載入,組態檔裝配,執行服務的管理等,後續的原始碼分析都圍繞該API展開;
ApplicationContext:應用上下文核心介面,在該介面中所有的方法都是唯讀模式,即只能通過Get方法進行存取;
ConfigurableApplicationContext:上下文設定擴充套件介面,提供了應用上下文的設定能力,生命週期的維護,以及在關閉之後的相關資源釋放;
AbstractApplicationContext:上下文介面抽象實現,核心的API,對應用上下文中的公共能力做了實現;
ConfigurableWebApplicationContext:Web應用上下文設定擴充套件介面,提供了Web應用的上下文設定能力;
WebServerApplicationContext:Web服務上下文,建立並管理Web應用的伺服器,在該流程中嵌入的是Tomcat服務;
根據應用上下文幾個核心的API設計,體會Spring原始碼的設計思路,從頂級的介面開始,不斷向下擴充套件並且新增方法,理解抽象實現類的邏輯,以及服務執行時所依賴的具體API;
什麼是資源,可以是各種型別的檔案和設定,位元組輸入流的轉換,也可以是URL資源定位,Spring框架在執行的過程中,需要依賴Resource介面實現對底層資源的存取;
Resource:資源描述的頂級介面,提供了一系列的方法,繼承InputStreamSource介面,支援將資源轉換為流的形式操作;
AbstractResource:資源存取的抽象實現類,這裡的設計原理與AbstractApplicationContext類似,提供資源存取方法的基礎實現;
ResourceLoader:資源載入的封裝介面,應用下文需要依賴該介面實現資源的獲取與存取;
針對不同應用場景需求,Resource介面的實現類有如下幾個:FileSystemResource檔案系統資源,ClassPathResource類路徑下資源,InputStreamResource輸入流資源等;
對於Property和Environment原始碼設計體系,參考上述的原始碼模組,在思路上是相似的,此處不多描述;
應用程式的屬性和環境涉及到的引數描述非常多,比較直接的手段是通過System類中的方法輸出,至於資訊如何載入,在StandardEnvironment類中提供了方法,可以斷點檢視;
基於Spring框架的應用程式中,由Spring容器負責建立,裝配,設定屬性,進而管理整個生命週期的物件,稱為Bean物件;Bean的生命週期非常複雜,過程大致如下:範例化,屬性載入,初始化前後管理,銷燬;
BeanFactory:工廠類,Spring框架的核心能力,Bean容器的頂級介面,提供了一系列Bean物件的存取方法,是IOC思想和依賴注入的基礎支撐;
ConfigurableBeanFactory:Bean容器可設定化介面,該擴充套件介面只是為了允許框架內部的隨插即用和存取bean工廠的設定方法;
AbstractBeanFactory:Bean管理的抽象實現類,可以檢視其內部doGetBean方法,提供Bean範例物件的獲取邏輯,如果無法獲取則執行建立邏輯;
初次啟動SpringBoot工程時,最大的疑問就是可見Tomcat啟動紀錄檔,但是沒有顯式的做伺服器裝配,直接啟動JAR包即可,這在流程上簡化了一大步;
WebServer:Web應用伺服器介面,比如常用的Tomcat,Jetty,Netty等,根據應用型別選擇,只提供了啟動、停止、獲取埠三個方法,通過WebServerApplicationContext與應用上下文相關聯;
TomcatWebServer:SpringBoot框架管理內建Tomcat服務的核心類,對Tomcat生命週期的管理提供了一層包裝;
Tomcat:Apache元件中輕量級Tomcat啟動器,提供了Tomcat基礎設定,比如預設的Port和HostName,以及生命週期管理的方法,TomcatWebServer類中呼叫的就是該API中的具體方法;
事件驅動模型是複雜流程中的常用解耦手段,即通過事件傳送和監聽兩個拆解動作,實現流程的分步執行,這在SpringBoot啟動流程和上下文裝載中更是發揮的淋漓盡致;
ApplicationEvent:應用事件基礎抽象類,繼承自JDK中EventObject類,具體事件會繼承該類,內部宣告了事件源和發生時間兩個核心屬性;
ApplicationEventMulticaster:應用事件廣播的頂級介面,可以將指定的應用事件廣播給適合的監聽器;
SimpleApplicationEventMulticaster:應用事件廣播介面的簡單實現,可以斷點該類的multicastEvent方法,檢視廣播時應用事件和其相應的監聽器;
ApplicationListener:應用事件監聽器介面,繼承自JDK中EventListener介面,Spring中擴充套件了多種具體的事件監聽器,以實現各種不同的場景需求,比如最常見的ConfigFileApplicationListener組態檔監聽器;
SpringBoot工程中,組態檔的管理策略非常複雜,有內部程式執行載入設定,也有外部整合的元件設定,當然最核心的就是工程的自定義設定;
ConfigFileApplicationListener.Loader:組態檔監聽器的內部類,實現對工程中的設定源載入,其核心邏輯在Loader.load方法中實現,具體邏輯由相關的實現類完成;
PropertySourceLoader:設定載入的策略介面,在Spring工程中支援多種型別的檔案設定,比如yml、yaml、properties、xml,需要通過檔案的擴充套件名選擇相應的載入實現類;
YamlPropertySourceLoader:載入.yml
或者.yaml
型別的檔案,SpringBoot工程中常用的組態檔型別,最終轉換成Name和Value的屬性源集合,即通過PropertySource抽象類來描述;
Spring框架的強大之處還在於能夠和其他元件進行簡單快速的整合,比如常用的資料庫、快取、訊息佇列等各種型別的元件,分析內部的整合邏輯,會發現很多原理上的相似性,尤其在SpringBoot框架中,約定大於設定;
DataSourceAutoConfiguration:SpringBoot工程中資料庫的自動化設定類,在設定中Hikari是預設選擇的連線池,也是號稱速度最快的;
DataSourceProperties:資料來源設定相關的基礎類,在DataSourceConfiguration設定類中,會基於引數去建立資料來源物件;
HikariDataSource:Hikari連線池元件中的資料來源API,描述資料來源的具體資訊,例如設定、連線池、狀態等,具體的資料庫連線邏輯是在該元件內部完成的;
基於SpringBoot整合資料庫的原理,可以擴充套件性的看看:Redis元件的RedisAutoConfiguration設定類;Kafka元件的KafkaAutoConfiguration設定類,Elasticsearch元件的RestClientAutoConfiguration設定類,在設計原理上都有異曲同工之妙;
寫在最後
從個人經驗來看,想要閱讀Spring框架的原始碼設計,需要基於應用流程先構建一個大的輪廓結構,理解設計中的常用策略和原理,然後再深入單個模組的細節邏輯,這樣容易找到閱讀節奏;
本文並沒有涉及原始碼中過多的細節邏輯,只是從服務啟動作為切入點,整理與開發關聯性較為直接的原始碼模組,描述個人對於Spring原始碼閱讀的基礎思路。
應用倉庫:
https://gitee.com/cicadasmile/butte-flyer-parent
元件封裝:
https://gitee.com/cicadasmile/butte-frame-parent