Guice是一個(gè)輕量級的DI框架。本文對Guice的基本用法作以介紹。
本文的所有例子基于Guice 3.0
考慮到是入門介紹,本文中并未涉及到AOP相關(guān)內(nèi)容,如有需要還請參考上面鏈接。
首先有一個(gè)需要被實(shí)現(xiàn)的接口:
然后,有一個(gè)實(shí)現(xiàn)該接口的實(shí)現(xiàn)類:
- public interface BillingService {
- /**
- * Attempts to charge the order to the credit card. Both successful and
- * failed transactions will be recorded.
- *
- * @return a receipt of the transaction. If the charge was successful, the
- * receipt will be successful. Otherwise, the receipt will contain a
- * decline note describing why the charge failed.
- */
- Receipt chargeOrder(PizzaOrder order, CreditCard creditCard);
- }
現(xiàn)在接口有了,實(shí)現(xiàn)類也有了,接下來就是如何將接口和實(shí)現(xiàn)類關(guān)聯(lián)的問題了,在Guice中需要定義Module來進(jìn)行關(guān)聯(lián)
- class RealBillingService implements BillingService {
- private final CreditCardProcessor processor;
- private final TransactionLog transactionLog;
- @Inject
- RealBillingService(CreditCardProcessor processor,
- TransactionLog transactionLog) {
- this.processor = processor;
- this.transactionLog = transactionLog;
- }
- @Override
- public Receipt chargeOrder(PizzaOrder order, CreditCard creditCard) {
- ...
- }
- }
- public class BillingModule extends AbstractModule {
- @Override
- protected void configure() {
- /*
- * This tells Guice that whenever it sees a dependency on a TransactionLog,
- * it should satisfy the dependency using a DatabaseTransactionLog.
- */
- bind(TransactionLog.class).to(DatabaseTransactionLog.class);
- /*
- * Similarly, this binding tells Guice that when CreditCardProcessor is used in
- * a dependency, that should be satisfied with a PaypalCreditCardProcessor.
- */
- bind(CreditCardProcessor.class).to(PaypalCreditCardProcessor.class);
- }
- }
好了,現(xiàn)在萬事俱備,就讓我們一起看看怎么使用Guice進(jìn)行依賴注入吧:
- 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.
- */
- RealBillingService billingService = injector.getInstance(RealBillingService.class);
- ...
- }
以上就是使用Guice的一個(gè)完整的例子,很簡單吧,不需要繁瑣的配置,只需要定義一個(gè)Module來表述接口和實(shí)現(xiàn)類,以及父類和子類之間的關(guān)聯(lián)關(guān)系的綁定。本文不對比guice和spring,只是單純介紹Guice的用法。
就是直接把一種類型的class對象綁定到另外一種類型的class對象,這樣,當(dāng)外界獲取TransactionLog時(shí),其實(shí)返回的就是一個(gè)DatabaseTransactionLog對象。當(dāng)然,鏈?zhǔn)浇壎ㄒ部梢源饋恚纾?/p>
這樣,當(dāng)外界請求TransactionLog時(shí),其實(shí)返回的就會是一個(gè)MySqlDatabaseTransactionLog對象。
- protected void configure() {
- bind(TransactionLog.class).to(DatabaseTransactionLog.class);
- bind(DatabaseTransactionLog.class).to(MySqlDatabaseTransactionLog.class);
- }
然后,使用這個(gè)注解去修飾目標(biāo)字段或參數(shù),如:
- 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 {
- @Inject
- public RealBillingService(@PayPal CreditCardProcessor processor,
- TransactionLog transactionLog) {
- ...
- }
- }
- public class RealBillingService implements BillingService {
- @Inject
- @Www
- private CreditCardProcessor processor;
- ...
- }
最后,在我們進(jìn)行鏈?zhǔn)浇壎〞r(shí),就可以區(qū)分一個(gè)接口的不同實(shí)現(xiàn)了,如:
直接使用@Named修飾要注入的目標(biāo),并起個(gè)名字,下面就可以把用這個(gè)名字的注解修飾的接口綁定到目標(biāo)實(shí)現(xiàn)類了
- public class RealBillingService implements BillingService {
- @Inject
- public RealBillingService(@Named("Checkout") CreditCardProcessor processor,
- TransactionLog transactionLog) {
- ...
- }
- bind(CreditCardProcessor.class)
- .annotatedWith(Names.named("Checkout"))
- .to(CheckoutCreditCardProcessor.class);
上面介紹的鏈?zhǔn)浇壎ㄊ前呀涌诘腸lass對象綁定到實(shí)現(xiàn)類的class對象,而實(shí)例綁定則可以看作是鏈?zhǔn)浇壎ǖ囊环N特例,它直接把一個(gè)實(shí)例對象綁定到它的class對象上。
需要注意的是,實(shí)例綁定要求對象不能包含對自己的引用。并且,盡量不要對那種創(chuàng)建實(shí)例比較復(fù)雜的類使用實(shí)例綁定,否則會讓應(yīng)用啟動變慢
在使用基于@Provides方法綁定的過程中,如果方法中創(chuàng)建對象的過程很復(fù)雜,我們就會考慮,是不是可以把它獨(dú)立出來,形成一個(gè)專門作用的類。Guice提供了一個(gè)接口:
實(shí)現(xiàn)這個(gè)接口,我們就會得到專門為了創(chuàng)建相應(yīng)類型對象所需的類:
- public interface Provider {
- T get();
- }
這樣以來,我們就可以在configure方法中,使用toProvider方法來把一種類型綁定到具體的Provider類。當(dāng)需要相應(yīng)類型的對象時(shí),Provider類就會調(diào)用其get方法獲取所需的對象。
- public class DatabaseTransactionLogProvider implements Provider {
- private final Connection connection;
- @Inject
- public DatabaseTransactionLogProvider(Connection connection) {
- this.connection = connection;
- }
- public TransactionLog get() {
- DatabaseTransactionLog transactionLog = new DatabaseTransactionLog();
- transactionLog.setConnection(connection);
- return transactionLog;
- }
- }
其實(shí),個(gè)人感覺在configure方法中使用Provider綁定和直接寫@Provides方法所實(shí)現(xiàn)的功能是沒有差別的,不過使用Provider綁定會使代碼更清晰。而且當(dāng)提供對象的方法中也需要有其他類型的依賴注入時(shí),使用Provider綁定會是更好的選擇。
- bind(MyConcreteClass.class);
- bind(AnotherConcreteClass.class).in(Singleton.class);
如果使用注解綁定的話,就不能用無目標(biāo)綁定,必須指定目標(biāo),即使目標(biāo)是它自己。如:
無目標(biāo)綁定,主要是用于與被@ImplementedBy 或者 @ProvidedBy修飾的類型一起用。如果無目標(biāo)綁定的類型不是被@ImplementedBy 或者 @ProvidedBy修飾的話,該類型一定不能只提供有參數(shù)的構(gòu)造函數(shù),要么不提供構(gòu)造函數(shù),要么提供的構(gòu)造函數(shù)中必須有無參構(gòu)造函數(shù)。因?yàn)間uice會默認(rèn)去調(diào)用該類型的無參構(gòu)造函數(shù)。
- public class BillingModule extends AbstractModule {
- @Override
- protected void configure() {
- try {
- bind(TransactionLog.class).toConstructor(
- DatabaseTransactionLog.class.getConstructor(DatabaseConnection.class));
- } catch (NoSuchMethodException e) {
- addError(e);
- }
- }
- }
這種綁定方式主要用于不方便用注解@Inject修飾目標(biāo)類型的構(gòu)造函數(shù)的時(shí)候。比如說目標(biāo)類型是第三方提供的類型,或者說目標(biāo)類型中有多個(gè)構(gòu)造函數(shù),并且可能會在不同情況采用不同的構(gòu)造函數(shù)。
- @Singleton
- public class ConsoleTransactionLog implements TransactionLog {
- private final Logger logger;
- @Inject
- public ConsoleTransactionLog(Logger logger) {
- this.logger = logger;
- }
- public void logConnectException(UnreachableException e) {
- /* the message is logged to the "ConsoleTransacitonLog" logger */
- logger.warning("Connect exception failed, " + e.getMessage());
- }
需要注意的是Provide方法必須被@Provides所修飾。同時(shí),@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;
- }
- }
這樣一來,只有被@PayPal修飾的CreditCardProcessor對象才會使用provide方法來創(chuàng)建對象,同時(shí)
- @Provides @PayPal
- CreditCardProcessor providePayPalCreditCardProcessor(
- @Named("PayPal API key") String apiKey) {
- PayPalCreditCardProcessor processor = new PayPalCreditCardProcessor();
- processor.setApiKey(apiKey);
- return processor;
- }
- @ImplementedBy(PayPalCreditCardProcessor.class)
- public interface CreditCardProcessor {
- ChargeResult charge(String amount, CreditCard creditCard)
- throws UnreachableException;
- }
等價(jià)于:
- bind(CreditCardProcessor.class).to(PayPalCreditCardProcessor.class);
等價(jià)于:
- @ProvidedBy(DatabaseTransactionLogProvider.class)
- public interface TransactionLog {
- void logConnectException(UnreachableException e);
- void logChargeResult(ChargeResult result);
- }
并且,和@ImplementedBy類似,@ProvidedBy的優(yōu)先級也比較低,是一種默認(rèn)實(shí)現(xiàn),當(dāng)@ProvidedBy和toProvider函數(shù)兩種綁定方式并存時(shí),后者有效。
- bind(TransactionLog.class)
- .toProvider(DatabaseTransactionLogProvider.class);
這里需要注意的是,Guice在創(chuàng)建對象的過程中,無法初始化該類型的內(nèi)部類(除非內(nèi)部類有static修飾符),因?yàn)閮?nèi)部類會有隱含的對外部類的引用,Guice無法處理。
- public class PayPalCreditCardProcessor implements CreditCardProcessor {
- private final String apiKey;
- @Inject
- public PayPalCreditCardProcessor(@Named("PayPal API key") String apiKey) {
- this.apiKey = apiKey;
- }
該接口指明了它的實(shí)現(xiàn)類SayHello
- package guice.test;
- import com.google.inject.ImplementedBy;;
- @ImplementedBy (SayHello.class)
- public interface Talk {
- public void sayHello();
- }
接下來就是屬性注入的例子:
- package guice.test;
- public class SayHello implements Talk{
- @Override
- public void sayHello() {
- System.out.println("Say Hello!");
- }
- }
這里面,指明熟悉Talk類型的bs將會被注入。使用的例子是:
- package guice.test;
- import com.google.inject.Inject;
- public class FieldDI {
- @Inject
- private Talk bs;
- public Talk getBs() {
- return bs;
- }
- }
如果我們沒有用@Inject修飾Talk bs的話,就會得到如下錯誤:
- package guice.test;
- import com.google.inject.Guice;
- import com.google.inject.Injector;
- public class Test {
- public static void main(String[] args) {
- Injector injector = Guice.createInjector(new BillingModule());
- FieldDI fdi = injector.getInstance(FieldDI.class);
- fdi.getBs().sayHello();
- }
- }
Exception in thread "main" java.lang.NullPointerException
如果我們用@Inject修飾Talk bs了,但是Talk本身沒有被@ImplementedBy修飾的話,會得到如下錯誤:
- Exception in thread "main" com.google.inject.ConfigurationException: Guice configuration errors:
- 1) No implementation for guice.test.Talk was bound.
- while locating guice.test.Talk
- for field at guice.test.FieldDI.bs(FieldDI.java:5)
- while locating guice.test.FieldDI
- 1 error
- at com.google.inject.internal.InjectorImpl.getProvider(InjectorImpl.java:1004)
- at com.google.inject.internal.InjectorImpl.getProvider(InjectorImpl.java:961)
- at com.google.inject.internal.InjectorImpl.getInstance(InjectorImpl.java:1013)
- at guice.test.Test.main(Test.java:24)
另外,屬性注入的一種特例是注入provider。如:
其中,Provider的定義如下:
- public class RealBillingService implements BillingService {
- private final Provider processorProvider;
- private final Provider transactionLogProvider;
- @Inject
- public RealBillingService(Provider processorProvider,
- Provider transactionLogProvider) {
- this.processorProvider = processorProvider;
- this.transactionLogProvider = transactionLogProvider;
- }
- public Receipt chargeOrder(PizzaOrder order, CreditCard creditCard) {
- CreditCardProcessor processor = processorProvider.get();
- TransactionLog transactionLog = transactionLogProvider.get();
- /* use the processor and transaction log here */
- }
- }
- public interface Provider {
- T get();
- }
- package guice.test;
- import com.google.inject.Inject;
- public class FieldDI {
- @Inject
- public void setBs(Talk bs) {
- this.bs = bs;
- }
- private Talk bs;
- public Talk getBs() {
- return bs;
- }
- }
下面我們就以@Singleton舉例說明怎么來告訴Guice我們要以@Singleton的方式產(chǎn)生對象:1. 在定義子類型時(shí)聲明
2.在module的configure方法中做綁定時(shí)聲明
- @Singleton
- public class InMemoryTransactionLog implements TransactionLog {
- /* everything here should be threadsafe! */
- }
3.在module的Provides方法里聲明bind(TransactionLog.class).to(InMemoryTransactionLog.class).in(Singleton.class);
- @Provides @Singleton
- TransactionLog provideTransactionLog() {
- ...
- }
這里有一點(diǎn)需要注意的是,如果發(fā)生下面的情況:
這樣一共會生成2個(gè)Applebees對象,一個(gè)給Bar用,一個(gè)給Grill用。如果在上面的配置的情況下,還有下面的配置,
- bind(Bar.class).to(Applebees.class).in(Singleton.class);
- bind(Grill.class).to(Applebees.class).in(Singleton.class);