那些你應該掌握的 SpringFramework 原理

參考文章

《Spring In Action, 2nd.Ed.》

前言

spring 目前所代表的已經不是一個框架, 或者多個框架, 而是 java 企業級應用中一整套體系化的解決方案。
這一點從 spring 官方的宣傳圖上就可以看出來。
在這裏插入圖片描述
對於普通 java 開發者而言, 應用 spring 已經是再熟悉不過的事情了, 本文旨在梳理一些 spring 應用級別之外, 應該被進階開發者所掌握的原理性知識。

由於 spring 的內容已經涵蓋了方方面面, 本文僅會涉及 spring 中最核心, 最值得普通開發者掌握的一些原理性知識。

Spring 的核心 IoC 容器

在展開Spring IoC 容器的實現原理之前,值得思考的是, IoC 容器究竟爲我們提供了哪些好處, 以致於被如此廣泛的使用。

以下這段話引自 《Spring In Action, 2nd.Ed.》作者 Craig Walls , 他同樣是 Pivotal 公司的開發人員( 不瞭解 Pivotal 公司的童鞋需要知道: Spring 以及衍生框架、Redis 存儲、消息隊列框架 RabbitMQ 等都出自 Pivotal 公司)

“Any nontrivial application is made up of two or more classes that collaborate with each other to perform some business logic. Traditionally, each object is responsible for obtaining its own references to the objects it collaborates with (its dependencies). When applying DI, the objects are given their dependencies at creation time by some external entity that coordinates each object in the system. In other words, dependencies are injected into objects.”

任何正式的應用都是由多個類相互協作, 各自實現不同的業務邏輯完成的。 傳統上,每一個對象都是自行負責獲取它所依賴對象引用的。 但是應用了 DI(Dependency Injection)後, 每個對象所依賴的對象不再由其自身負責獲取, 而是統一由一個外部實體在創建這些需要相互協作的對象時提供。

上面這段話, 有些抽象, 通過代碼和圖片更加形象地表述一下

應用 控制翻轉 / 依賴注入 (IoC/DI)之前

public class A {
    public void doSomethingByA(){
        // 實現一些 A 邏輯

        // 需要 B 類執行一些 B 邏輯
        // new B 也可以換做是 ActionFacotry.createB() 之類的工廠方法
        // 這裏的關鍵是, 由 A 關心怎麼獲得 b 的引用
        B b = new B();

        // 執行 B 邏輯
        b.doSomethingB();
    }
}

由於在類 A 中直接編寫了構造 B(或者是獲取 B 的應用的代碼), A 與 B 類變成了一種緊耦合的協作方式, 下圖可以形象的表達這種耦合關係。
在這裏插入圖片描述
這種耦合意味着, 如果我們希望更換 A 類獲取的類 B 引用, 不得不更改 A 類的代碼。

有同學可能會在這裏指出, 如果使用工廠模式 BFacotry 用於創建類型爲 B 的實例, 那麼不需要修改類 A 也可以更換類 B 的引用。

但實際上, 這並沒有太大區別, 只是把 A與 B 的耦合替換成了 A 與 BFactory 的耦合而已, 會進一步引入 BFactory 的引用如何獲取的問題

應用 控制翻轉 / 依賴注入 (IoC/DI)之前, 代碼是這項協作的 。

應用 控制翻轉 / 依賴注入 (IoC/DI)之後

public class A {
	@Autowired
	private B b;
    public void doSomethingByA(){
        // 實現一些 A 邏輯
        
        // 執行 B 邏輯
        b.doSomethingB();
    }
}

由於 A 類的代碼中沒有編寫任何獲取 B 類引用的代碼, 使得 A 與 B 的耦合關係變成了如下的鬆耦合狀態
在這裏插入圖片描述

這樣當我們想要替換 B 的引用時, 我們就完全無需改動 A 的代碼。

鬆耦合的好處

