Google 開源的依賴注入庫,比 Spring 更小更快!

Google開源的一個依賴注入類庫Guice,相比於Spring IoC來講更小更快。Elasticsearch大量使用了Guice,本文簡單的介紹下Guice的基本概念和使用方式。mysql

學習目標sql

 

  • 概述:瞭解Guice是什麼,有什麼特色;
  • 快速開始:經過實例瞭解Guice;
  • 核心概念:瞭解Guice涉及的核心概念,如綁定(Binding)、範圍(Scope)和注入(Injection);
  • 最佳實踐:官方推薦的最佳實踐;

Guice概述數據庫

 

  • Guice是Google開源的依賴注入類庫,經過Guice減小了對工廠方法和new的使用,使得代碼更易交付、測試和重用;
  • Guice能夠幫助咱們更好地設計API,它是個輕量級非侵入式的類庫;
  • Guice對開發友好,當有異常發生時能提供更多有用的信息用於分析;

快速開始api

 

假設一個在線預訂Pizza的網站,其有一個計費服務接口:併發

 

public interface BillingService {
 /**
  * 經過信用卡支付。不管支付成功與否都須要記錄交易信息。
  *
  * @return 交易回執。支付成功時返回成功信息,不然記錄失敗緣由。
  */
  Receipt chargeOrder(PizzaOrder order, CreditCard creditCard);
}

 

 

使用new的方式獲取信用卡支付處理器和數據庫交易日誌記錄器:app

public class RealBillingService implements BillingService {
  public Receipt chargeOrder(PizzaOrder order, CreditCard creditCard) {
    CreditCardProcessor processor = new PaypalCreditCardProcessor();
    TransactionLog transactionLog = new DatabaseTransactionLog();

    try {
      ChargeResult result = processor.charge(creditCard, order.getAmount());
      transactionLog.logChargeResult(result);

      return result.wasSuccessful()
          ? Receipt.forSuccessfulCharge(order.getAmount())
          : Receipt.forDeclinedCharge(result.getDeclineMessage());
     } catch (UnreachableException e) {
      transactionLog.logConnectException(e);
      return Receipt.forSystemFailure(e.getMessage());
    }
  }
}
 

 

使用new的問題是使得代碼耦合,不易維護和測試。好比在UT裏不可能直接用真實的信用卡支付,須要Mock一個CreditCardProcessor。分佈式

相比於new,更容易想到的改進是使用工廠方法,可是工廠方法在測試中仍存在問題(由於一般使用全局變量來保存實例,若是在用例中未重置可能會影響其餘用例)。ide

更好的方式是經過構造方法注入依賴:高併發

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

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

  public Receipt chargeOrder(PizzaOrder order, CreditCard creditCard) {
    try {
      ChargeResult result = processor.charge(creditCard, order.getAmount());
      transactionLog.logChargeResult(result);

      return result.wasSuccessful()
          ? Receipt.forSuccessfulCharge(order.getAmount())
          : Receipt.forDeclinedCharge(result.getDeclineMessage());
     } catch (UnreachableException e) {
      transactionLog.logConnectException(e);
      return Receipt.forSystemFailure(e.getMessage());
    }
  }
}
 

 

對於真實的網站應用能夠注入真正的業務處理服務類:學習

public static void main(String[] args) {
    CreditCardProcessor processor = new PaypalCreditCardProcessor();
    TransactionLog transactionLog = new DatabaseTransactionLog();
    BillingService billingService
        = new RealBillingService(processor, transactionLog);
    ...
}
 

 

中能夠注入Mock類:

public class RealBillingServiceTest extends TestCase {

  private final PizzaOrder order = new PizzaOrder(100);
  private final CreditCard creditCard = new CreditCard("1234", 11, 2010);

  private final InMemoryTransactionLog transactionLog = new InMemoryTransactionLog();
  private final FakeCreditCardProcessor processor = new FakeCreditCardProcessor();

  public void testSuccessfulCharge() {
    RealBillingService billingService
        = new RealBillingService(processor, transactionLog);
    Receipt receipt = billingService.chargeOrder(order, creditCard);

    assertTrue(receipt.hasSuccessfulCharge());
    assertEquals(100, receipt.getAmountOfCharge());
    assertEquals(creditCard, processor.getCardOfOnlyCharge());
    assertEquals(100, processor.getAmountOfOnlyCharge());
    assertTrue(transactionLog.wasSuccessLogged());
  }
}
 

 

