Guice的使用:
public interface Log { void log(); }
定義一個實現類:
public class DbLog implements Log { public void log() { System.out.println("dblog"); } }
定義一個Module管理接口與實現類的綁定關係:
public class LinkedBindingModule extends AbstractModule { @Override protected void configure() { bind(Log.class).to(DbLog.class); } }
Module(模塊)是Guice用來管理一組綁定關係的地方。自定義模塊需要繼承AbstractModule類並覆蓋configure方法,在configure方法中設置綁定關係。
Guice創建時可使用多個模塊,則注入器可以注入多個模塊中指定的綁定關係。
如何使用:
使用@Inject註解標註需要注入的屬性,Guice會根據綁定關係將接口的具體實現類注入。
開啓Guice並獲取Log接口的實現類:
輸出結果:
Guice支持的注入方式
Guice支持3種注入方式:屬性注入,方法注入,構造器注入
上文給出了屬性注入的方式,下面看一下方法注入和構造器注入。
方法注入:
方法注入支持任意方法名,但是建議使用setter方法。
方法注入支持多個參數的注入。
構造器注入:
構造器注入是推薦的注入方式,使用一個構造器注入所有依賴,可以避免忘記注入某個依賴。
儘量使用final修飾屬性,可以避免屬性引用被重新賦值導致的問題,這種問題一般很難被發現。
注意:
1.如果類中有構造器但是沒有標註@Inject,需要保證類中有一個public的無參構造器,Guice使用反射機制調用這個構造器創建實例。
2.如果類中有多個構造器,只能有一個構造器使用@Inject標註。Guice不支持多個構造器標註@Inject。如果有多個構造器都需要注入依賴,則使用下文中的Provider綁定或構造器綁定。
Guice的綁定方式
Guice提供了多種綁定方式,用來解決綁定依賴,以及創建綁定過程中碰到的問題。
1.最簡單的綁定:鏈接綁定
語法:bind(接口類.class).to(實現類.class)
鏈接綁定綁定接口到實現類。任何使用接口的地方都可以得到自動注入的實現類。
鏈接綁定可以被傳遞。上面這種情況,所有請求TransactionLog
的地方,注入器(injector)會注入MySqlDatabaseTransactionLog,而不是DatabaseTransactionLog。
2.一個接口有多個實現類的綁定:註解綁定
藉助註解區分接口的不同實現類。兩種方式:自定義註解或使用Guice提供的@Named註解
自定義註解:
@BindingAnnotation @Target({ FIELD, PARAMETER, METHOD }) @Retention(RUNTIME) public @interface PayPal {}
@BindingAnnotation
告訴Guice這是一個綁定註解。如果在一個位置(FIELD, PARAMETER, METHOD),使用多個綁定註解,Guice會報錯。@Target({FIELD, PARAMETER, METHOD})註解生效的位置
@Retention(RUNTIME)
這個註解在運行時起作用
使用自定義註解:
public class RealBillingService implements BillingService { @Inject public RealBillingService(@PayPal CreditCardProcessor processor, TransactionLog transactionLog) { ... }
Module中綁定註解和特定實現類:
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) { ... }
在Module中聯繫註解和特定實現類
bind(CreditCardProcessor.class) .annotatedWith(Names.named("Checkout")) .to(CheckoutCreditCardProcessor.class);
3.綁定接口的一個具體實例:實例綁定
一般的綁定都是class到class的綁定。Guice提供對綁定具體實例的支持,結合Names註解,可以方便的注入某些參數。
bind(String.class) .annotatedWith(Names.named("JDBC URL")) .toInstance("jdbc:mysql://localhost/pizza"); bind(Integer.class) .annotatedWith(Names.named("login timeout seconds")) .toInstance(10);
注意:
1.實例綁定僅用於自身不包含依賴需要綁定的場景。
2.實例綁定中的實例不應該包含複雜耗時的操作,那樣會使系統啓動變慢。如果有複雜的操作,可以使用下面的綁定:Provides綁定
4.需要代碼方式創建對象,使用:Provide綁定
接口的具體實現類,需要在代碼中new出來或者其他方式創建了對象。在Module中創建一個方法並標註@Provides註解即可。
每當需要注入TransactionLog,注入器就會調用這個方法provideTransactionLog.
考慮另外一種情況:@Provides所在的方法中也需要注入參數怎麼辦?Guice支持!直接在方法參數中標註即可。Guice會在調用@Provides方法前,注入對應依賴。
再考慮另外一種情況:當前方法返回值只是接口的某一個特定實現怎麼辦?Guice支持!直接在方法上標註@PayPal或@Named("PayPal")即可。
注意:@Provides方法不支持拋出異常,所有異常都會被包裝在ProvisionException中。
如果需要拋出特定異常,可以使用Guice的擴展:ThrowingProviders extension @CheckedProvides標註的方法。
@Provides方法只能在Module中定義,如果方法比較複雜,比較多則可以考慮分解到單獨的類中。
Guice提供Provider接口實現這個功能。
接口定義如下:
public interface Provider<T> { T get(); }
Provide類通過泛型支持特定的接口。
這種Provide需要在Module中使用.toProvider 方法綁定聯繫。
注意:使用Provider綁定,無法應用Guice提供的AOP功能。
5.構造器綁定
適用於以下情況:
1.綁定依賴到第三方等無法添加@Inject註解的類
2.類中有多個構造器參與依賴注入,且不使用屬性注入/方法注入時。
以上2種情況都可以用provider,直接使用java代碼創建對象的方式注入。
但是provider直接創建對象無法應用Guice提供的AOP功能.
使用構造器綁定非常簡單,在Module中調用toConstructor方法即可。
TransactionLog綁定的實現類爲DatabaseTransactionLog,且Guice會調用對應的含有一個DatabaseConnection參數的構造器,構造實現類。
DatabaseConnection將根據綁定關係注入正確的實現類,完成依賴注入。
6.內建綁定
有一些綁定是Guice已經準備好的,不需要在Module中指定綁定關係。
- java.util.logging.Logger,Guice自動注入合適的實現類。這個我們一般不用,我們一般用slf4j,log4j等。
-
Injector 注入器
-
Providers 實例提供者
-
TypeLiterals
-
Stage 舞臺,環境 TOOL,DEVELOPMENT,PRODUCTION
-
MembersInjectors 註冊實例的依賴
7.沒有目標(target)的綁定
除了在Module中綁定接口的實現類之外,還可以使用@ImplementedBy
or @ProvidedBy標記接口,提供實現類。
這種情況下,可以不用在Module中指定綁定關係,也可以在Module中指定沒有目標的綁定,此時綁定關係中沒有to語句:
注意:如果帶有標註,則必須指定具體實現類。
8.自動被Guice注入的綁定:實時綁定(Just-in-time Bindings)
在Module中指定的綁定稱爲顯式綁定,如果沒有指定顯式綁定,Guice會嘗試實時綁定(Just-in-time Bindings)。
實時綁定包括3種:除了@ImplementedBy和@ProvidedBy這2種之外,還有一種是通過合適的構造器綁定。
- @ImplementedBy
等同於:
bind(CreditCardProcessor.class).to(PayPalCreditCardProcessor.class);
注意:使用這個標註不是一個很好的設計。一個接口不應該知道它的實現類。
- @ProvidedBy
等同於:
bind(TransactionLog.class) .toProvider(DatabaseTransactionLogProvider.class);
注意:如果顯式綁定和實時綁定同時存在,則以顯式綁定爲準。
合適的構造器
合適的構造器是指:類中的構造器必須有@Inject標註
實例的作用範圍(Scope)
幾種常見的作用範圍
- No Scope 每次創建新實例
- Singleton 整個應用中只有一個
- RequestScoped 每個servlet請求創建一個實例
- SessionScoped 每個Servlet會話創建一個實例
- 自定義範圍 自定義
應用作用範圍,使其生效
Guice默認指定No Scope.
下面看下如何指定單例:
1.使用@Singleton註解
2.顯式指定
3.使用@Provides標註的方法
注意:實現Provider接口的提供者,指定Singleton無效。
如果一個實現類繼承2個接口,且應用中只需要一個,如何綁定?
在實現類中指定@Singleton註解或指定實現類爲單例:bind(Applebees.class).in(Singleton.class);
作用範圍爲RequestScoped ,SessionScoped 比較簡單,直接指定即可:
bind(UserPreferences.class) .toProvider(UserPreferencesProvider.class) .in(ServletScopes.REQUEST);
“飢渴”的單例(延遲加載)
延遲加載會加快開發,測試等進度,但是也容易隱藏問題。
使用asEagerSingleton方法,可以強制Guice立即實例化類並注入:
bind(TransactionLog.class).to(InMemoryTransactionLog.class).asEagerSingleton();
在不同的環境下,延遲加載的作用機制:
選擇一個作用範圍
如果是一個有狀態的對象,必須明顯的指出它的作用範圍,比如整個應用有效,則標註@Singleton,每個請求有效則標註@RequestScoped等等。
如果一個對象是無狀態的,並且創建新對象開銷不大,那麼就讓Guice創建新的。
單例模式比較重要,可以減低對象的創建減少垃圾回收,但是單例模式的類的初始化需要線程同步。所以非單例的對象引用單例的對象,需要添加volatile參數,保證初始化完成後對所有線程可見,且不會被多次執行。
單例模式常在以下情況下使用:
- 有狀態對象,比如配置信息
- 創建比較昂貴的對象
- 連接資源的對象,比如數據庫連接
作用範圍與線程安全性
單例和SessionScoped的對象必須是線程安全的。所有注入到線程安全的對象的其他實例也必須是線程安全的。
RequestScoped的對象不是線程安全的,比較容易犯的錯誤是單例對象注入了非線程安全的對象,比如SessionScoped的對象注入了RequestScoped的對象。
好的解決方案是範圍寬的對象需要注入範圍窄的對象時,注入Provider,祥見下面注入Provider.
注入Provider
Provider可以通過get方法創建自定義的對象,Guice可以通過實時綁定,自動注入Provider。
注入Provider一般用來解決幾種問題:需要一個對象的多個實例時,按條件延遲加載時,混合多個作用範圍時。
需要一個對象的多個實例時:
有時候在一個方法中需要一個對象的多個實例,比如一次收費的摘要和詳情應該保存在不同的記錄中。
按條件延遲加載時:
有時候需要在某些條件下才需要獲取對象:比如收費失敗時才獲取數據庫連接並記錄。
這種情況一般用於比較昂貴的對象或連接資源的對象。
混合多個作用範圍時:
一個對象注入了範圍比自己窄的對象是錯誤的!
比如一個單例的對象需要一個請求範圍的當前用戶對象,Guice注入的對象只是當前用戶的,這個注入不變,下一個用戶使用的就不是自己的用戶信息了。
使用provider可以安全的解決這個問題。
Guice對AOP的支持
Guice通過支持方法攔截器來支持AOP.
爲了支持方法攔截,必須要有2個組件支持:匹配器(Matcher)和攔截器(Interceptors)
注意:爲了攔截到方法層面,匹配器需要2個配合,一個用來匹配類層次,另外一個用來匹配要攔截的那個方法。
明白了以上2個組件之後,使用AOP就非常簡單了:
定義一個類實現org.aopalliance.intercept.MethodInterceptor攔截器接口,然後在Module中綁定攔截器即可。
Module中指定綁定關係和匹配器:
匹配器支持的幾種匹配:
小結: