Google Guice使用入門

本文經過範例簡單地介紹Google Guice的使用,經過下面的範例咱們能夠知道,Google Guice的使用很是簡單。 java

Google Guice須要使用JDK1.5以上java環境。 程序員

下載Google Guice以後,有如下幾個文件: spring

Java代碼
  1. aopalliance.jar   
  2. guice-1.0.jar   
  3. guice-servlet-1.0.jar   
  4. guice-spring-1.0.jar   
  5. guice-struts2-plugin-1.0.jar  

本例只使用到guice-1.0.jar文件,將其加入到class path中。 數據庫

下面簡單地介紹範例: 編程

範例1:使用com.google.inject.Module接口實現類
  緩存

文件名
說明
HelloGuice.java
業務邏輯接口定義文件
HelloGuiceImpl.java
業務邏輯接口實現文件
HelloGuiceModule.java
該文件必須實現 com.google.inject.Module 接口
TestGuice.java
測試文件

HelloGuice.java 服務器

Java代碼
  1. package cn.jcourse.guice;   
  2.   
  3. /**  
  4.  * @author zhangtao  
  5.  * HelloGuice接口,用於表達問候  
  6.  */  
  7. public interface HelloGuice {   
  8.     public void sayHello();   
  9. }   

上面接口中,咱們定義了一個方法,sayHello,用於向用戶問候。這裏我只是作演示用,在實際的業務中,業務邏輯極可能不是這麼簡單。 app

HelloGuiceImpl.java
  框架

Java代碼
  1. package cn.jcourse.guice.impl;   
  2.   
  3. import cn.jcourse.guice.HelloGuice;   
  4.   
  5. /**  
  6.  * @author zhangtao  
  7.  * HellGuice實現類  
  8.  */  
  9. public class HelloGuiceImpl implements HelloGuice {   
  10.   
  11.     /* (non-Javadoc)  
  12.      * @see cn.jcourse.guice.HelloGuice#sayHello()  
  13.      */  
  14.     public void sayHello() {   
  15.         System.out.println("Hello Guice!");   
  16.     }   
  17. }  

該類是HelloGuice接口的實現類,這裏咱們僅僅是向控制檯輸出了一個Hello Guice!字符串。 dom

HelloGuiceModule.java
 

Java代碼
  1. package cn.jcourse.guice;   
  2.   
  3. import cn.jcourse.guice.impl.HelloGuiceImpl;   
  4.   
  5. import com.google.inject.Binder;   
  6. import com.google.inject.Module;   
  7. /**  
  8.  * @author zhangtao  
  9.  * HelloGuice模塊  
  10.  */  
  11. public class HelloGuiceModule implements Module {   
  12.   
  13.     /*  
  14.      * (non-Javadoc)  
  15.      *   
  16.      * @see com.google.inject.Module#configure(com.google.inject.Binder)  
  17.      */  
  18.     public void configure(Binder binder) {   
  19.         binder.bind(HelloGuice.class).to(HelloGuiceImpl.class);   
  20.     }   
  21. }  

上面的代碼用於告知Guice將接口和實現類綁定。

TestGuice.java
 

Java代碼
  1. package cn.jcourse.guice.test;   
  2.   
  3. import cn.jcourse.guice.HelloGuice;   
  4. import cn.jcourse.guice.HelloGuiceModule;   
  5.   
  6. import com.google.inject.Guice;   
  7. import com.google.inject.Injector;   
  8.   
  9. import junit.framework.TestCase;   
  10.   
  11. /**  
  12.  * @author zhangtao   
  13.  * 測試Guice  
  14.  */  
  15. public class TestGuice extends TestCase {   
  16.     public void testHelloGuice() {   
  17.         Injector injector = Guice.createInjector(new HelloGuiceModule());   
  18.         HelloGuice helloGuice = injector.getInstance(HelloGuice.class);   
  19.         helloGuice.sayHello();   
  20.     }   
  21. }  

