領先一步
VMware 提供培訓和認證,助您加速進步。
瞭解更多幾個月前,在我還沒有部落格的日子裡,Cedric 和 Bob 曾討論過“Getter 注入”。
基本概念是,IoC 容器可以在部署時覆蓋託管物件上的抽象或具體方法。容器正在注入一個方法,例如 getter 方法,而不是像 Setter 注入中那樣注入引用或原始型別。碰巧的是,我當時已經在為 Spring 1.1 開發容器方法覆蓋機制,該機制現已在 Spring 1.1 RC1 中釋出。這是一個有趣的概念,並且絕對是完整 IoC 容器的一部分。但是,我認為這個概念更普遍,需要一個更通用的名稱。此外,它應該只在相當窄的場景中使用。
你為什麼要這樣做?Cedric 的動機是 setter 方法“無用”,並且“在一個 Java 物件中擁有你永遠不會呼叫的方法是一種設計異味”。在他看來,物件中最重要的實際上是 getter 方法,它們通常返回儲存在 setter 中的物件引用。因此,他建議讓容器實現 getter 方法,並取消 setter。實際上,這意味著容器將實際覆蓋作為應用程式程式碼一部分定義的 getter 方法,否則無法使用它們。因此,容器最終將使用類似於 CMP 2.x 的機制來實現它(儘管希望任何相似之處到此為止)。
我並不真正認同“無用方法”的論點,因為 setter 將被使用依賴注入的 IoC 容器呼叫,並且 將在沒有任何容器的單元測試中被呼叫。如果物件在容器外部使用,它們將被應用程式程式碼呼叫。此外,getter/setter 組合是一種很好的建立預設值的方式,以防你選擇不配置一個或多個 setter 被呼叫:如果你需要它,setter 就在那裡。雖然我能理解 Cedric 的動機,但這裡有一個權衡:如果我們擺脫了所謂的無用 setter,我們就會剩下不完整的類。如果 getter 是抽象的,我們就會回到需要測試抽象物件的 CMP 2.x 測試場景。如果 getter 是具體的,我們就會例行地編寫將在執行時被覆蓋的方法。在我看來,這才是真正的無用程式碼。(一般來說,我不喜歡覆蓋具體方法,並儘可能避免它。我想我第一次讀到這個建議是在《UML 參考手冊》中,它很有道理。)“setter 注入”中也包含一些魔法元素。如果我能有一個簡單的 POJO,沒有花哨的容器子類化,我更喜歡它。正如 Cedric 自己在去年 5 月 TSSS 的一次小組討論中說得非常好,“只有當科學失敗時才使用魔法。”
我認為這個概念應該改名為 方法注入,並且它對某些其他——不那麼常見——的場景具有更大的價值。
我不會在典型的使用依賴注入配置物件時將其用作 Setter 或建構函式注入的替代方案。Setter 方法和建構函式是普通的 Java 構造,在容器中工作得很好,但並不依賴於容器。這很好。IoC 容器提供的魔法方法會增加對容器的依賴,儘管當然仍然可以在容器外部對物件進行子類化,並且它們仍然只是 Java。
本質上,我將方法注入視為在某些極端情況下對子類化的一種替代方案,在這種情況下,超類應與容器依賴項隔離,並且容器比常規子類更容易實現必要的行為。所討論的方法不需要是 getter 方法(如 Setter 注入的 getter),儘管通常它會是返回某些東西的方法。
我看到了容器實現方法的三個主要用例
它們可以將容器依賴項從應用程式程式碼中移除。它們可以依賴於部署之前未知的基礎設施。它們可以為執行時環境定製遺留程式碼的行為。然而,普通的舊子類化在這裡也很有意義。容器子類化也比常規子類化更具動態性。我們可以潛在地採用一個基類並以不同的方式部署它,而無需管理多個類的原始碼。然而,由於它的魔法系數高於常規子類化、策略介面或各種替代方案,我覺得方法注入不應該過於急切地使用。
對我來說,方法注入的主要吸引力在於它是一種擺脫我有時在使用 Spring 1.0 時不得不產生的容器依賴的方式,並且它將適用於任何支援“非單例”或“原型”物件概念的容器。(也就是說,一個容器,它根據配置,讓你可以選擇在請求時獲取 IoC 管理物件的共享例項或新例項。)我喜歡使用 Spring,但我討厭為了配置而匯入 Spring API。
導致我實現此功能的特定用例是,當透過 Spring 配置的一個“單例”物件需要建立非單例物件的例項時——例如,一個單執行緒、一次性處理物件——但又希望該物件透過依賴注入配置,而不是僅僅使用 new。例如,假設 ThreadSafeService 需要建立 SingleShotHelper 的例項,而 SingleShotHelper 本身是透過依賴注入配置的。在 Spring 1.0.x 中,ThreadSafeService 必須實現 BeanFactoryAware 生命週期介面,儲存 BeanFactory 引用並呼叫
(SingleShotHelper) beanFactory.getBean("singleShotHelper")
每次需要建立一個助手時。這工作正常,測試起來也不難(BeanFactory 是一個簡單的介面,所以很容易模擬),但它是一個 Spring 依賴,如果能更接近一個完全非侵入性的框架,那就太理想了。型別轉換也有些不雅,但無傷大雅。
我通常在 10 個類中會遇到一個這樣的情況。我有時會重構它以提取一個方法,像這樣
protected SingleShotHelper createSingleShotHelper() { return (SingleShotHelper) context.getBean("singleShotHelper"); } 我現在可以子類化來實現這一點,並將 Spring 依賴項保留在超類之外,但這似乎有點過度。
這種方法是容器而非應用程式開發人員實現的理想選擇。它返回一個容器知道的物件;整個事情實際上可以透過配置比程式碼更簡潔地表達(當你考慮到儲存 BeanFactory 引用所需的一點點程式碼時)。
隨著 Spring 1.1 中引入的新方法注入功能,可以使用抽象(或具體)方法,例如
protected abstract SingleShotHelper createSingleShotHelper();
並告訴容器在部署時覆蓋該方法以返回來自相同或父工廠的特定 bean,如下所示
<lookup-method name="createSingleShotHelper" bean="singleShotHelper" >
方法可以是受保護的或公共的。任意數量的方法都可以被覆蓋。<lookup-method> 元素可以像 property 或 constructor-arg 元素一樣在 bean 定義元素中使用。
我認為方法注入最引人注目的用例是返回查詢容器管理的命名物件的結果。(當然,這並非 Spring 特有:任何容器都可以實現這一點。)查詢通常是針對非單例 bean(用 Spring 術語來說)。
這樣,應用程式程式碼中就沒有對 Spring 或任何其他 IoC 容器的依賴。一個極端情況在無需匯入 Spring API 的情況下解決了。正如我所說,這個功能直接受到我正在處理的客戶端專案需求的驅動,並已在實踐中證明有用。
查詢方法可以與 Setter 注入或建構函式注入結合使用。它們不帶引數,因此方法過載不是問題。
實現使用 CGLIB 對類進行子類化。(它僅在 CGLIB 在類路徑上時可用,以避免使 Spring 核心容器依賴於 CGLIB。)
Spring 更進一步,允許您為被覆蓋的方法定義任意行為——而不僅僅是 bean 查詢。例如,您可能希望這樣做,以便基於執行時基礎設施使用通用行為——例如使用 Spring TransactionInterceptor 類進行事務回滾。(當然,通常應該使用回滾規則來避免這種情況。)或者可能存在通用覆蓋行為的引人注目的用例——例如“如果存在活動事務,則返回事務性資料來源 DS1,否則返回非事務性資料來源 DS2”。同樣,如果我們可以將這種邏輯從應用程式程式碼中隱藏起來,那將是一個勝利。在這裡,我們超出了純粹“getter”的範圍:例如,我們可以覆蓋方法來發布事件。
通常有任意容器覆蓋的替代方案,例如透過子類化類並以正常方式覆蓋方法(科學,而不是魔法),或者使用 AOP。在像示例中那樣的 bean 查詢情況下,容器進行覆蓋有明顯的好處,因為它消除了對 Spring API 的依賴。用 XML 描述也簡單得多。對於更一般的情況,需要有一種解決過載方法的方法。
這已經比我計劃的要長了——而且花了一些時間!——所以如果有人感興趣的話,我將在以後的帖子中討論 Spring 1.1 的任意覆蓋機制(包括它如何解決過載方法)。我對所有那些不知疲倦的博主,比如 Dion 和 Matt Raible,他們似乎每天發 3 次部落格,又有了新的敬佩。