天天看點

《Spring 手撸專欄》第 2 章:初顯身手,運用設計模式,實作 Bean 的定義、注冊、擷取

目錄

一、前言

二、目标

三、設計

四、實作

1. 工程結構

2. beandefinition 定義

3. 單例注冊接口定義和實作

4. 抽象類定義模闆方法(abstractbeanfactory)

5. 執行個體化bean類(abstractautowirecapablebeanfactory)

6. 核心類實作(defaultsingletonbeanregistry)

五、測試

1. 事先準備

2. 測試用例

3. 測試結果

六、總結

你是否能提前預見複雜内容的設計問題?

講道理,無論産品功能是否複雜,都有很大一部分程式員會寫出一堆 if...else 來完成開發并順利上線。這主要是原因沒法預見目前的需求,發展是否長遠、流量是否龐大、疊代是否迅速,是以在被催促上線的情況,不寫 if...else 是不可能的!

那你說,既然 if...else 實作的這麼快,還考慮資料結構、算法邏輯、設計模式、系統架構嗎?當然這基本要看你的項目在可預見下能活多久,如果一個項目至少存活一年,并且在這一年中又會不斷的的疊代。就像;你做了一個營銷優惠券系統,在各種條件下發放各種類型的券,如果在最開始沒有考慮好系統設計和架構模式,那麼當活動頻發、流量暴增、需求疊代下、最後你可能會挂在系統事故上!

我們在把系統設計的視角聚焦到具體代碼實作上,你會有什麼手段來實作你想要的設計模式呢?其實編碼方式主要依托于:接口定義、類實作接口、抽象類實作接口、繼承類、繼承抽象類,而這些操作方式可以很好的隔離開每個類的基礎功能、通用功能和業務功能,當類的職責清晰後,你的整個設計也會變得容易擴充和疊代。

接下來在本章節繼續完善 spring bean 容器架構的功能開發,在這個開發過程中會用到較多的接口、類、抽象類,它們之間會有類的實作、類的繼承。可以仔細參考這部分内容的開發實作,雖然并不會很複雜,但這種設計思路是完全可以複用到我們自己的業務系統開發中的。

在上一章節 《小試牛刀,實作一個簡單的bean容器》 我們初步依照 spring bean 容器的概念,實作了一個粗糙版本的代碼實作。那麼本章節我們需要結合已實作的 spring bean 容器進行功能完善,實作 bean 容器關于 bean 對象的注冊和擷取。

這一次我們把 bean 的建立交給容器,而不是我們在調用時候傳遞一個執行個體化好的 bean 對象,另外還需要考慮單例對象,在對象的二次擷取時是可以從記憶體中擷取對象的。此外不僅要實作功能還需要完善基礎容器架構的類結構體,否則将來就很難擴容進去其他的功能了。

鑒于本章節的案例目标,我們需要将 spring bean 容器完善起來,首先非常重要的一點是在 bean 注冊的時候隻注冊一個類資訊,而不會直接把執行個體化資訊注冊到 spring 容器中。那麼就需要修改 beandefinition 中的屬性 object 為 class,接下來在需要做的就是在擷取 bean 對象時需要處理 bean 對象的執行個體化操作以及判斷目前單例對象在容器中是否已經緩存起來了。整體設計如圖 3-1

《Spring 手撸專欄》第 2 章:初顯身手,運用設計模式,實作 Bean 的定義、注冊、擷取

首先我們需要定義 beanfactory 這樣一個 bean 工廠,提供 bean 的擷取方法 getbean(string name),之後這個 bean 工廠接口由抽象類 abstractbeanfactory 實作。這樣使用模闆模式的設計方式,可以統一收口通用核心方法的調用邏輯和标準定義,也就很好的控制了後續的實作者不用關心調用邏輯,按照統一方式執行。那麼類的繼承者隻需要關心具體方法的邏輯實作即可。

