Google Guice之綁定方式

在Guice中,注入器的工做是裝配對象圖,當請求某一類型實例時,注入器根據對象圖來判斷如何建立實例、解析依賴。要肯定如何解析依賴就須要經過配置注入器的綁定方式。java

要建立綁定(Binding)對象,能夠繼承自AbstractModule類,而後覆蓋其configure方法,在方法調用bind()方法來指來定每一次綁定,這些方法帶有類型檢查,若是你使用了錯誤的類型編譯器就會報告編譯錯誤。若是你已經寫好了Module類,則建立一個Module類對象做爲參數傳遞給Guice.createInjector()方法用於建立一個注入器。mysql

經過Module對象能夠建立連接綁定(linked bindings)、實例綁定(instance bindings)、@Provides methods、提供者綁定(provider bindings)、構建方法綁定(constructor bindings)與無目標綁定(untargetted bindings)。這些綁定方式統稱爲內置綁定,相對應的還有種及時綁定,若是在解析一個依賴時若是在內置綁定中沒法找到,那麼Guice將會建立一個及時綁定。sql

1、連接綁定(LinkdedBindings)

連接綁定即映射一類型到它的實現類,例如映射TransactionLog接口到實現類DatabaseTransactionLogapi

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

這樣,當你調用injector.getInstance(TransactionLog.class)方法,或者當注入器碰到TransactionLog依賴時,就會使用DatabaseTransactionLog對象。連接是從一類型到它任何的子類型,這包括接口實現類,類的子類;因此以下映射也是能夠的:bind(DatabaseTransactionLog.class).to(MySqlDatabaseTransactionLog.class);
而且連接綁定支持鏈式寫法:安全

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

在這種狀況下,當請求一個TransactionLog類型對象時,注入器將返回一個MySqlDatabaseTransactionLog對象。ide

2、綁定註解

某些狀況下你可能想爲同一種類型設置多種綁定。這就能夠經過綁定註解來實現,該註解與綁定的類型用於惟一結識一個綁定,合在一塊兒稱爲Key。示例:ui

package example.pizza;

import com.google.inject.BindingAnnotation;
import java.lang.annotation.Target;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;

@BindingAnnotation @Target({ FIELD, PARAMETER, METHOD }) @Retention(RUNTIME)
public @interface PayPal {}

這裏關鍵的是@BindingAnnotation元註解,當Guice描述到該註解時,就會把PayPal做爲綁定註解。
而後在Moduleconfigure方法中使用annotatedWith語句,以下:
bind(CreditCardProcessor.class).annotatedWith(PayPal.class).to(PayPalCreditCardProcessor.class);
這樣就把CreditCardProcessor映射到了PayPalCreditCardProcessorthis

用法以下:google

public class RealBillingService implements BillingService {

@Inject
    public RealBillingService(@PayPal CreditCardProcessor processor,
        TransactionLog transactionLog) {
        ...
    }
}

還有一種狀況,咱們可使用Guice已經定義好的@Named註解,例如:url

public class RealBillingService implements BillingService {

  @Inject
  public RealBillingService(@Named("Checkout") CreditCardProcessor processor,
        TransactionLog transactionLog) {
        ...
  }
}

要綁定一具體名稱,使用Names.named()來建立一個實現傳給annotatedWith方法:

bind(CreditCardProcessor.class)
        .annotatedWith(Names.named("Checkout"))
        .to(CheckoutCreditCardProcessor.class);

由於編譯器不會對字符串進行檢查,Guice建議咱們少使用@Named註解,可是我我的認爲,只要本身寫代碼時只要名稱不要寫錯,經過這種方法式是最容易爲同一類型映射多個綁定的,這很相似Spring中的實現方式,Spring的@Service, @Controller, @Repository不就能夠指定名稱嗎?

3、實例綁定(Instance Bindings)

經過實例綁定咱們能夠爲某類型綁定一個具體的實例,這僅僅適用於這些實例類型沒有其它依賴的狀況,例如值對象:

bind(String.class)
        .annotatedWith(Names.named("JDBC URL"))
        .toInstance("jdbc:mysql://localhost/pizza");
    bind(Integer.class)
        .annotatedWith(Names.named("login timeout seconds"))
        .toInstance(10);

使用方式以下 :

@Inject @Named("JDBC URL")
private String url

Guice建議咱們避免使用.toInstance來建立複雜的對象,由於這會延遲應用啓動。相似地,可使用@Provides方法實現。

4、@Provides方法

當使用@Provides方法建立對象時,該方法必須定義在Module類中,而且它必須加以@Provides註解,該方法的返回值類型就是被綁定的對象。當注入器須要該類型的實例時,它就會來調用該方法。

public class BillingModule extends AbstractModule {
  @Override
  protected void configure() {
    ...
  }

  @Provides
  TransactionLog provideTransactionLog() {
    DatabaseTransactionLog transactionLog = new DatabaseTransactionLog();
    transactionLog.setJdbcUrl("jdbc:mysql://localhost/pizza");
    transactionLog.setThreadPoolSize(30);
    return transactionLog;
  }
}

若是在@Provides方法上有@PayPal@Named("Checkout")綁定註解,Guice以綁定註解優先。Guice在調用@Provides方法以前會先解析該方法的依賴:

@Provides @PayPal
CreditCardProcessor providePayPalCreditCardProcessor(@Named("PayPal API key") String apiKey) {
    PayPalCreditCardProcessor processor = new PayPalCreditCardProcessor();
    processor.setApiKey(apiKey);
    return processor;
}