上面的代碼咱們使用JUnit來進行單元測試,這裏的代碼也相對比較簡單。

在編寫完上述代碼後,咱們運行TestGuice類,將會發現它向控制檯輸出了Hello Guice!。

範例2:使用Java Annotation

範例1中,咱們本身手工的去配置了綁定關係,固然咱們也能夠不用那麼作。咱們能夠直接爲HelloGuice加上@ImplementedBy註釋,而省略掉對com.google.inject.Module的實現。

HelloGuice.java
 

Java代碼
  1. package cn.jcourse.guice;   
  2.   
  3. import cn.jcourse.guice.impl.HelloGuiceImpl;   
  4.   
  5. import com.google.inject.ImplementedBy;   
  6.   
  7. /**  
  8.  * @author zhangtao  
  9.  * HelloGuice接口,用於表達問候  
  10.  */  
  11. @ImplementedBy(HelloGuiceImpl.class)   
  12. public interface HelloGuice {   
  13.     public void sayHello();   
  14. }   

這裏咱們使用了Guice提供的註解,ImplementedBy,表示該接口由HelloGuiceImpl類實現。這樣咱們就能夠不手動的去配置依賴關係。再看看TestGuice.java。

TestGuice.java
 

Java代碼
  1. package cn.jcourse.guice.test;   
  2.   
  3. import junit.framework.TestCase;   
  4. import cn.jcourse.guice.HelloGuice;   
  5.   
  6. import com.google.inject.Guice;   
  7. import com.google.inject.Injector;   
  8.   
  9. /**  
  10.  * @author zhangtao   
  11.  * 測試Guice  
  12.  */  
  13. public class TestGuice extends TestCase {   
  14.     public void testHelloGuice() {   
  15.         //Injector injector = Guice.createInjector(new HelloGuiceModule());   
  16.            
  17.         Injector injector = Guice.createInjector();   
  18.            
  19.         HelloGuice helloGuice = injector.getInstance(HelloGuice.class);   
  20.         helloGuice.sayHello();   
  21.     }   
  22. }  

能夠看出,咱們不須要本身去new一個Module了,Guice會根據咱們提供的註解本身來配置依賴關係。

咱們運行例子的時候能夠看出,它也輸出了Hello Guice!到控制檯。

 

 

 

經過 Guice 進行依賴項注入(1)

2009-01-12 做者: 
zhangtaolv

本教程是轉載教程,目的是讓你們瞭解又一個很強大的依賴注入框架Guice.

Guice 是一個依賴項注入(DI)框架。幾年來我一直建議開發人員使用 DI,由於它提升了可維護性、可測試性和靈活性。經過觀察工程師對 Guice 的反饋,我發現說服程序員去採用一種新技術的最好方法是使這種技術簡單易用。Guice 讓 DI 變得很簡單,所以 Google 採用了這種方法。我但願本文能幫助您輕鬆學習 Guice。

Guice 2.0 beta

在寫這篇文章時,Guice 開發團隊正在奮力編寫 Guice 2.0,但願能在 2008 年末以前發佈。早期的 beta 發佈在 Google 代碼下載站點。這是一個好消息,由於 Guice 團隊添加了一些新功能,使 Guice 代碼的使用和理解變得更簡單。beta 版沒有最終版中的一些功能,可是 beta 很穩定,質量也很好。事實上,Google 在產品軟件中使用的是 beta 版。我建議您使用 beta 版。這篇文章是專門爲 Guice 2.0 編寫的,介紹了 Guice 的一些新功能,但沒有討論 1.0 中已經廢棄的一些功能。Guice 團隊向我保證:這裏討論的功能在最終發行版和當前 beta 版中都是同樣的。

若是您已經瞭解了 DI,並且知道爲何要藉助一個框架來使用 DI,那麼您能夠跳到 經過 Guice 進行基本注入 小節。不然,請繼續閱讀,瞭解 DI 的好處。

DI 案例

我將以一個例子開始。假設我正在編寫一個超級英雄(superhero)應用程序,同時實現一個名爲 Frog Man 的 hero(英雄)。清單 1 是相關代碼和第一個測試(您必定明白編寫單元測試的重要性,這裏就很少說了)。

清單 1. 一個基本 hero 及其測試

Java代碼
  1. public class FrogMan {   
  2.   private FrogMobile vehicle = new FrogMobile();   
  3.   public FrogMan() {}   
  4.   // crime fighting logic goes here...   
  5. }   
  6.   
  7. public class FrogManTest extends TestCase {   
  8.  public void testFrogManFightsCrime() {   
  9.     FrogMan hero = new FrogMan();   
  10.     hero.fightCrime();   
  11.     //make some assertions...   
  12.   }   
  13. }  

彷佛一切正常,但在運行測試時出現瞭如清單 2 所示的異常:

清單 2. 依賴項出現問題

Java代碼
  1. java.lang.RuntimeException: Refinery startup failure.   
  2.   at HeavyWaterRefinery.(HeavyWaterRefinery.java:6)   
  3.   at FrogMobile.(FrogMobile.java:5)   
  4.   at FrogMan.(FrogMan.java:8)   
  5.   at FrogManTest.testFrogManFightsCrime(FrogManTest.java:10)  

彷佛 FrogMobile 構建了一個 HeavyWaterRefinery,假設我不能在測試中構建其中一個依賴項。固然,我能夠在生產環境中實現這一點,可是不能保證能在測試中構建第二個提煉廠(refinery)。在現實生活中,您不可能提煉出氧化氘,但您能夠依賴遠程服務器和強大的數據庫。原理是同樣的:這些依賴項很難啓動,交互起來也很慢,這使得測試比平時更容易失敗。

輸入 DI

爲了不這個問題,您能夠建立一個接口(例如 Vehicle),使 FrogMan 類接受 Vehicle 做爲一個構造函數參數,如清單 3 所示:

清單 3. 依賴接口並注入它們

Java代碼
  1. public class FrogMan {   
  2.   private Vehicle vehicle;   
  3.   
  4.   public FrogMan(Vehicle vehicle) {   
  5.     this.vehicle = vehicle;   
  6.   }   
  7.   // crime fighting logic goes here...   
  8. }  

這種用法就是 DI 的本質 — 使類經過引用接口而不是構建接口(或使用靜態引用)來接受它們的依賴項。清單 4 顯示了 DI 如何使測試變得更簡單:

清單 4. 測試可使用 mock 而不是麻煩的依賴項

Java代碼
  1. static class MockVehicle implements Vehicle {   
  2.   boolean didZoom;   
  3.   
  4.   public String zoom() {   
  5.     this.didZoom = true;   
  6.     return "Mock Vehicle Zoomed.";   
  7.   }   
  8. }   
  9.   
  10. public void testFrogManFightsCrime() {   
  11.   MockVehicle mockVehicle = new MockVehicle();   
  12.   
  13.   FrogMan hero = new FrogMan(mockVehicle);   
  14.   hero.fightCrime();   
  15.   
  16.   assertTrue(mockVehicle.didZoom);   
  17.   // other assertions   
  18. }  

這個測試使用了一個手動編寫的 mock 對象來替換 FrogMobile。DI 不只在測試中省去了麻煩的 refinery 啓動過程,並且使測試不用瞭解 FrogMobile 具體細節。須要的僅是一個 Vehicle 接口。除了使測試變得更簡單以外,DI 還有助於提升代碼的整體模塊性和可維護性。如今,若是想將 FrogMobile 切換爲 FrogBarge,能夠不修改 FrogMan。全部 FrogMan 都依賴於 Vehicle 接口。

不過這裏有一個陷阱。若是您是第一次閱讀 DI,可能會想:「這下好了,如今全部 FrogMan 的調用方 都必須知道 FrogMobile(refinery、refinery 的依賴項,依此類推……)」。但若是是這樣,DI 就永遠不會這麼流行。您能夠不增長調用方的負擔,而是編寫一些工廠 來管理對象及其依賴項的建立。

工廠是存放框架的地方。工廠須要大量冗長重複的代碼。工廠每每會讓程序員(和讀者)很痛苦,他們甚至會嫌它麻煩而放棄編寫。Guice 和其餘 DI 框架可做爲 「超級工廠」,您能夠經過配置它們來構建對象。配置 DI 框架比本身編寫工廠容易得多。所以,程序員編寫的代碼大部分是 DI 樣式的。測試越多代碼就越好,程序員之後也就越省事。

經過 Guice 進行基本注入

我但願在個人介紹以後,您會相信 DI 能爲您的設計增長價值,並且使用框架會使工做更輕鬆。如今讓咱們從 @Inject 註釋和模塊開始深刻討論 Guice。

告訴 Guice 給類添加 @Inject

FrogMan 與 Guice 上的 FrogMan 之間的惟一區別是 @Inject。清單 5 顯示了 FrogMan 帶有註釋的構造函數:

清單 5. FrogMan 已經加上 @Inject

Java代碼
  1. @Inject  
  2. public FrogMan(Vehicle vehicle) {   
  3.   this.vehicle = vehicle;   
  4. }  

一些工程師不喜歡給類添加 @Inject。他們更喜歡一個徹底忽略 DI 框架的類。這種說法有必定道理,可是我不大讚同。依賴項的注入會使註釋的做用更加明顯。@Inject 標記只在您要求 Guice 構建類時纔有意義。若是不要求 Guice 構建 FrogMan,這個註釋對代碼行爲就沒有任何影響。這個註釋恰當地指出了 Guice 將參與構建類。可是,使用它須要源代碼級別的訪問。若是這個註釋帶來不便,或者正在使用 Guice 建立沒法控制其源代碼的對象,那麼 Guice 就會用一個替代機制。

告訴 Guice 您須要哪一個依賴項

Guice 知道您的 hero 須要一個 Vehicle 後,它須要知道提供什麼 Vehicle。清單 6 包含一個 Module:一個特殊的類,用於告訴 Guice 各個接口對應的實現。

清單 6. HeroModule 將 Vehicle 綁定到 FrogMobile

Java代碼
  1. public class HeroModule implements Module {   
  2.   public void configure(Binder binder) {   
  3.     binder.bind(Vehicle.class).to(FrogMobile.class);   
  4.   }   
  5. }  

模塊就是一個具備某種單實例對象方法的接口。Guice 傳遞給模塊的 Binder 用於告訴 Guice 您想如何構造對象。綁定程序 API 造成一種 區域特定語言。這種小語言容許您編寫表達式代碼,好比 bind(X).to(Y).in(Z)。後面將提供更多有關綁定程序做用的例子。每次調用 bind 都會建立一個綁定,Guice 將使用綁定集解析注入請求。

使用 Injector 啓動

而後,使用 Injector 類啓動 Guice。一般須要儘早在程序中建立注入器。這樣 Guice 可以幫助您建立大部分對象。清單 7 包含一個以 Injector 開始的示例 main 程序:

清單 7 使用 Injector 啓動應用程序

Java代碼
  1. public class Adventure {   
  2.   public static void main(String[] args){   
  3.     Injector injector = Guice.createInjector(new HeroModule());   
  4.     FrogMan hero = injector.getInstance(FrogMan.class);   
  5.     hero.fightCrime();   
  6.   }   
  7. }  

爲了獲取注入器,須要在 Guice 類上調用 createInjector。向 createInjector 傳遞一個模塊列表,用於配置它自己(本例只有一個,但您能夠添加一個配置 evildoer 的 VillainModule)。擁有注入器後,使用 getInstance 向它請求對象,傳遞您想返回的 .class(細心的讀者會注意到您不須要告訴 Guice 有關 FrogMan 的信息。若是您請求一個具體類,而它有一個 @Inject 構造函數或公共非參數構造函數的話,Guice 就會建立這個類,而無需調用 bind)。

這是 Guice 構造對象的第一種方式:顯式詢問。可是,您不會但願在啓動例程以外使用這個操做。更好、更簡單的方式是讓 Guice 注入依賴項、依賴項的依賴項,依此類推(正如諺語所說:「背起地球的海龜站在另外一個海龜的背上」)。最初看來,這彷佛比較麻煩,但您很快就會習慣這種用法。例如,清單 8 顯示了一個注入了 FuelSource 的 FrogMobile:

清單 8. FrogMobile 接受一個 FuelSource

Java代碼
  1. @Inject  
  2. public FrogMobile(FuelSource fuelSource){   
  3.   this.fuelSource = fuelSource;   
  4. }  

這意味着,當您檢索 FrogMan 時,Guice 會構建一個 FuelSource、一個 FrogMobile,最後是一個 FrogMan。即便應用程序與注入器只交互一次,也是如此。

固然,您並不老是有機會控制應用程序的 main 例程。例如,許多 Web 框架自動構建 「操做」、「模板」 或其餘一些初始服務。老是能夠找到一個地方插入 Guice,無論是使用該框架的一個插件,仍是使用一些本身手動編寫的代碼(例如,Guice 項目發佈了一個 Struts 2 插件,它容許 Guice 配置您的 Strut 操做)。

 

 

經過 Guice 進行依賴項注入(2)

2009-01-12 做者: 
zhangtaolv

其餘注入形式

到目前爲止,我展現了 @Inject 應用於構造函數的用法。當 Guice 找到註釋時,它會挑選構造函數參數,並試圖爲每一個參數找到一個配置綁定。這稱爲 構造函數注入。根據 Guice 的最佳實踐指南,構造函數注入是詢問依賴項的首選方式。但這不是惟一的方式。清單 9 顯示了配置 FrogMan 類的另外一種方式:

清單 9. 方法注入

