Guice基本用法
本文適合對依賴注入有相對了解的讀者,文章中對于部分名詞未作詳細解釋。對于沒有恰當的中文與之對應的英文內容,遂未翻譯
Guice簡介
Guice 簡介,本文中的內容也是參考該文檔完成,如有不一致,以該文為準。
快速上手
作為示例,我們使用 BillingService,它依賴于 CreditCardProcessor 和 TransactionLog 兩個接口。接下來我們看看如何使用Guice:
class BillingService {private final CreditCardProcessor processor;private final TransactionLog transactionLog;@InjectBillingService(CreditCardProcessor processor, TransactionLog transactionLog) {this.processor = processor;this.transactionLog = transactionLog;}public Receipt chargeOrder(PizzaOrder order, CreditCard creditCard) {...} }我們將會把 PaypalCreditCardProcessor 和 DatabaseTransactionLog 注入BillingService。
Guice 使用 bindings 將類型和實現對應起來。module 是特定的 bindings 的集合。
現在可以編寫main方法了:
public static void main(String[] args) {/** Guice.createInjector() takes your Modules, and returns a new Injector* instance. Most applications will call this method exactly once, in their* main() method.*/Injector injector = Guice.createInjector(new BillingModule());/** Now that we've got the injector, we can build objects.*/BillingService billingService = injector.getInstance(BillingService.class);...}大功告成!!!
Bindings
injector 的職責是確定系統中需要構造哪些對象,解析依賴,裝配對象(將被依賴的對象注入依賴的對象)。那么如何指定依賴的解析方式,答案是使用 bindings 配置你的 injector
創建bindings
繼承 AbstractModule 重寫 configure 方法。在該方法中調用 bind() 便創建了一個binding。完成module之后,調用 Guice.createInjector(),將module作為參數傳入,便可獲得一個injector對象。
Linked Bindings
Linked bindings 將一個實現綁定到一個類型。
下面例子將 DatabaseTransactionLog 綁定到接口 TransactionLog
綁定之后,當你調用 injector.getInstance(TransactionLog.class) 或當injector遇到一個對象依賴與 TransactionLog,它便會使用 DatabaseTransactionLog。鏈接可以建立于接口和其實現類,或者子類和父類之間。Linked bindings 可以鏈式使用。
public class BillingModule extends AbstractModule {@Override protected void configure() {bind(TransactionLog.class).to(DatabaseTransactionLog.class);bind(DatabaseTransactionLog.class).to(MySqlDatabaseTransactionLog.class);} }在這種情況下,當一個對象依賴于 TransactionLog,injector將會返回一個 MySqlDatabaseTransactionLog.
BindingAnnotations
Binding Annotations
有時候,你想要給特定的類型綁定多個實現。例如,你想給 CreditCardProcessor同時綁定 PaypalCreditCardProcessor 和 CheckoutCreditCardProcessor 兩個實現. Guice 通過binding annotation滿足上述需求。注解和類型共同確定了一個唯一的binding。 在這兒,注解和類型構成了Key。
首先我們定義一個注解:
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 {}然后使用我們定義的注解標示需要注入的類型。
public class RealBillingService implements BillingService {@Injectpublic RealBillingService(@PayPal CreditCardProcessor processor,TransactionLog transactionLog) {...}最后我們還需要創建相應的binding。
bind(CreditCardProcessor.class).annotatedWith(PayPal.class).to(PayPalCreditCardProcessor.class);@Named
也并不是必須創建自己的注解,Guice提供了一個內建的注解@Named。用法如下:
public class RealBillingService implements BillingService {@Injectpublic RealBillingService(@Named("Checkout") CreditCardProcessor processor,TransactionLog transactionLog) {...}bind(CreditCardProcessor.class).annotatedWith(Names.named("Checkout")).to(CheckoutCreditCardProcessor.class);因為編譯器不能對String類型進行檢查,所以不建議使用@Named
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);盡量避免給創建起來比較復雜的對象使用 .toInstance 方法,那樣會導致應用啟動比較慢??梢允褂?@Provides 代替該方法。
Provides Methods
當你需要編寫創建對象的代碼,使用 @Provides 方法。該方法只能定義在module中。并且需要使用 @Provides 修飾。他的返回值是一個對象。當Injector需要某個類型的實例時,便會調用相應的@Provides 方法。
public class BillingModule extends AbstractModule {@Overrideprotected void configure() {...}@ProvidesTransactionLog provideTransactionLog() {DatabaseTransactionLog transactionLog = new DatabaseTransactionLog();transactionLog.setJdbcUrl("jdbc:mysql://localhost/pizza");transactionLog.setThreadPoolSize(30);return transactionLog;} }如果 @Provides 方法有binding annotation ,比如@Paypal 或者 @Named("Checkout"),Guice 也是可以的。所有的被依賴對象以參數形式傳入該方法即可。
@Provides @PayPalCreditCardProcessor providePayPalCreditCardProcessor(@Named("PayPal API key") String apiKey) {PayPalCreditCardProcessor processor = new PayPalCreditCardProcessor();processor.setApiKey(apiKey);return processor;}需要注意的是, Guice不允許 @Provides 方法拋出異常。
Provider Bindings
當 @Provides 方法比較復雜時,你也許會考慮將該方法轉移到一個單獨的類中。Provider類繼承Guice的 Provider 接口。 Provider 接口定義如下:
public interface Provider<T> {T get(); }我們的Provider實現類有自己的依賴,所有的依賴是通過被@Inject 修飾的構造函數接收的。
public class DatabaseTransactionLogProvider implements Provider<TransactionLog> {private final Connection connection;@Injectpublic DatabaseTransactionLogProvider(Connection connection) {this.connection = connection;}public TransactionLog get() {DatabaseTransactionLog transactionLog = new DatabaseTransactionLog();transactionLog.setConnection(connection);return transactionLog;} }綁定
public class BillingModule extends AbstractModule {@Overrideprotected void configure() {bind(TransactionLog.class).toProvider(DatabaseTransactionLogProvider.class);}Untargeted Bindings
一些情況下,你需要創建bindings,但是卻不能指定具體的實現。這個對于被@ImplementedBy 或者 @ProvidedBy 修飾的具體類或者類型特別有用。An untargetted binding informs the injector about a type, so it may prepare dependencies eagerly. Untargetted bindings have no to clause, like so:
bind(MyConcreteClass.class); bind(AnotherConcreteClass.class).in(Singleton.class);當指定binding annotation時,必須加上綁定的目標。
bind(MyConcreteClass.class).annotatedWith(Names.named("foo")).to(MyConcreteClass.class); bind(AnotherConcreteClass.class).annotatedWith(Names.named("foo")).to(AnotherConcreteClass.class).in(Singleton.class);Constructor Bindings
有時候, 我們需要綁定一個類型到任意的的構造函數。以下情況會有這種需求:@Inject 注解無法被應用到目標構造函數?;蛘咴擃愋褪且粋€第三方類。或者該類型中有多個構造函數參與依賴注入。
針對這個,Guice 有 @toConstructor() 類型的綁定方式。
JustInTimeBindings
Just-in-time Bindings
當Injector需要一個類型的實例的時候,它需要一個binding。 如果這個binding在一個module中被創建,那么這個binding是顯式binding,此時injector在每次需要該類型實例時,都使用該實例。但是如果Injector需要一個類型的實例,但是這個類型并沒有對應的顯式binding。此時injector會嘗試創建一個Just-in-time binding。也叫JIT binding或者隱式binding。
合格的構造函數
Guice會使用具體類型的可注入構造函數創建binding??勺⑷霕嬙旌瘮敌枰欠莗rivate,無參數的或者該構造函數被 @Inject 修飾。
public class PayPalCreditCardProcessor implements CreditCardProcessor {private final String apiKey;@Injectpublic PayPalCreditCardProcessor(@Named("PayPal API key") String apiKey) {this.apiKey = apiKey;}@ImplementedBy
告訴injector,該類型的默認實現類。
@ImplementedBy(PayPalCreditCardProcessor.class) public interface CreditCardProcessor {ChargeResult charge(String amount, CreditCard creditCard)throws UnreachableException; }上述代碼和一下代碼是等價的:
bind(CreditCardProcessor.class).to(PayPalCreditCardProcessor.class);如果某個類型同時使用 bind() 和 @ImplementedBy,bind() 會生效。
@ProvidedBy
告訴Injector,產生該類型的Provider類
@ProvidedBy(DatabaseTransactionLogProvider.class) public interface TransactionLog {void logConnectException(UnreachableException e);void logChargeResult(ChargeResult result); }上述代碼等價于:
bind(TransactionLog.class).toProvider(DatabaseTransactionLogProvider.class);如果同時使用 @ProvidedBy 和 bind() , bind() 會生效。
Scopes
默認情況下,Guice 每次都會返回一個新創建的對象。不過這也是可以配置的,以便于我們重用實例。
配置Scopes
Guice 使用注解標示Scope。例如:
@Singleton public class InMemoryTransactionLog implements TransactionLog {/* everything here should be threadsafe! */ }@Provides @Singleton TransactionLog provideTransactionLog() {... }上例中,@Singleton 標示該類型只能有一個實例。并且是線程安全的。
Scope也可以通過代碼來配置:
bind(TransactionLog.class).to(InMemoryTransactionLog.class).in(Singleton.class);如果某個類型已經被注解標注了scope,同時還使用bind() 方法配置了scope,那么后者生效。如果一個類型已經被注解配置了scope而你不想那樣,你可以使用 bind() 覆蓋。
預加載的單例
bind(TransactionLog.class).to(InMemoryTransactionLog.class).asEagerSingleton();Injections
構造函數注入
這種情況下,需要用 @Inject 標注構造函數。構造函數同時需要將所依賴的類型作為其參數。通常情況下,都是將傳入的參數復制給類的final成員。
public class RealBillingService implements BillingService {private final CreditCardProcessor processorProvider;private final TransactionLog transactionLogProvider;@Injectpublic RealBillingService(CreditCardProcessor processorProvider,TransactionLog transactionLogProvider) {this.processorProvider = processorProvider;this.transactionLogProvider = transactionLogProvider;}如果沒有 @Inject 標注的構造函數,Guice 會使用共有的午餐構造函數(如果存在)。
方法注入
這種情況下,需要使用@Inject 標注方法,該方法的參數為需要注入的類型。
public class PayPalCreditCardProcessor implements CreditCardProcessor {private static final String DEFAULT_API_KEY = "development-use-only";private String apiKey = DEFAULT_API_KEY;@Injectpublic void setApiKey(@Named("PayPal API key") String apiKey) {this.apiKey = apiKey;}字段注入
這種情況下,需要使用 @Inject 標注字段。
public class DatabaseTransactionLogProvider implements Provider<TransactionLog> {@Inject Connection connection;public TransactionLog get() {return new DatabaseTransactionLog(connection);} }可選的注入
有時候,我們的依賴項不是必須的,如果系統中存在依賴項則注入,如果不存在,也不強制要求注入。這種情況在方法注入和字段注入中都是適用的。 啟用可選注入,只需要使用 @Inejct(optional=true) 標注字段或方法即可。
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;}不過,如果混用可選注入和Just-in-time bindings,可能會產生奇怪的接口。例如:
@Inject(optional=true) Date launchDate;上面代碼中的date總是會被成功注入即使沒有為他創建對應的顯示binding,因為它有無參構造函數,Guice會為他創建Just-in-time bindings。
On-demand注入
方法和字段注入可以用來初始化一個現存的實例。我們可以使用Injector.injectMember()API:
public static void main(String[] args) {Injector injector = Guice.createInjector(...);CreditCardProcessor creditCardProcessor = new PayPalCreditCardProcessor();injector.injectMembers(creditCardProcessor);AOP
Guice 支持方法攔截器。這里直接看一個實例:
假如我們需要禁止在周末調用特定方法
為了標注我們在周末禁止調用的方法,我們定義一個注解類型:
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @interface NotOnWeekends {}然后使用該注解標注我們方法
public class RealBillingService implements BillingService {@NotOnWeekendspublic Receipt chargeOrder(PizzaOrder order, CreditCard creditCard) {...} }現在我們定義我們的攔截器,我們的攔截器需要實現org.aopalliance.intercept.MethodInterceptor 接口。
public class WeekendBlocker implements MethodInterceptor {public Object invoke(MethodInvocation invocation) throws Throwable {Calendar today = new GregorianCalendar();if (today.getDisplayName(DAY_OF_WEEK, LONG, ENGLISH).startsWith("S")) {throw new IllegalStateException(invocation.getMethod().getName() + " not allowed on weekends!");}return invocation.proceed();} }然后配置我們的攔截器
public class NotOnWeekendsModule extends AbstractModule {protected void configure() {/**在這里,我們匹配所有的類,但是只匹配類中有NotOnWeekends的方法*/bindInterceptor(Matchers.any(), Matchers.annotatedWith(NotOnWeekends.class), new WeekendBlocker());} }所有工作就完成了。
注入攔截器
如果需要注入攔截器,使用 `requestInjection` API
public class NotOnWeekendsModule extends AbstractModule {protected void configure() {WeekendBlocker weekendBlocker = new WeekendBlocker();requestInjection(weekendBlocker);bindInterceptor(Matchers.any(), Matchers.annotatedWith(NotOnWeekends.class), weekendBlocker);} }另外一種方式是使用 `Binder.getProvider`,將依賴的內容傳入攔截器的構造函數。
public class NotOnWeekendsModule extends AbstractModule {protected void configure() {bindInterceptor(any(),annotatedWith(NotOnWeekends.class),new WeekendBlocker(getProvider(Calendar.class)));} }END
本人有意進一步閱讀Guice源碼,但是個人能力有限,如有感興趣的讀者,希望可以一起研究。
總結
- 上一篇: Git 忽略编译后文件
- 下一篇: HGE2D引擎按键消息分析(续)