領先一步
VMware 提供培訓和認證,以加速您的進展。
了解更多幾個月前,在我還沒有部落格的時候,Cedric 和 Bob 討論了關於 "Getter Injection" 的議題。
基本概念是 IoC 容器可以在部署時覆寫受管理物件上的抽象或具體方法。 容器正在注入一個方法,例如 getter 方法,而不是像 Setter Injection 那樣注入參考或基本型別。 碰巧的是,我已經在為 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 小組討論中說得很好:「只有在科學失敗時才使用魔法。」
我認為這個概念應該被重新命名為 方法注入 (Method Injection),並且它的價值在其他一些不太常見的場景中要大得多。
在典型的使用依賴注入配置物件時,我不會將它用作 Setter 或建構子注入的替代方案。 Setter 方法和建構子是普通的 Java 建構,它們在容器中工作得很好,但並不依賴於容器。 這很好。 由 IoC 容器提供的魔術方法會增加對容器的依賴,儘管當然仍然可以在容器外部對物件進行子類別化,並且它們仍然只是 Java。
本質上,我將方法注入視為在某些特殊情況下子類別化的替代方案,在這些情況下,超類別應該與容器依賴項隔離,並且容器可以比常規子類別更輕鬆地實現必要的行為。 有問題的方法不需要是 getter 方法(如 Setter Injection 中的 getter),儘管通常它會是一個返回某些東西的方法。
我看到容器實現的方法有三個主要情況
它們可以將容器依賴項從應用程式碼中移出。 他們可以依賴於在部署之前未知的基礎架構。 他們可以為運行時環境自定義舊程式碼的行為。 然而,普通的子類別化在這裡也很有意義。 容器子類別化也比常規子類別化更具動態性。 我們可以潛在地採用一個基類別,並以不同的方式部署它,而無需管理多個類別的原始程式碼。 然而,由於它的魔術係數高於常規子類別化、策略介面或各種替代方案,我認為不應該過於熱切地使用方法注入。
對我來說,方法注入的主要吸引力是作為一種擺脫容器依賴的方式,我過去有時必須使用 Spring 1.0 來承擔這種依賴,並且它將適用於任何支持「非單例」或「原型」物件概念的容器。(也就是說,一個容器可以讓您選擇根據配置在請求時獲取 IoC 管理物件的共享或新實例。)我喜歡使用 Spring,但我討厭為了配置而導入 Spring API。
導致我實現這一點的具體用例是,一個透過 Spring 配置的「單例」物件需要建立非單例物件的實例 - 例如,一個單線程、一次性處理物件 - 但希望該物件使用依賴注入進行配置,而不是僅僅使用 new。 例如,想像一下 ThreadSafeService 需要建立 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> 元素可以在 bean 定義元素中使用,就像 property 或 constructor-arg 元素一樣。
我認為方法注入最引人注目的情況是返回查找由容器管理的命名物件的結果。(當然,這不是 Spring 特有的:任何容器都可以實現這一點。)查找通常是非單例 bean(在 Spring 術語中)。
這樣應用程式碼中就不會存在對 Spring 或任何其他 IoC 容器的依賴。 在不需要導入 Spring API 的情況下關閉了一個特殊情況。 正如我所說,此功能直接受到我正在處理的客戶端專案中的要求的推動,並且在實踐中證明是有用的。
查找方法可以與 Setter Injection 或建構子注入組合使用。 它們不接受參數,因此方法重載不是問題。
該實現使用 CGLIB 來子類別化該類別。(只有當 CGLIB 在類別路徑上時才可用,以避免使 Spring 核心容器依賴於 CGLIB。)
Spring 更進一步,允許您為覆寫的方法定義任意行為 - 不僅僅是 bean 查找。 例如,您可能希望這樣做,以使用基於運行時基礎架構的通用行為 - 例如使用 Spring TransactionInterceptor 類別進行事務回滾。(當然,通常應該使用回滾規則來避免這種情況。)或者,可能存在對通用覆寫行為的引人注目的情況 - 例如「如果存在活動事務,則返回事務資料來源 DS1,否則返回非事務資料來源 DS2」。 再次,如果我們可以從應用程式碼中隱藏這種邏輯,這就是一個勝利。 在這裡,我們超出了純粹的「getter」的範圍:例如,我們可以覆寫方法來發布事件。
通常有替代任意容器覆寫的方法,例如子類別化該類別並以正常方式覆寫該方法(科學,而不是魔法),或使用 AOP。 在像範例中那樣進行 bean 查找的情況下,容器進行覆寫顯然是有好處的,因為它可以消除對 Spring API 的依賴。 它在 XML 中描述起來也簡單得多。 對於更一般的情況,必須有一種解決重載方法的方法。
這已經比我計劃的要長 - 並且花了一段時間! - 所以如果有人感興趣,我將把討論 Spring 1.1 的任意覆寫機制(包括它如何解決重載方法)留到以後的文章中。 我越來越欽佩像 Dion 和 Matt Raible 這樣不知疲倦的部落客,他們似乎每天發布 3 次部落格。