那麼在繼承抽象類 abstractbeanfactory 後的 abstractautowirecapablebeanfactory 就可以實作相應的抽象方法了,因為 abstractautowirecapablebeanfactory 本身也是一個抽象類,是以它隻會實作屬于自己的抽象方法,其他抽象方法由繼承 abstractautowirecapablebeanfactory 的類實作。這裡就展現了類實作過程中的各司其職,你隻需要關心屬于你的内容,不是你的内容,不要參與。這一部分内容我們會在代碼裡有具體的展現

另外這裡還有塊非常重要的知識點,就是關于單例 singletonbeanregistry 的接口定義實作,而 defaultsingletonbeanregistry 對接口實作後,會被抽象類 abstractbeanfactory 繼承。現在 abstractbeanfactory 就是一個非常完整且強大的抽象類了,也能非常好的展現出它對模闆模式的抽象定義。接下來我們就帶着這些設計層面的思考,去看代碼的具體實作結果

工程源碼:公衆号「bugstack蟲洞棧」,回複:spring 專欄,擷取源碼

spring bean 容器類關系,如圖 3-2

《Spring 手撸專欄》第 2 章:初顯身手,運用設計模式,實作 Bean 的定義、注冊、擷取

圖 3-2

雖然這一章節關于 spring bean 容器的功能實作與 spring 源碼中還有不少的差距,但以目前實作結果的類關系圖來看,其實已經具備了一定的設計複雜性,這些複雜的類關系設計在各個接口定義和實作以及在抽象類繼承中都有所展現,例如:

beanfactory 的定義由 abstractbeanfactory 抽象類實作接口的 getbean 方法

而 abstractbeanfactory 又繼承了實作了 singletonbeanregistry 的defaultsingletonbeanregistry 類。這樣 abstractbeanfactory 抽象類就具備了單例 bean 的注冊功能。

abstractbeanfactory 中又定義了兩個抽象方法:getbeandefinition(string beanname)、createbean(string beanname, beandefinition beandefinition) ,而這兩個抽象方法分别由 defaultlistablebeanfactory、abstractautowirecapablebeanfactory 實作。

最終 defaultlistablebeanfactory 還會繼承抽象類 abstractautowirecapablebeanfactory 也就可以調用抽象類中的 createbean 方法了。

綜上這一部分的類關系和實作過程還是會有一些複雜的,因為所有的實作都以職責劃分、共性分離以及調用關系定義為标準搭建的類關系。這部分内容的學習,可能會豐富你在複雜業務系統開發中的設計思路。

cn.bugstack.springframework.beans.factory.config.beandefinition

在 bean 定義類中已經把上一章節中的 object bean 替換為 class,這樣就可以把 bean 的執行個體化操作放到容器中處理了。如果你有仔細閱讀過上一章并做了相應的測試,那麼你會發現 bean 的執行個體化操作是放在初始化調用階段傳遞給 beandefinition 構造函數的。

cn.bugstack.springframework.beans.factory.config.singletonbeanregistry

這個類比較簡單主要是定義了一個擷取單例對象的接口。

cn.bugstack.springframework.beans.factory.config.defaultsingletonbeanregistry

在 defaultsingletonbeanregistry 中主要實作 getsingleton 方法,同時實作了一個受保護的 addsingleton 方法,這個方法可以被繼承此類的其他類調用。包括:abstractbeanfactory 以及繼承的 defaultlistablebeanfactory 調用。

cn.bugstack.springframework.beans.factory.support.abstractbeanfactory

abstractbeanfactory 首先繼承了 defaultsingletonbeanregistry,也就具備了使用單例注冊類方法。

接下來很重要的一點是關于接口 beanfactory 的實作,在方法 getbean 的實作過程中可以看到,主要是對單例 bean 對象的擷取以及在擷取不到時需要拿到 bean 的定義做相應 bean 執行個體化操作。那麼 getbean 并沒有自身的去實作這些方法,而是隻定義了調用過程以及提供了抽象方法,由實作此抽象類的其他類做相應實作。

後續繼承抽象類 abstractbeanfactory 的類有兩個,包括:abstractautowirecapablebeanfactory、defaultlistablebeanfactory,這兩個類分别做了相應的實作處理,接着往下看。