這個地方可能還會有同學進一步產生疑問, 爲什麼總是要假設改變 B 的引用, 實際開發中,相互依賴的類在很多情況下, 並不需要動態修改啊。

這裏的答案是 測試需求

想象一下, 你要編寫一段針對某個類某個方法進行單元測試, 由於這個方法的正常執行會依賴很多其他的類, 於是在構造測試環境時, 需要 mock 很多用於測試的假的對象。

如果你編寫的代碼使用了 IoC 容器, 想要爲待測試的組件提供一個虛假的 mock 引用就變成了很容易的事。 反之, 如果沒有使用 IoC 容器, 那麼在不改動代碼的情況下, 替換待測試類的依賴組件就變成了一個很難實現的目標。

除去測試需求, 即便你沒有靈活替換某個類引用的需求, 將對象之間的引用關係維護放在一個統一的組件中管理, 也可以使得系統組件中錯綜複雜的關係變得有跡可循

IoC 就沒有副作用嗎

有, 就像前面提到的, 必要的耦合關係是不能被消除的, 只是被從一種耦合替換爲另外一種耦合。使用 IoC 容器後, 由於將系統中組件依賴關係都交給了容器去維護, 整個系統就與容器耦合了起來。
在這裏插入圖片描述
也就是說, 如果你的系統有一天想要替換框架, 這就變得很困難, 當然這種需求很少會有, 所以 Spring IoC 框架已經近乎成爲了 java 世界中企業應用的標準框架。

Spring IoC 容器的實現原理

根據 spring-framework 官方文檔給出的定義, 由 Spring IoC 容器實例化, 組裝, 管理的對象被統稱爲 Bean。 所以後文我們都會用 Bean 來描述被 Spring 所創建以及管理的對象。

Spring IoC 實現的最基本的功能是對象的的創建與注入, 所以從最原始的抽象層面考慮, 至少存在如下 3 個步驟。

  1. 獲取 Bean 的定義信息
  2. 根據 Bean 的定義信息創建 Bean
  3. 在需要時刻, 將創建好的 Bean_A 注入 Bean_B

所以, 如果要理解 Spring, 應該從源碼角度其理解 Spring 是如何實現這三個基本步驟的。

獲取 Bean 定義信息

Spring 框架剛創建時, 是通過 xml 文件描述一個 Bean 的。例如下圖
在這裏插入圖片描述

即描述了一個 class 爲 MyTestBean 的對象, 其 id 被定義爲 myTestBean

public class MyTestBean {
	private String testStr = "testStr";
	
	public String getTestStr(){
			return testStr;
	}
	public void setTestStr(String testStr){
			this.testStr = testStr;
	}
	
}

隨着 jdk1.5 引入 Annotation 的引入, Spring 也支持了通過 Annotation 來定義 Bean , 由於 Annotation 的便捷性, 這成爲了最爲廣泛使用的 Bean 的描述方式。

對於 Spring 容器來說, 無論是通過 xml 獲取 Bean 的定義, 還是通過 annotation 獲取 bean 的定義, 只是兩種不同的信息獲取渠道而已, 前者通過解析 xml 文件內容完成, 後者通過掃描 java class 文件, 利用 jvm 提供的反射功能, 獲取 annotation 信息 。

所以 Spring 讀取解析 Bean 的定義過程, 我們可以直接略過。 重要的是我們需要了解, 這些 Bean 的描述信息, 最終會被存儲在一個 BeanDefination 的實例中, spring 容器後續會以此爲基礎來創建以及管理 Bean

BeanDefinition 的管理

正如前文所描述, 通過 xml 和 annotation 獲取到 Bean 的基本定義信息 BeanDefinition 後, 肯定需要將其統一存儲和管理起來。 在 Spring 框架代碼中, BeanDefinitionRegistry 這個接口就定義了 BeanDefinition 的各類增刪改操作的。

我們可以看到 BeanDefinitionRegistry 接口定義瞭如下方法
在這裏插入圖片描述

創建 Bean

。。。 整理中

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章