
面對複雜的源碼,試着找到開頭和結尾是一件非常具有挑戰的事。為了讓更多的初學者上手,從本文開始,我們將通過實踐的方式帶領讀者逐步實現 Spring 框架的核心鏈路和功能邏輯。
簡化上手、凸顯重點、摒棄冗餘,讓更多的初學者都能在這場學習旅途中收穫滿滿。旅途即將開始,你準備好了嗎?
• 本文難度:★☆☆☆☆
• 本文重點:基於 Spring Bean 容器的存儲功能和讀取功能,採用時間複雜度為O(log n) 的 HashMap 數據結構進行設計和實現。
Spring Bean 包含並管理應用對象的配置和生命周期。從這個意義上講,它是一種用於承載對象的容器,開發者可以設置每個 Bean 對象是如何被創建的,以及它們是如何互相關聯、構建和使用的。使用這些 Bean 對象可以創建一個單獨的實例,或者在需要時生成一個新的實例。
如果將一個 Bean 對象交給 Spring Bean 容器管理,則這個 Bean 對象會以類似零件的方式被拆解,然後存儲到 Spring Bean 容器的定義中,便於 Spring Bean 容器管理,這相當於把對象解耦。
當一個 Bean 對象被定義和存儲後,會由 Spring Bean 容器統一進行分配,這個過程包括 Bean 對象的初始化和屬性填充等。最終,我們可以完整地使用一個被實例化後的Bean 對象。
本文將帶領讀者實現一個簡單的 Spring Bean 容器,用於定義、存儲和獲取 Bean對象。
我們將可以存儲數據的數據結構稱為容器,如 ArrayList、LinkedList、HashSet 等。但在 Spring Bean 容器中,需要一種可以用於存儲對象和使用對象名稱進行便捷索引的數據結構,所以選擇 HashMap 數據結構是最合適的,下面簡單介紹 HashMap。
HashMap 是一種基於擾動函數、負載因子和紅黑樹轉換等技術形成的拉鏈尋址的數據結構,能讓數據更加均勻地分布在哈希桶,以及碰撞時形成的鍊表和紅黑樹上。HashMap 的數據結構會最大限度地使整個數據讀取的複雜度範圍為 O(1) ~ O(n),也存在較多使用複雜度為 O(n) 的鍊表查找數據的情況。不過,經過 10 萬個單詞數據的擾動函數索引計算後,通過在尋址位置膨脹的方差穩定性對比驗證得出,使用擾動函數的數據會更均勻地分布在各個哈希桶索引上,基於這些特性的 HashMap 非常適合用於實現Spring Bean 容器。
另外,實現一個簡單的 Spring Bean 容器,還需要完成 Bean 對象的定義、註冊和獲取 3 個基本步驟,如圖 1-1 所示。
1.定義
BeanDefifinition 是我們在查閱 Spring 源碼時經常看到的一個類,如 singleton 屬性、prototype 屬性和 BeanClassName 類型等。這裡的實現會採用更加簡單的方法,只需要定義一個 Object 類型用於存儲任意類型的對象。
2.註冊
註冊過程相當於把數據存儲到 HashMap 中,現在 HashMap 中存儲的是被定義的Bean 對象的信息。
3.獲取
最後就是獲取對象。Bean 對象的名字就是 key。當 Spring Bean 容器初始化 Bean 對象後,Bean 對象就可以被直接獲取。按照上述設計過程,我們來實現一個簡單的 Spring Bean 容器。
1.工程結構
Spring Bean容器中類的關係如圖1-2所示。
Spring Bean 容器實現 的 內 容 非 常 簡 單, 僅包 括 一 個 簡 單 的 BeanFactory 類 和BeanDefifinition 類。這裡的類名稱與 Spring 源碼中的類名稱一致,只是實現起來會相對簡化一些。在後續的擴展過程中,我們可以不斷地添加內容。
• BeanDefifinition:用於定義 Bean 對象,它的實現方式是以一個 Object 類型存儲對象。
• BeanFactory:代表 Bean 對象的工廠,可以將 Bean 對象的定義存儲到 Map 中以便獲取 Bean 對象。
2.Bean對象的定義
源碼詳見:cn.bugstack.springframework.BeanDefifinition。
在目前的 Bean 對象定義中,只有一個 Object 類型,因為它是所有類型的父類,可以存儲任意類型的 Bean 對象。讀者可以參考 Spring 源碼中這個類的信息,其名稱都是一樣的。在後續章節的實現過程中,我們會逐步完善 BeanDefifinition 類對 Bean 對象定義的相關屬性字段,如propertyValues、initMethodName、destroyMethodName、scope、beanClass等。
3.Bean工廠
源碼詳見:cn.bugstack.springframework.BeanFactory。
BeanFactory 類是用於生成和使用對象的 Bean 工廠,BeanFactory 類的實現過程包括Bean 對象的註冊和獲取,這裡註冊的是 Bean 對象的定義信息。
目前,BeanFactory 類的實現是非常簡化的,但這種簡化的實現卻是整個 Spring Bean容器中關於 Bean 對象使用的最終體現,只不過在實現過程中只展示出了基本的核心原理。在後續的補充實現中,這個類的內容會不斷增加。
1.事先準備
源碼詳見:cn.bugstack.springframework.test.bean.UserService。
這裡簡單定義了一個 UserService 對象類,方便後續對 Spring Bean 容器進行測試。
2.測試實例
源碼詳見:cn.bugstack.springframework.test.ApiTest。
在單元測試中,主要包括初始化 BeanFactory 接口、註冊 Bean 對象、獲取 Bean 對象 3 個步驟,在使用效果上貼近於 Spring 框架,但這裡會更加簡化一些。
在註冊 Bean 對象的過程中,這裡直接把 UserService 類實例化後作為入參信息傳遞給 BeanDefifinition。在後續的代碼中,我們會將這部分內容放入 Bean 工廠中實現。
3.測試結果
從測試結果中可以看到,目前的 Spring Bean 容器案例已經有了雛形。
關於 Spring Bean 容器的一個簡單實現已經完成了,這部分代碼相對簡單,讀者稍加嘗試就可以實現這部分內容。
但對於學習知識的過程來說,寫代碼只是最後的步驟,形成整體思路和設計方案才是更重要的。讀者只有釐清了思路,才能真正地理解代碼。
學習 Spring 源碼難嗎?
難!
難到有1~2年編程經驗的工程師,也不知道從哪裡下手。
大部分資料和書籍都是從一個知識點直接透析到內核。
沒有閱讀源碼經驗的小白,根本沒法如編寫者般感同身受地學習,看時如雲裡霧裡,忘時如過眼雲煙。
為啥會這樣?
因為 Spring產生太久了,源碼太大了,主幹核心源碼外的繁枝末節太多太多。就像將一個沙發左移、套個沙發罩、蓋上一塊布,再鋪個小坐墊,沙發套、蓋布、坐墊對初學源碼的開發者來說,並沒有那麼重要,我們要了解的是最初的沙發、最開始的木板,看看它的結構、聞聞它的味道。
所以,如果你真地想了解Spring 這個源碼級複雜框架的設計和實現精髓,就應該像開發一個項目一樣,從零寫一遍。只有你寫了這個項目,才能知道具體細節是如何處理的,各類設計是如何實現的。
獨樂不如眾樂,小傅哥把手動實現簡單版 Spring 框架的內容編寫成書,希望幫助更多的研發人員學習Spring 源碼,編寫出有價值的源碼設計方案。
從此,你多了一段「手寫Spring」項目的學習經歷:具備了複雜源碼的架構設計能力、吸收了複雜場景分治和抽象的思想、我就是學會了複雜結構中設計模式的運用。
讀者在學習的過程中,可以參考書中Spring框架地圖,通過全局的視角,可以更好地理解和學習 Spring 框架的設計與開發。
首發限時特惠,限量100本
END
關注後端面試那些事,回復【2022面經】
獲取最新大廠Java面經