cn.bugstack.springframework.beans.factory.support.abstractautowirecapablebeanfactory

在 abstractautowirecapablebeanfactory 類中實作了 bean 的執行個體化操作 newinstance,其實這塊會埋下一個坑,有構造函數入參的對象怎麼處理?可以提前思考

在處理完 bean 對象的執行個體化後,直接調用 addsingleton 方法存放到單例對象的緩存中去。

cn.bugstack.springframework.beans.factory.support.defaultsingletonbeanregistry

defaultlistablebeanfactory 在 spring 源碼中也是一個非常核心的類,在我們目前的實作中也是逐漸貼近于源碼,與源碼類名保持一緻。

defaultlistablebeanfactory 繼承了 abstractautowirecapablebeanfactory 類,也就具備了接口 beanfactory 和 abstractbeanfactory 等一連串的功能實作。是以有時候你會看到一些類的強轉,調用某些方法,也是因為你強轉的類實作接口或繼承了某些類。

除此之外這個類還實作了接口 beandefinitionregistry 中的 registerbeandefinition(string beanname, beandefinition beandefinition) 方法,當然你還會看到一個 getbeandefinition 的實作,這個方法我們文中提到過它是抽象類 abstractbeanfactory 中定義的抽象方法。現在注冊bean定義與擷取bean定義就可以同時使用了,是不感覺這個套路還蠻深的。接口定義了注冊,抽象類定義了擷取,都集中在 defaultlistablebeanfactory 中的 beandefinitionmap 裡

cn.bugstack.springframework.test.bean.userservice

這裡簡單定義了一個 userservice  對象,友善我們後續對 spring 容器測試。

cn.bugstack.springframework.test.apitest

在此次的單元測試中除了包括;bean 工廠、注冊 bean、擷取 bean,三個步驟,還額外增加了一次對象的擷取和調用。這裡主要測試驗證單例對象的是否正确的存放到了緩存中。

此外與上一章節測試過程中不同的是,我們把 userservice.class 傳遞給了 beandefinition 而不是像上一章節那樣直接 new userservice() 操作。

這裡會有兩次測試資訊,一次是擷取 bean 時直接建立的對象,另外一次是從緩存中擷取的執行個體化對象。

此外從調試的截圖中也可以看到第二次擷取單例對象,已經可以從記憶體中擷取了,如圖 3-3

《Spring 手撸專欄》第 2 章:初顯身手,運用設計模式,實作 Bean 的定義、注冊、擷取

到這本章節的功能實作和測試驗證就完成了,關于測試過程中可以再去斷點調試下各個階段類的調用,熟悉調用關系。

相對于前一章節對 spring bean 容器的簡單概念實作,本章節中加強了功能的完善。在實作的過程中也可以看到類的關系變得越來越多了,如果沒有做過一些稍微複雜的系統類系統,那麼即使現在這樣9個類搭出來的容器工廠也可以給你繞暈。

在 spring bean 容器的實作類中要重點關注類之間的職責和關系,幾乎所有的程式功能設計都離不開接口、抽象類、實作、繼承,而這些不同特性類的使用就可以非常好的隔離開類的功能職責和作用範圍。而這樣的知識點也是在學習手寫 spring bean 容器架構過程非常重要的知識。

最後要強調一下關于整個系列内容的學習,可能在學習的過程中會遇到像第二章節那樣非常簡單的代碼實作,但要做一個有成長的程式員要記住代碼實作隻是最後的落地結果,而那些設計上的思考才是最有價值的地方。就像你是否遇到過,有人讓你給一個内容做個描述、文檔、說明,你總覺得太簡單了沒什麼可寫的,即使要動筆寫了也不知道要從哪開始!其實這些知識内容都來源你對整體功能的了解,這就不隻是代碼開發還包括了需求目标、方案設計、技術實作、邏輯驗證等等過程性的内容。是以,不要隻是被看似簡單的内容忽略了整體全局觀,要學會放開視野,開放學習視角。