Google開源的一個依賴注入類庫Guice,相比於Spring IoC來講更小更快。Elasticsearch大量使用了Guice,本文簡單的介紹下Guice的基本概念和使用方式。mysql
學習目標sql
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註解的、方法返回值即爲綁定映射的類型。
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; } }
若是使用@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));
最佳實踐
本人免費整理了Java高級資料,涵蓋了Java、Redis、MongoDB、MySQL、Zookeeper、Spring Cloud、Dubbo高併發分佈式等教程,一共30G,須要本身領取。
傳送門:https://mp.weixin.qq.com/s/igMojff-bbmQ6irCGO3mqA