那經過Guice怎麼實現依賴注入呢?首先咱們須要告訴Guice若是找到接口對應的實現類,這個能夠經過模塊來實現:

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);
  }
}
 

 

這裏的模塊只須要實現Module接口或繼承自AbstractModule,而後在configure方法中設置綁定(後面會繼續介紹)便可。Spring Boot 構造器參數綁定,這篇推薦你們看下。

而後只需在原有的構造方法中增長@Inject註解便可注入:

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) {
    try {
      ChargeResult result = processor.charge(creditCard, order.getAmount());
      transactionLog.logChargeResult(result);

      return result.wasSuccessful()
          ? Receipt.forSuccessfulCharge(order.getAmount())
          : Receipt.forDeclinedCharge(result.getDeclineMessage());
     } catch (UnreachableException e) {
      transactionLog.logConnectException(e);
      return Receipt.forSystemFailure(e.getMessage());
    }
  }
}
 

 

最後,再看看main方法中是如何調用的:

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

 

綁定

 

鏈接綁定

鏈接綁定是最經常使用的綁定方式,它將一個類型和它的實現進行映射。下面的例子中將TransactionLog接口映射到它的實現類DatabaseTransactionLog。

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

 

鏈接綁定還支持鏈式,好比下面的例子最終將TransactionLog接口映射到實現類MySqlDatabaseTransactionLog。

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

 

註解綁定

經過一個類型可能存在多個實現,好比在信用卡支付處理器中存在PayPal的支付和Google支付,這樣經過鏈接綁定就搞不定。

這時咱們能夠經過註解綁定來實現:

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

public class RealBillingService implements BillingService {

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

 

// 當注入的方法參數存在@PayPal註解時注入PayPalCreditCardProcessor實現
bind(CreditCardProcessor.class).annotatedWith(PayPal.class).to(PayPalCreditCardProcessor.class);

 

 

能夠看到在模塊的綁定時用annotatedWith方法指定具體的註解來進行綁定,這種方式有一個問題就是咱們必須增長自定義的註解來綁定,基於此Guice內置了一個@Named註解知足該場景:

public class RealBillingService implements BillingService {

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

// 當注入的方法參數存在@Named註解且值爲Checkout時注入CheckoutCreditCardProcessor實現
bind(CreditCardProcessor.class).annotatedWith(Names.named("Checkout")).to(CheckoutCreditCardProcessor.class);
 

 

實例綁定

將一個類型綁定到一個具體的實例而非實現類,這個經過是在無依賴的對象(好比值對象)中使用。若是toInstance包含複雜的邏輯會致使啓動速度,此時應該經過@Provides方法綁定。

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方法綁定

模塊中定義的、帶有@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
  CreditCardProcessor providePayPalCreditCardProcessor(@Named("PayPal API key") String apiKey) {
    PayPalCreditCardProcessor processor = new PayPalCreditCardProcessor();
    processor.setApiKey(apiKey);
    return processor;
  }
}
 

 

Provider綁定

若是使用@Provides方法綁定邏輯愈來愈複雜時就能夠經過Provider綁定(一個實現了Provider接口的實現類)來實現。

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

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;
  }
}

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

 

無目標綁定

當咱們想提供對一個具體的類給注入器時就能夠採用無目標綁定。

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

 

構造器綁定

3.0新增的綁定,適用於第三方提供的類或者是有多個構造器參與依賴注入。經過@Provides方法能夠顯式調用構造器,可是這種方式有一個限制:沒法給這些實例應用AOP。

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

 

 

 

範圍

默認狀況下,Guice每次都會返回一個新的實例,這個能夠經過範圍(Scope)來配置。常見的範圍有單例(@Singleton)、會話(@SessionScoped)和請求(@RequestScoped),另外還能夠經過自定義的範圍來擴展。

範圍的註解能夠應該在實現類、@Provides方法中,或在綁定的時候指定(優先級最高):

@Singleton
public class InMemoryTransactionLog implements TransactionLog {
  /* everything here should be threadsafe! */
}

