Google Guice 用戶手冊之 閱讀筆記
目的:
直接調用new -
直接耦合具體的實現類。這是最不靈活的方式,直接緊密耦合,當需要置換具體實施方式的時候(比如在測試中不能用真正的服務)會遇到麻煩。
使用 Factory
用戶調用工廠 .getInstance() 方法得到具體實現。間接耦合。缺點是每次使用前都要設置工廠,以便得到想要的實現。每個接口都要有相應的工廠。如果對象增加依賴,要記得在每一處需要的地方(比如每個 Unit Test)設置工廠。如果忘記設置/初始化工廠,僅僅在要用到服務的時候才會出錯。
初始化時注入
在初始化一個對象的時候給出所有的依賴對象。好處是(1)間接耦合,對象僅僅關心接口。(2)當對象需要新增加一個依賴的時候,編譯器會強制每個用戶給出該依賴的實現對象。缺點是,現在對象的用戶需要關心所有的初始化和依賴。
Dependency Injection with Guice
首先,是一個配置或者說映射。謝天謝地,Google 也恨 XML。所以,Google 用一個 java class 來做配置模塊。
看起來很簡單。就是把接口映射到具體的實現的類。再來看需要被注入依賴實例的類:
也很簡單,一個 @Inject 就可以了。最后,最終用戶需要從配置模塊生成 Injector,再從 Injector 得到需要的實例。
Bindings
首先是各種 Binding,也就是說如何配置想注入的對象。Module 是由 Bindings 組成的。有以下幾種Binding:
Linked Binding:
例子: bind(Interface.class).to(Implementation.class)
用處: 把實現連接到接口,或者把子類連接到父類(慎用)。甚至可以 A 連接到 B,B 連接到 C 這樣地串聯起來。這樣,當要 A 的時候,得到 C。
Binding Annotations
先定義一個標注
用處:當不同的地方要不同的實例的時候。
Instance Binding
提供一個簡單的實例,而不是去初始化。通常就是簡單類型,做配置用。
在配置模塊里面寫 @Provides 方法。
public class BillingModule extends AbstractModule {
@Override
protected void configure() {
...
}
@Provides
TransactionLog provideTransactionLog() {
// 這個方法生成實例并且返回。
}
}
Provider Bindings
@Provider 寫得太多,模塊就會變得太大,而且太雜亂。這時候就可以寫 Provider。Provider 需要實現這個簡單的接口:
這個最簡單了,不需要指派綁定到的具體實現。這個需要接口指定了默認的 @ImplementedBy (類)或者 @ProvidedBy (Provider類)。
這些是Guice提供的“拿來就用”的綁定,很方便。比如java.util.logging.Logger。Guice 還會幫助做些設置。其他的似乎 Guice 也不推薦或者詳細說明,就不研究了。
就是沒有在模塊里面說明,而是在需要注入的時候決定的。首先是默認構造函數,如果一個類有默認構造函數(就是沒有參數的),那么Guice就用它。其次是接口指定了默認的 @ImplementedBy (類)或者 @ProvidedBy (Provider類)。
Scope
上面講了綁定。下面講 Scope。Scope 就是在什么范圍內可以共享同一個實例。比如@Singleton就在整個應用中使用同一個實例。Scope 可以這樣指定:
標注在實現類上:
默認的 Scope 是每一次 Guice 都生成一個新實例。其他Scope 還有 Singleton, Request, Session。
確定為 Singleton 的實例可能在啟動的時候生成,這樣可以早發現問題,但可能增加啟動時間。
選擇 Scope 要看實例是不是有狀態,該狀態要在什么范圍共享,以及創建實例要花費的代價。 @Singleton 和 @SessionScoped 必須線程安全。而 @RequestScoped 不需要。
注入
注入可以在多處進行。首先,是前面用過的在構造函數中(推薦,編譯器檢查,好測試):
直接調用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);
}
}
@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) {

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

}
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) {
...
}
也可以自己寫帶有參數的標注。先寫標注接口,再寫標注實例,注意實現 equals() 和 hashCode(),就可以用在 annotatedWith 里面了。bind(CreditCardProcessor.class)
.annotatedWith(Names.named("Checkout"))
.to(CheckoutCreditCardProcessor.class);
用處:當不同的地方要不同的實例的時候。
Instance Binding
提供一個簡單的實例,而不是去初始化。通常就是簡單類型,做配置用。
bind(String.class)@Provides Methods
.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() {
// 這個方法生成實例并且返回。
}
}
@Provides 也可以帶有標注,或者有參數的標注比如 @Named("Checkout")。這樣這個標注就會被綁定在這個@Provider方法上了。總之,Guice 以 @Provider 函數返回的類型以及標注來判斷該調用哪個函數來得到實例。
@Provides @PayPal
CreditCardProcessor providePayPalCreditCardProcessor(
@Named("PayPal API key") String apiKey) {
// 具體實現。。。
}
Provider Bindings
@Provider 寫得太多,模塊就會變得太大,而且太雜亂。這時候就可以寫 Provider。Provider 需要實現這個簡單的接口:
public interface Provider<T> {
T get();
}
Untargetted BindingsProvider 也可以有自己的依賴關系。然后,就可以在Binding 里面使用了。public class BillingModule extends AbstractModule {
@Override
protected void configure() {
bind(TransactionLog.class)
.toProvider(DatabaseTransactionLogProvider.class);
}
這個最簡單了,不需要指派綁定到的具體實現。這個需要接口指定了默認的 @ImplementedBy (類)或者 @ProvidedBy (Provider類)。
bind(MyConcreteClass.class);Built-in Bindings
bind(AnotherConcreteClass.class).in(Singleton.class);
這些是Guice提供的“拿來就用”的綁定,很方便。比如java.util.logging.Logger。Guice 還會幫助做些設置。其他的似乎 Guice 也不推薦或者詳細說明,就不研究了。
@InjectJust-in-Time Bindings
public ConsoleTransactionLog(Logger logger) {
this.logger = logger;
}
就是沒有在模塊里面說明,而是在需要注入的時候決定的。首先是默認構造函數,如果一個類有默認構造函數(就是沒有參數的),那么Guice就用它。其次是接口指定了默認的 @ImplementedBy (類)或者 @ProvidedBy (Provider類)。
@ImplementedBy(PayPalCreditCardProcessor.class)
public interface CreditCardProcessor {
ChargeResult charge(String amount, CreditCard creditCard)
throws UnreachableException;
}
@ProvidedBy(DatabaseTransactionLogProvider.class)這種默認的接口實現可以在使用時,由明確的 Binding 取代。而如果沒有被取代,就用默認。
public interface TransactionLog {
void logConnectException(UnreachableException e);
void logChargeResult(ChargeResult result);
}
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注意 Scope 是按照要得到的類型(接口類型)而不是具體實現的類而計算的。比如,Applebees 實現了 Bar 和 Grill 兩個接口,下面將會用兩個 Applebees 實例。
TransactionLog provideTransactionLog() {
...
}
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還可以進行方法攔截。具體不再敘述,看手冊的例子。