在Guice中,注入器的工做是裝配對象圖,當請求某一類型實例時,注入器根據對象圖來判斷如何建立實例、解析依賴。要肯定如何解析依賴就須要經過配置注入器的綁定方式。java
要建立綁定(Binding
)對象,能夠繼承自AbstractModule
類,而後覆蓋其configure
方法,在方法調用bind()
方法來指來定每一次綁定,這些方法帶有類型檢查,若是你使用了錯誤的類型編譯器就會報告編譯錯誤。若是你已經寫好了Module
類,則建立一個Module
類對象做爲參數傳遞給Guice.createInjector()
方法用於建立一個注入器。mysql
經過Module
對象能夠建立連接綁定(linked bindings)、實例綁定(instance bindings)、@Provides methods、提供者綁定(provider bindings)、構建方法綁定(constructor bindings)與無目標綁定(untargetted bindings)。這些綁定方式統稱爲內置綁定,相對應的還有種及時綁定,若是在解析一個依賴時若是在內置綁定中沒法找到,那麼Guice將會建立一個及時綁定。sql
連接綁定即映射一類型到它的實現類,例如映射TransactionLog
接口到實現類DatabaseTransactionLog
:api
public class BillingModule extends AbstractModule { @Override protected void configure() { bind(TransactionLog.class).to(DatabaseTransactionLog.class); } }
這樣,當你調用injector.getInstance(TransactionLog.class)
方法,或者當注入器碰到TransactionLog
依賴時,就會使用DatabaseTransactionLog
對象。連接是從一類型到它任何的子類型,這包括接口實現類,類的子類;因此以下映射也是能夠的:bind(DatabaseTransactionLog.class).to(MySqlDatabaseTransactionLog.class);
而且連接綁定支持鏈式寫法:安全
public class BillingModule extends AbstractModule { @Override protected void configure() { bind(TransactionLog.class).to(DatabaseTransactionLog.class); bind(DatabaseTransactionLog.class).to(MySqlDatabaseTransactionLog.class); } }
在這種狀況下,當請求一個TransactionLog
類型對象時,注入器將返回一個MySqlDatabaseTransactionLog
對象。ide
某些狀況下你可能想爲同一種類型設置多種綁定。這就能夠經過綁定註解來實現,該註解與綁定的類型用於惟一結識一個綁定,合在一塊兒稱爲Key。示例:ui
package example.pizza; 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 {}
這裏關鍵的是@BindingAnnotation
元註解,當Guice描述到該註解時,就會把PayPal做爲綁定註解。
而後在Module
的configure
方法中使用annotatedWith
語句,以下:bind(CreditCardProcessor.class).annotatedWith(PayPal.class).to(PayPalCreditCardProcessor.class);
這樣就把CreditCardProcessor
映射到了PayPalCreditCardProcessor
。this
用法以下:google
public class RealBillingService implements BillingService { @Inject public RealBillingService(@PayPal CreditCardProcessor processor, TransactionLog transactionLog) { ... } }
還有一種狀況,咱們可使用Guice已經定義好的@Named
註解,例如:url
public class RealBillingService implements BillingService { @Inject public RealBillingService(@Named("Checkout") CreditCardProcessor processor, TransactionLog transactionLog) { ... } }
要綁定一具體名稱,使用Names.named()
來建立一個實現傳給annotatedWith
方法:
bind(CreditCardProcessor.class) .annotatedWith(Names.named("Checkout")) .to(CheckoutCreditCardProcessor.class);
由於編譯器不會對字符串進行檢查,Guice建議咱們少使用@Named
註解,可是我我的認爲,只要本身寫代碼時只要名稱不要寫錯,經過這種方法式是最容易爲同一類型映射多個綁定的,這很相似Spring中的實現方式,Spring的@Service
, @Controller
, @Repository
不就能夠指定名稱嗎?
經過實例綁定咱們能夠爲某類型綁定一個具體的實例,這僅僅適用於這些實例類型沒有其它依賴的狀況,例如值對象:
bind(String.class) .annotatedWith(Names.named("JDBC URL")) .toInstance("jdbc:mysql://localhost/pizza"); bind(Integer.class) .annotatedWith(Names.named("login timeout seconds")) .toInstance(10);
使用方式以下 :
@Inject @Named("JDBC URL") private String url
Guice建議咱們避免使用.toInstance
來建立複雜的對象,由於這會延遲應用啓動。相似地,可使用@Provides
方法實現。
@Provides
方法當使用@Provides
方法建立對象時,該方法必須定義在Module
類中,而且它必須加以@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
或@Named("Checkout")
綁定註解,Guice以綁定註解優先。Guice在調用@Provides
方法以前會先解析該方法的依賴:
@Provides @PayPal CreditCardProcessor providePayPalCreditCardProcessor(@Named("PayPal API key") String apiKey) { PayPalCreditCardProcessor processor = new PayPalCreditCardProcessor(); processor.setApiKey(apiKey); return processor; }
關於異常:Guice不容許在@Provides
方法中拋出異常。若是有異常拋出,那麼異常將會被包裝在ProvisionException
對象中。
若是@Provides
方法愈來愈複雜,咱們可能會想把它們移到一個單獨的類中。一個提供者類實現了Provider
接口,它是一個用於提供值的簡單通用接口。
public interface Provider<T> { T get(); }
若是提供者實現類有其本身的依賴時,能夠經過在其構造方法上添加@Inject
註解進行注入,以保證值安全返回。
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; } }
最後使用.toProvider
語句來綁定到提供者:
public class BillingModule extends AbstractModule { @Override protected void configure() { bind(TransactionLog.class) .toProvider(DatabaseTransactionLogProvider.class); } }
Guice容許咱們建立綁定時不指定目標類,也就是沒有to語句,這對於具體類或者使用了@ImplementedBy
或@ProvidedBy
註解類型頗有用。例如:
bind(MyConcreteClass.class);
bind(AnotherConcreteClass.class).in(Singleton.class);
然而,在使用綁定註解時,咱們依賴必須指定綁定目標,即它是一個具體類,例如:
bind(MyConcreteClass.class) .annotatedWith(Names.named("foo")) .to(MyConcreteClass.class); bind(AnotherConcreteClass.class) .annotatedWith(Names.named("foo")) .to(AnotherConcreteClass.class) .in(Singleton.class);
有些時候你可能須要將某一類型綁定到任一構建方法,例如在@Inject
註解沒法添加到目標類構造方法,其緣由多是這個類是第三方提供的,或者說該類有多個構建方法參與依賴注入。此時@Provides
方法是解決這個問題的最好方案,由於它能夠明確指定調用哪一個構造方法,並且不須要使用反射機制。可是使用@Provides
方法在某些地方有限制,例如:手動建立對象不能在AOP中使用。
正是由於這個緣由,Guice使用了toConstructor()
進行綁定,這須要咱們使用反射來選擇構造方法與處理異常。
public class BillingModule extends AbstractModule { @Override protected void configure() { try { bind(TransactionLog.class).toConstructor( DatabaseTransactionLog.class.getConstructor(DatabaseConnection.class)); } catch (NoSuchMethodException e) { addError(e); } } }
上這個例子中DatabaseTransactionLog
類必須有一個帶DatabaseConnection
參數的構造方法,該構造方法中不須要使用@Inject
註解Guice會自動調用該構造方法。每一條toConstructor()
語句建立的綁定,其做用域是獨立的,若是你建立了多個單例綁定而且使用目標類的同一個構造方法,每個綁定仍是擁有各自的實例。
當注入器須要某一類型實例的時候,它須要獲取一個綁定。在Module
類中的綁定叫作顯示綁定,只要它們可用,注入器就可使用它們。若是須要某一類型實例,但它又不是顯示綁定,那麼注入器將試圖建立一個及時綁定(Just-In-Time bindings),它也被稱爲JIT綁定與隱式綁定。
可用於建立及時綁定的狀況以下:
有一個合適的構建方法,即非私有,不帶參數或者標有@Inject
註解的構造方法,例如:
public class PayPalCreditCardProcessor implements CreditCardProcessor { private final String apiKey; @Inject public PayPalCreditCardProcessor(@Named("PayPal API key") String apiKey) { this.apiKey = apiKey; } }
Guice不會建立內部類實例除非它有static修飾符,由於內部類含有一個指向外問類的隱式引用,而這個隱式引用沒法注入。
@ImplementedBy
@ImplementedBy
註解於用告訴注入器某類型的缺省實現類型是什麼,這與連接綁定很類似。爲某一類型綁定一子類型以下:
@ImplementedBy(PayPalCreditCardProcessor.class) public interface CreditCardProcessor { ChargeResult charge(String amount, CreditCard creditCard) throws UnreachableException; }
@ImplementedBy(PayPalCreditCardProcessor.class)
等效於下面的bind()
語句:
bind(CreditCardProcessor.class).to(PayPalCreditCardProcessor.class);
若是某一類型即有bind()
語句又有@ImplementedBy
註解,則bind()
語句優先。使用@ImplementedBy
請當心,由於它爲接口添加了編譯時依賴。
@ProvidedBy
@ProvidedBy
註解用於告訴注入器,Provider
類的實現是什麼,例如:
@ProvidedBy(DatabaseTransactionLogProvider.class) public interface TransactionLog { void logConnectException(UnreachableException e); void logChargeResult(ChargeResult result); }
這等價於bind(TransactionLog.class).toProvider(DatabaseTransactionLogProvider.class);
相似@ImplementedBy
註解,若是某個類型既使用了bind()
語句,又使用了@ProvidedBy
註解,那麼bind()
語句優先。
-------------------------------- END -------------------------------
及時獲取更多精彩文章,請關注公衆號《Java精講》。