Kava Pava Gava Tava Nava Zava Java

          everything about Java
          隨筆 - 15, 文章 - 0, 評論 - 1, 引用 - 0
          數據加載中……

          Google Guice 用戶手冊之 閱讀筆記

          目的:

                   直接調用new - 
                          直接耦合具體的實現類。這是最不靈活的方式,直接緊密耦合,當需要置換具體實施方式的時候(比如在測試中不能用真正的服務)會遇到麻煩。

                  使用 Factory
                          用戶調用工廠 .getInstance() 方法得到具體實現。間接耦合。缺點是每次使用前都要設置工廠,以便得到想要的實現。每個接口都要有相應的工廠。如果對象增加依賴,要記得在每一處需要的地方(比如每個 Unit Test)設置工廠。如果忘記設置/初始化工廠,僅僅在要用到服務的時候才會出錯。

                  初始化時注入
                          在初始化一個對象的時候給出所有的依賴對象。好處是(1)間接耦合,對象僅僅關心接口。(2)當對象需要新增加一個依賴的時候,編譯器會強制每個用戶給出該依賴的實現對象。缺點是,現在對象的用戶需要關心所有的初始化和依賴。

          Dependency Injection with Guice

          首先,是一個配置或者說映射。謝天謝地,Google 也恨 XML。所以,Google 用一個 java class 來做配置模塊。

          public class BillingModule extends AbstractModule {
            @Override 
            
          protected void configure() {
              bind(TransactionLog.
          class).to(DatabaseTransactionLog.class);
              bind(CreditCardProcessor.
          class).to(PaypalCreditCardProcessor.class);
              bind(BillingService.
          class).to(RealBillingService.class);
            }
          }

          看起來很簡單。就是把接口映射到具體的實現的類。再來看需要被注入依賴實例的類:

          public class RealBillingService implements BillingService {
            
          private final CreditCardProcessor processor;
            
          private final TransactionLog transactionLog;

            @Inject
            
          public RealBillingService(CreditCardProcessor processor,
                TransactionLog transactionLog) {
              
          this.processor = processor;
              
          this.transactionLog = transactionLog;
            }

            
          public Receipt chargeOrder(PizzaOrder order, CreditCard creditCard) {
               
            }
          }

          也很簡單,一個 @Inject 就可以了。最后,最終用戶需要從配置模塊生成 Injector,再從 Injector 得到需要的實例。

            public static void main(String[] args) {
              Injector injector 
          = Guice.createInjector(new BillingModule());
              BillingService billingService 
          = injector.getInstance(BillingService.class);
              
            }

          Bindings

          首先是各種 Binding,也就是說如何配置想注入的對象。Module 是由 Bindings 組成的。有以下幾種Binding:

          Linked Binding:
                  例子: bind(Interface.class).to(Implementation.class)
                  用處: 把實現連接到接口,或者把子類連接到父類(慎用)。甚至可以 A 連接到 B,B 連接到 C 這樣地串聯起來。這樣,當要 A 的時候,得到 C。

          Binding Annotations

                  先定義一個標注
          @BindingAnnotation @Target({ FIELD, PARAMETER, METHOD }) @Retention(RUNTIME)
          public @interface PayPal {}
              然后在定義 @Inject 的時候同時使用標注
            @Inject
           
          public RealBillingService(@PayPal CreditCardProcessor processor,
               
          TransactionLog transactionLog) {
             
          ...
           
          }
              最后,在 Binding 的時候使用標注來說明想要注入的是哪個實例
          
          
              bind(CreditCardProcessor.class)
                 
          .annotatedWith(PayPal.class)
                 
          .to(PayPalCreditCardProcessor.class);
              如果不想自己寫標注,也能容忍字符匹配,可以用@named標注:
            @Inject
           
          public RealBillingService(@Named("Checkout") CreditCardProcessor processor,
               
          TransactionLog transactionLog) {
             
          ...
           
          }
              bind(CreditCardProcessor.class)
                 
          .annotatedWith(Names.named("Checkout"))
                 
          .to(CheckoutCreditCardProcessor.class);
                  也可以自己寫帶有參數的標注。先寫標注接口,再寫標注實例,注意實現 equals() 和 hashCode(),就可以用在 annotatedWith 里面了。

                  用處:當不同的地方要不同的實例的時候。

          Instance Binding

                  提供一個簡單的實例,而不是去初始化。通常就是簡單類型,做配置用。
              bind(String.class)
                 
          .annotatedWith(Names.named("JDBC URL"))
                 
          .toInstance("jdbc:mysql://localhost/pizza");
              bind
          (Integer.class)
                 
          .annotatedWith(Names.named("login timeout seconds"))
                 
          .toInstance(10);
          @Provides Methods

          在配置模塊里面寫 @Provides 方法。
          public
          class BillingModule extends AbstractModule {
           
          @Override
           
          protected void configure() {
             
          ...
           
          }

           @Provides

           
          TransactionLog provideTransactionLog() {
              // 這個方法生成實例并且返回。
            }
          }


            @Provides 也可以帶有標注,或者有參數的標注比如 @Named("Checkout")。這樣這個標注就會被綁定在這個@Provider方法上了。
           
          @Provides @PayPal
           
          CreditCardProcessor providePayPalCreditCardProcessor(
               
          @Named("PayPal API key") String apiKey) {
              // 具體實現。。。

           
          }
              總之,Guice 以 @Provider 函數返回的類型以及標注來判斷該調用哪個函數來得到實例。

          Provider Bindings

              @Provider 寫得太多,模塊就會變得太大,而且太雜亂。這時候就可以寫 Provider。Provider 需要實現這個簡單的接口:

              public interface Provider<T> {
                T
          get();
              }
          Provider 也可以有自己的依賴關系。然后,就可以在Binding 里面使用了。
           
              public class BillingModule extends AbstractModule {
               
          @Override
               
          protected void configure() {
                  bind
          (TransactionLog.class)
                     
          .toProvider(DatabaseTransactionLogProvider.class);
               
          }
          Untargetted Bindings

                  這個最簡單了,不需要指派綁定到的具體實現。這個需要接口指定了默認的 @ImplementedBy (類)或者 @ProvidedBy (Provider類)。
              bind(MyConcreteClass.class);
              bind
          (AnotherConcreteClass.class).in(Singleton.class);
          Built-in Bindings 

                  這些是Guice提供的“拿來就用”的綁定,很方便。比如java.util.logging.Logger。Guice 還會幫助做些設置。其他的似乎 Guice 也不推薦或者詳細說明,就不研究了。
            @Inject
           
          public ConsoleTransactionLog(Logger logger) {
             
          this.logger = logger;
           
          }
          Just-in-Time Bindings

                  就是沒有在模塊里面說明,而是在需要注入的時候決定的。首先是默認構造函數,如果一個類有默認構造函數(就是沒有參數的),那么Guice就用它。其次是接口指定了默認的 @ImplementedBy (類)或者 @ProvidedBy (Provider類)。

          @ImplementedBy(PayPalCreditCardProcessor.class)
          public interface CreditCardProcessor {
           
          ChargeResult charge(String amount, CreditCard creditCard)
               
          throws UnreachableException;
          }
          @ProvidedBy(DatabaseTransactionLogProvider.class)
          public interface TransactionLog {
           
          void logConnectException(UnreachableException e);
           
          void logChargeResult(ChargeResult result);
          }
          這種默認的接口實現可以在使用時,由明確的 Binding 取代。而如果沒有被取代,就用默認。

          Scope

          上面講了綁定。下面講 Scope。Scope 就是在什么范圍內可以共享同一個實例。比如@Singleton就在整個應用中使用同一個實例。Scope 可以這樣指定:

          標注在實現類上:
              @Singleton
              public class InMemoryTransactionLog implements TransactionLog {
               
          /* everything here should be threadsafe! */
              }
          在Binding中明確表示
              bind(TransactionLog.class).to(InMemoryTransactionLog.class).in(Singleton.class);
          在 @Provides 方法中
            @Provides @Singleton
           
          TransactionLog provideTransactionLog() {
             
          ...
           
          }
          注意 Scope 是按照要得到的類型(接口類型)而不是具體實現的類而計算的。比如,Applebees 實現了 Bar 和 Grill 兩個接口,下面將會用兩個 Applebees 實例。

            bind(Bar.class).to(Applebees.class).in(Singleton.class);
            bind
          (Grill.class).to(Applebees.class).in(Singleton.class);
          想只用一個實例,就這樣加上一條規則:
            bind(Applebees.class).in(Singleton.class);
          在 in 子句里面可以用 RequestScoped.class 或者 ServletScopes.REQUEST。用前者比較好,因為表示的不只是 Servlet 中的 Scope。

          默認的 Scope 是每一次 Guice 都生成一個新實例。其他Scope 還有 Singleton, Request, Session。

          確定為 Singleton 的實例可能在啟動的時候生成,這樣可以早發現問題,但可能增加啟動時間。

          選擇 Scope 要看實例是不是有狀態,該狀態要在什么范圍共享,以及創建實例要花費的代價。 @Singleton 和 @SessionScoped 必須線程安全。而 @RequestScoped 不需要。

          注入

          注入可以在多處進行。首先,是前面用過的在構造函數中(推薦,編譯器檢查,好測試):

          public class RealBillingService implements BillingService {
           
          private final CreditCardProcessor processorProvider;
           
          private final TransactionLog transactionLogProvider;

           
          @Inject
           
          public RealBillingService(CreditCardProcessor processorProvider,
               
          TransactionLog transactionLogProvider) {
             
          this.processorProvider = processorProvider;
             
          this.transactionLogProvider = transactionLogProvider;
           
          }
          可以在方法中(僅僅類型影響Guice,方法名字不重要。比構造函數靈活。):
          public class PayPalCreditCardProcessor implements CreditCardProcessor {
           
           
          private static final String DEFAULT_API_KEY = "development-use-only";
           
           
          private String apiKey = DEFAULT_API_KEY;

            @Inject

           
          public void setApiKey(@Named("PayPal API key") String apiKey) {
             
          this.apiKey = apiKey;
           
          }
          也可以在 Field 中(不推薦)
              public class DatabaseTransactionLogProvider implements Provider<TransactionLog> {
               
          @Inject Connection connection;
              
               
          public TransactionLog get() {
                 
          return new DatabaseTransactionLog(connection);
               
          }
          }
          Method 和 Field 還可選。如果找不到,Guice 就不注入。
            @Inject(optional=true)
           
          public void setApiKey(@Named("PayPal API key") String apiKey) {
             
          this.apiKey = apiKey;
           
          }
          另外,對于一個已經初始化了的實例,Guice的injector還有一個injectMembers方法。
          Static Injections 手冊對于新寫的代碼不推薦,不再研究。
          Automatic Injection :對于作為參數傳給了 toInstance()方法的實例(Instance Binding),還有傳給了 toProvider()方法的實例(Provider Binding),如果需要,Guice都會自動去注入。
          另外,也可以將一個 Provider 注入給用戶,由用戶自己決定什么時候去調用 Provider 去取得實例。這樣可以獲得多個實例,或者實現 lazy loading。
          AOP
          對于有Guice生成的實例,Guice還可以進行方法攔截。具體不再敘述,看手冊的例子。

          posted on 2009-12-30 18:55 bing 閱讀(2201) 評論(1)  編輯  收藏

          評論

          # re: Google Guice 用戶手冊之 閱讀筆記  回復  更多評論   

          不錯,不錯,我也寫了一篇文章:http://www.aygfsteel.com/xylz/archive/2009/12/22/306955.html
          2009-12-30 23:27 | xylz

          只有注冊用戶登錄后才能發表評論。


          網站導航:
           
          主站蜘蛛池模板: 宁明县| 汽车| 光山县| 榆林市| 双牌县| 奉贤区| 天门市| 区。| 宁城县| 宿迁市| 荣昌县| 文山县| 景宁| 新河县| 华容县| 湘潭县| 竹山县| 平昌县| 云南省| 博罗县| 万载县| 二手房| 井研县| 新化县| 山西省| 类乌齐县| 台山市| 武强县| 遂溪县| 博湖县| 蒙阴县| 贵州省| 苏州市| 青神县| 镶黄旗| 淮滨县| 綦江县| 德钦县| 泾源县| 疏附县| 霍城县|