// scopes apply to the binding source, not the binding target
bind(TransactionLog.class).to(InMemoryTransactionLog.class).in(Singleton.class);

@Provides @Singleton
TransactionLog provideTransactionLog() {
    ...
}
 

 

另外,Guice還有一種特殊的單例模式叫飢餓單例(相對於懶加載單例來講):

// Eager singletons reveal initialization problems sooner, 
// and ensure end-users get a consistent, snappy experience. 
bind(TransactionLog.class).to(InMemoryTransactionLog.class).asEagerSingleton();
 

 

 

注入

依賴注入的要求就是將行爲和依賴分離,它建議將依賴注入而非經過工廠類的方法去查找。注入的方式一般有構造器注入、方法注入、屬性注入等。

 

// 構造器注入
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;
  }
}

// 方法注入
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;
  }
}

// 屬性注入
public class DatabaseTransactionLogProvider implements Provider<TransactionLog> {
  @Inject Connection connection;

  public TransactionLog get() {
    return new DatabaseTransactionLog(connection);
  }
}

// 可選注入:當找不到映射時不報錯
public class PayPalCreditCardProcessor implements CreditCardProcessor {
  private static final String SANDBOX_API_KEY = "development-use-only";
  private String apiKey = SANDBOX_API_KEY;

  @Inject(optional=true)
  public void setApiKey(@Named("PayPal API key") String apiKey) {
    this.apiKey = apiKey;
  }
}
 

 

輔助注入

輔助注入(Assisted Inject)屬於Guice擴展的一部分,它經過@Assisted註解自動生成工廠來增強非注入參數的使用。

 

// RealPayment中有兩個參數startDate和amount沒法直接注入
public class RealPayment implements Payment {
  public RealPayment(
        CreditService creditService, // from the Injector
        AuthService authService, // from the Injector
        Date startDate, // from the instance's creator
        Money amount); // from the instance's creator
  }
  ...
}

// 一種方式是增長一個工廠來構造
public interface PaymentFactory {
  public Payment create(Date startDate, Money amount);
}

public class RealPaymentFactory implements PaymentFactory {
  private final Provider<CreditService> creditServiceProvider;
  private final Provider<AuthService> authServiceProvider;

  @Inject
  public RealPaymentFactory(Provider<CreditService> creditServiceProvider,
      Provider<AuthService> authServiceProvider) {
    this.creditServiceProvider = creditServiceProvider;
    this.authServiceProvider = authServiceProvider;
  }

  public Payment create(Date startDate, Money amount) {
    return new RealPayment(creditServiceProvider.get(),
      authServiceProvider.get(), startDate, amount);
  }
}

bind(PaymentFactory.class).to(RealPaymentFactory.class);

// 經過@Assisted註解能夠減小RealPaymentFactory
public class RealPayment implements Payment {
  @Inject
  public RealPayment(
        CreditService creditService,
        AuthService authService,
        @Assisted Date startDate,
        @Assisted Money amount);
  }
  ...
}

// Guice 2.0
//bind(PaymentFactory.class).toProvider(FactoryProvider.newFactory(PaymentFactory.class, RealPayment.class));
// Guice 3.0
install(new FactoryModuleBuilder().implement(Payment.class, RealPayment.class).build(PaymentFactory.class));
 

 

 

最佳實踐

  • 最小化可變性:儘量注入的是不可變對象;
  • 只注入直接依賴:不用注入一個實例來獲取真正須要的實例,增長複雜性且不易測試;
  • 避免循環依賴
  • 避免靜態狀態:靜態狀態和可測試性就是天敵;
  • 採用@Nullable:Guice默認狀況下禁止注入null對象;
  • 模塊的處理必需要快而且無反作用
  • 在Providers綁定中小心IO問題:由於Provider不檢查異常、不支持超時、不支持重試;
  • 不用在模塊中處理分支邏輯
  • 儘量不要暴露構造器

本人免費整理了Java高級資料,涵蓋了Java、Redis、MongoDB、MySQL、Zookeeper、Spring Cloud、Dubbo高併發分佈式等教程,一共30G,須要本身領取。
傳送門:https://mp.weixin.qq.com/s/igMojff-bbmQ6irCGO3mqA

相關文章
相關標籤/搜索