Java代碼
  1. public class FrogMan{   
  2.   private Vehicle vehicle;   
  3.   
  4.   @Inject  
  5.   public void setVehicle(Vehicle vehicle) {   
  6.     this.vehicle = vehicle;   
  7.   }   
  8. //etc. ...  

注意,我沒有使用注入的構造函數,而是改用一個帶有 @Inject 標記的方法。Guice 會在構造好 hero 以後當即調用此方法。Spring 框架的忠實用戶能夠將此方法視爲 「setter 注入」。不過,Guice 只關心 @Inject;您能夠任意命名這個方法,它能夠帶有多個參數。此方法能夠是包保護的,也能夠是私有方法。

若是您認爲 Guice 訪問私有方法不是很好,能夠參見清單 10,其中 FrogMan 使用了字段注入:

清單 10. 字段注入

Java代碼
  1. public class FrogMan {   
  2.   @Inject private Vehicle vehicle;   
  3.   public FrogMan(){}   
  4. //etc. ...  

一樣,全部 Guice 都只關心 @Inject 註釋。字段注入查找註釋的全部字段並試圖注入相應的依賴項。

哪一種方法最好

三個 FrogMan 版本都展現了相同的行爲:Guice 在構建時注入相應的 Vehicle。不過,像 Guice 的做者同樣,我更喜歡構造函數注入。下面簡單分析這三種方式:

  • 構造函數注入 很簡單。由於 Java 技術能保證構造函數調用,您不用擔憂出現未初始化的對象 — 無論是否是由 Guice 建立的。您還能夠將字段標記爲 final。
  • 字段注入 會影響可測試性,特別是將字段標記爲 private 時。這破壞了使用 DI 的主要目的。應該儘可能少使用字段注入。
  • 方法注入 在您不控制類的實例化時頗有用。若是您有一個須要某些依賴項的超類,也可使用方法注入(構造函數注入會使這種狀況變得很複雜)。

選擇實現

如今,假設應用程序中有多個 Vehicle。同樣英勇的 Weasel Girl 沒法駕馭 FrogMobile!同時,您不想在 WeaselCopter 上硬編碼依賴項。清單 11 顯示了 Weasel Girl 請求一種更快的傳輸模式:

清單 11. 使用註釋請求某種特定的實現

Java代碼
  1. @Inject  
  2. public WeaselGirl(@Fast Vehicle vehicle) {   
  3.   this.vehicle = vehicle;   
  4. }  

在清單 12 中,HeroModule 使用綁定函數告訴 Guice WeaselCopter 是 「很快」 的:

清單 12. 告訴 Guice Module 中的相關注釋

Java代碼
  1. public class HeroModule implements Module {   
  2.  public void configure(Binder binder) {   
  3.     binder.bind(Vehicle.class).to(FrogMobile.class);   
  4.     binder.bind(Vehicle.class).annotatedWith(Fast.class).to(WeaselCopter.class);   
  5.   }   
  6. }  

注意,我選擇了一個註釋,描述我想以抽象形式描述的工具種類(@Fast),而不是與實現太接近的註釋(@WeaselCopter)。若是您使用的註釋將想要的實現描述得太精確,就讓讀者以爲建立一個隱式依賴項。若是使用 @WeaselCopter,並且 Weasel Girl 借用了 Wombat Rocket,就會對程序員閱讀和調試代碼形成混淆。

要建立 @Fast 註釋,須要複製清單 13 中的模板:

清單 13. 複製粘貼這段代碼以建立一個綁定註釋

Java代碼
  1. @Retention(RetentionPolicy.RUNTIME)   
  2. @Target({ElementType.FIELD, ElementType.PARAMETER})   
  3. @BindingAnnotation  
  4. public @interface Fast {}  

若是您編寫了大量 BindingAnnotations,就會獲得許多這樣的小文件,每一個文件只是註釋名稱不一樣。若是您以爲這很繁瑣,或者須要執行快速的原型設計,能夠考慮 Guice 的內置 @Named 註釋,它接受一個字符串屬性。清單 14 展現了這種替代方法:

清單 14. 使用 @Named 代替自定義註釋

Java代碼
  1. // in WeaselGirl   
  2. @Inject  
  3. public WeaselGirl(@Named("Fast") Vehicle vehicle) {   
  4.   //...   
  5. }   
  6.   
  7. // in HeroModule   
  8. binder.bind(Vehicle.class)   
  9.   .annotatedWith(Names.named("Fast")).to(WeaselCopter.class);  

這種方法是可行的,但因爲名稱只在字符串內有效,因此這不能利用編譯時檢查和自動補齊。總的來講,我更願意本身編寫註釋。

若是您根本不想使用註釋,怎麼辦?即便添加 @Fast 或 @Named("Fast") 都會使類在某種程度上影響配置自己。若是想知道如何解決這個問題,請接着閱讀。

provider 方法

若是每次探險都派遣 Frog Man,您可能會厭煩。您喜歡在每一個場景中出現的 hero 是隨機的。可是,Guice 的默認綁定程序 API 不容許出現 「每次調用時將 Hero 類綁定到一個不一樣的實現」 這樣的調用。不過,您能夠 告訴 Guice 使用一種特殊的方法來建立每一個新的 Hero。清單 15 顯示了將一個新方法添加到 HeroModule 中,並用特殊的 @Provides 註釋進行註釋:

清單 15. 使用 provider 編寫自定義建立邏輯

Java代碼
  1. @Provides  
  2. private Hero provideHero(FrogMan frogMan, WeaselGirl weaselGirl) {   
  3.   if (Math.random() > .5) {   
  4.     return frogMan;   
  5.   }   
  6.   return weaselGirl;   
  7. }  

Guice 會自動發現具備 @Provides 註釋的 Module 中的全部方法。根據 Hero 的返回類型,在您請求某個 hero 時,Guice 會進行計算,它應該調用 provider 方法來提供 hero。您能夠爲 provider 方法添加邏輯以構建對象並在緩存中查詢它,或者經過其餘方式得到它。provider 方法是將其餘庫集成到 Guice 模塊中的很好方式。它們也是從 Guice 2.0 開始提供的新方法(Guice 1.0 中只編寫自定義 provider 類,這比較乏味,並且更加繁瑣。若是您已經決定使用 Guice 1.0,用戶指南中有這種舊方法的文檔,並且在本文隨附的 示例代碼 中,您能夠找到一個自定義 provider)。

在清單 15 中,Guice 自動使用正確的參數注入 provider 方法。這意味着 Guice 將從它的綁定列表中找到 WeaselGirl 和 FrogMan,您無需在 provider 方法中手動構建它們。這演示了 「海龜背地球」 原則(海龜背地球,哪海龜下面是什麼呢?是由另外一隻海龜揹着,如此反覆)。您依靠 Guice 來提供依賴項,即便是配置 Guice 模塊自己。

請求一個 Provider 而不是一個依賴項

假設一個故事(Saga)中要有多個 hero。若是要求 Guice 注入一個 Hero,只會獲得一個 hero。但若是您請求一個 「hero provider」,就能夠根據須要建立任意多的 hero,如清單 17 所示:

清單 17. 注入 provider 來控制實例化

Java代碼
  1. public class Saga {   
  2.   private final Provider heroProvider;   
  3.   
  4.   @Inject  
  5.   public Saga(Provider heroProvider) {   
  6.     this.heroProvider = heroProvider;   
  7.   }   
  8.   
  9.   public void start() throws IOException {   
  10.     for (int i = 0; i < 3; i++) {   
  11.       Hero hero = heroProvider.get();   
  12.       hero.fightCrime();   
  13.     }   
  14.   }   
  15. }  

提供者也能夠推遲英雄的出場時間,直到傳奇真正開始。若是英雄依賴於時間敏感或上下文敏感的數據,這就會很方便。

Provider 接口有一個方法:get。要訪問提供的對象,調用這個方法便可。每次有沒有獲取新對象以及對象如何配置取決於 Guice 是如何配置的(參閱下面的 做用域 部分,瞭解單實例對象和其餘長生命週期對象的詳細信息)。在本例中,Guice 使用 @Provides 方法,由於它是構建新 Hero 的註冊方式。這意味着該傳奇應該由任意三位英雄混合而成。

不要把提供者與 provider 方法弄混淆了(在 Guice 1.0,這二者更難區分開來)。儘管該 Saga 是從自定義 @Provides 方法中得到它的英雄,但您能夠請求任意 Guice 實例化依賴項的一個 Provider。若是須要,能夠根據清單 18 從新編寫 FrogMan 的構造函數:

清單 18. 請求 Provider 而不是依賴項

Java代碼
  1. @Inject  
  2. public FrogMan(Provider vehicleProvider) {   
  3.   this.vehicle = vehicleProvider.get();   
  4. }  

(注意您徹底不用更改這個模塊代碼)。從新編寫沒有任何做用;只是說明您老是能夠請求 Provider,而不用直接請求依賴項。

做用域

默認狀況下,Guice 爲每一個請求的依賴項建立一個新實例。若是對象是輕量級的,這個策略能夠很好地工做。可是,若是有一個建立開銷很大的依賴項,就可能須要在幾臺客戶機之間共享實例。在清單 19 中,HeroModule 將 HeavyWaterRefinery 做爲一個單實例對象綁定:

清單 19. 將 HeavyWaterRefinery 綁定爲一個單實例對象

Java代碼
  1. public class HeroModule implements Module {   
  2.   public void configure(Binder binder) {   
  3.     //...   
  4.     binder.bind(FuelSource.class)   
  5.       .to(HeavyWaterRefinery.class).in(Scopes.SINGLETON);   
  6.   }   
  7. }  

這意味着 Guice 會一直保持 「提煉廠」 可用,只要另外一個實例須要燃料源,Guice 就會注入相同 的 「提煉廠」。這避免了在應用程序中啓動多個 「提煉廠」。

在選擇做用域時,Guice 提供了一個選項。可使用綁定程序配置它們,或者直接註釋依賴項,如清單 20 所示:

清單 20. 改用註釋選擇做用域

Java代碼
  1. @Singleton  
  2. public class HeavyWaterRefinery implements FuelSource {...}  

Guice 提供了超出範圍的 Singleton 做用域,但它容許您定義本身的做用域(若是您願意)。例如,Guice servlet 包提供了兩個其餘做用域:Request 和 Session,它們爲 servlet 請求和 servlet 會話提供類的一個獨特實例。

常量綁定和模塊配置

HeavyWaterRefinery 須要一個許可密鑰才能啓動。Guice 能夠綁定常量值和新實例。請查看清單 21:

清單 21. 在模塊中綁定常量值

Java代碼
  1. public class HeavyWaterRefinery implements FuelSource {   
  2.   @Inject  
  3.   public HeavyWaterRefinery(@Named("LicenseKey") String key) {...}   
  4. }   
  5.   
  6. // in HeroModule:   
  7. binder.bind(String.class)   
  8.   .annotatedWith(Names.named("LicenseKey")).toInstance("QWERTY");  

這裏有必要使用綁定註釋,不然 Guice 將不能區分不一樣的 String。

注意,儘管前面不推薦使用,我仍是選擇使用 @Named 註釋。由於我想顯示清單 22 所示的代碼:

清單 22. 使用屬性文件配置模塊

Java代碼
  1. //In HeroModule:   
  2. private void loadProperties(Binder binder) {   
  3.   InputStream stream =   
  4.     HeroModule.class.getResourceAsStream("/app.properties");   
  5.   Properties appProperties = new Properties();   
  6.   try {   
  7.     appProperties.load(stream);   
  8.     Names.bindProperties(binder, appProperties);   
  9.   } catch (IOException e) {   
  10.     // This is the preferred way to tell Guice something went wrong   
  11.     binder.addError(e);   
  12.   }   
  13. }   
  14.   
  15. //In the file app.properties:   
  16. LicenseKey=QWERTY1234  

這段代碼使用 Guice Names.bindProperties 實用函數,經過恰當的 @Named 註釋將 app.properties 文件中的每一個屬性與一個常量綁定。這自己就很好,並且還顯示了您可使模塊代碼更復雜。若是喜歡,能夠從數據庫或 XML 文件加載綁定信息。模塊是純 Java 代碼,這提供了很大的 靈活性。

結束語

Guice 主要概念小結:

  • 使用 @Inject 請求依賴項。
  • 將依賴項與 Module 中的實現綁定。
  • 使用 Injector 引導應用程序。
  • 使用 @Provides 方法增長靈活性。

須要瞭解的 Guice 知識還不少,但您應該先掌握這篇文章中討論的內容。我建議下載它,以及本文的 示例代碼。固然,您也能夠建立本身的示例應用程序,這就更好了。經過示例深刻了解概念但又不用考慮生產代碼是頗有意思的。若是要了解更多 Guice 高級功能(好比面向方面編程支持),建議您訪問 參考資料 中的一些連接。

說到生產代碼,DI 的一個缺點是它可能感染病毒。注入一個類後,它會致使注入下一個類,依此類推。不過這很好,由於 DI 使代碼更好。另外一方面,這須要大量重構現有代碼。爲了使工做易於管理,能夠將 Guice Injector 存儲在某處並直接調用它。應該將這看成一根臨時須要的柺杖,但最後必定能夠擺脫它。

Guice 2.0 即將推出。有一些功能我尚未討論,它可使模塊的配置更簡單,並且能支持更大、更復雜的配置方案。

我但願您會考慮將 Guice 添加到工具包中。根據個人經驗,DI 對於實現靈活的可測試代碼庫特別有用。Guice 使 DI 變得簡單而有趣。還有什麼比容易編寫的、靈活的、可測試的代碼更好呢?

相關文章
相關標籤/搜索