關於異常:Guice不容許在@Provides方法中拋出異常。若是有異常拋出,那麼異常將會被包裝在ProvisionException對象中。

5、提供者綁定(Provider Bindings)

若是@Provides方法愈來愈複雜,咱們可能會想把它們移到一個單獨的類中。一個提供者類實現了Provider接口,它是一個用於提供值的簡單通用接口。

public interface Provider<T> {
  T get();
}

若是提供者實現類有其本身的依賴時,能夠經過在其構造方法上添加@Inject註解進行注入,以保證值安全返回。

public class DatabaseTransactionLogProvider implements Provider<TransactionLog> {
  private final Connection connection;

  @Inject
  public DatabaseTransactionLogProvider(Connection connection) {
    this.connection = connection;
  }

  public TransactionLog get() {
    DatabaseTransactionLog transactionLog = new DatabaseTransactionLog();
    transactionLog.setConnection(connection);
    return transactionLog;
  }
}

最後使用.toProvider語句來綁定到提供者:

public class BillingModule extends AbstractModule {
  @Override
  protected void configure() {
    bind(TransactionLog.class)
        .toProvider(DatabaseTransactionLogProvider.class);
  }
}

6、無目標綁定

Guice容許咱們建立綁定時不指定目標類,也就是沒有to語句,這對於具體類或者使用了@ImplementedBy@ProvidedBy註解類型頗有用。例如:

  1. bind(MyConcreteClass.class);
  2. bind(AnotherConcreteClass.class).in(Singleton.class);

然而,在使用綁定註解時,咱們依賴必須指定綁定目標,即它是一個具體類,例如:

bind(MyConcreteClass.class)
    .annotatedWith(Names.named("foo"))
    .to(MyConcreteClass.class);
bind(AnotherConcreteClass.class)
    .annotatedWith(Names.named("foo"))
    .to(AnotherConcreteClass.class)
    .in(Singleton.class);

7、構造方法綁定

有些時候你可能須要將某一類型綁定到任一構建方法,例如在@Inject註解沒法添加到目標類構造方法,其緣由多是這個類是第三方提供的,或者說該類有多個構建方法參與依賴注入。此時@Provides方法是解決這個問題的最好方案,由於它能夠明確指定調用哪一個構造方法,並且不須要使用反射機制。可是使用@Provides方法在某些地方有限制,例如:手動建立對象不能在AOP中使用。

正是由於這個緣由,Guice使用了toConstructor()進行綁定,這須要咱們使用反射來選擇構造方法與處理異常。

public class BillingModule extends AbstractModule {
  @Override 
  protected void configure() {
    try {
      bind(TransactionLog.class).toConstructor(
          DatabaseTransactionLog.class.getConstructor(DatabaseConnection.class));
    } catch (NoSuchMethodException e) {
      addError(e);
    }
  }
}

上這個例子中DatabaseTransactionLog類必須有一個帶DatabaseConnection參數的構造方法,該構造方法中不須要使用@Inject註解Guice會自動調用該構造方法。每一條toConstructor()語句建立的綁定,其做用域是獨立的,若是你建立了多個單例綁定而且使用目標類的同一個構造方法,每個綁定仍是擁有各自的實例。

8、及時綁定

當注入器須要某一類型實例的時候,它須要獲取一個綁定。在Module類中的綁定叫作顯示綁定,只要它們可用,注入器就可使用它們。若是須要某一類型實例,但它又不是顯示綁定,那麼注入器將試圖建立一個及時綁定(Just-In-Time bindings),它也被稱爲JIT綁定與隱式綁定。

可用於建立及時綁定的狀況以下:

  1. 有一個合適的構建方法,即非私有,不帶參數或者標有@Inject註解的構造方法,例如:

    public class PayPalCreditCardProcessor implements CreditCardProcessor {
     private final String apiKey;
    
     @Inject
     public PayPalCreditCardProcessor(@Named("PayPal API key") String apiKey) {
       this.apiKey = apiKey;
     }
    }

    Guice不會建立內部類實例除非它有static修飾符,由於內部類含有一個指向外問類的隱式引用,而這個隱式引用沒法注入。

  2. @ImplementedBy

    @ImplementedBy註解於用告訴注入器某類型的缺省實現類型是什麼,這與連接綁定很類似。爲某一類型綁定一子類型以下:

    @ImplementedBy(PayPalCreditCardProcessor.class)
    public interface CreditCardProcessor {
     ChargeResult charge(String amount, CreditCard creditCard)
         throws UnreachableException;
    }

    @ImplementedBy(PayPalCreditCardProcessor.class)等效於下面的bind()語句:

    bind(CreditCardProcessor.class).to(PayPalCreditCardProcessor.class);

    若是某一類型即有bind()語句又有@ImplementedBy註解,則bind()語句優先。使用@ImplementedBy請當心,由於它爲接口添加了編譯時依賴。

  3. @ProvidedBy

    @ProvidedBy註解用於告訴注入器,Provider類的實現是什麼,例如:

    @ProvidedBy(DatabaseTransactionLogProvider.class)
    public interface TransactionLog {
     void logConnectException(UnreachableException e);
     void logChargeResult(ChargeResult result);
    }

    這等價於bind(TransactionLog.class).toProvider(DatabaseTransactionLogProvider.class);
    相似@ImplementedBy註解,若是某個類型既使用了bind()語句,又使用了@ProvidedBy註解,那麼bind()語句優先。

    -------------------------------- END -------------------------------

及時獲取更多精彩文章,請關注公衆號《Java精講》。

相關文章
相關標籤/搜索