原地址:http://blog.csdn.net/derekjiang/article/details/7231490

一. 概述

Guice是一个轻量级的DI框架。本文对Guice的基本用法作以介绍。

本文的所有例子基于Guice 3.0

本文的很多代码来源于Guice主页:http://code.google.com/p/google-guice/wiki/GettingStarted

考虑到是入门介绍,本文中并未涉及到AOP相关内容,如有需要还请参考上面链接。

二. 举例说明Guice的用法

Guice本身只是一个轻量级的DI框架,首先我们通过一个例子来看看怎么使用Guice。

首先有一个需要被实现的接口:

  1. public interface BillingService {
  2. /**
  3. * Attempts to charge the order to the credit card. Both successful and
  4. * failed transactions will be recorded.
  5. *
  6. * @return a receipt of the transaction. If the charge was successful, the
  7. *      receipt will be successful. Otherwise, the receipt will contain a
  8. *      decline note describing why the charge failed.
  9. */
  10. Receipt chargeOrder(PizzaOrder order, CreditCard creditCard);
  11. }

然后,有一个实现该接口的实现类:

  1. class RealBillingService implements BillingService {
  2. private final CreditCardProcessor processor;
  3. private final TransactionLog transactionLog;
  4. @Inject
  5. RealBillingService(CreditCardProcessor processor,
  6. TransactionLog transactionLog) {
  7. this.processor = processor;
  8. this.transactionLog = transactionLog;
  9. }
  10. @Override
  11. public Receipt chargeOrder(PizzaOrder order, CreditCard creditCard) {
  12. ...
  13. }
  14. }

现在接口有了,实现类也有了,接下来就是如何将接口和实现类关联的问题了,在Guice中需要定义Module来进行关联

  1. public class BillingModule extends AbstractModule {
  2. @Override
  3. protected void configure() {
  4. /*
  5. * This tells Guice that whenever it sees a dependency on a TransactionLog,
  6. * it should satisfy the dependency using a DatabaseTransactionLog.
  7. */
  8. bind(TransactionLog.class).to(DatabaseTransactionLog.class);
  9. /*
  10. * Similarly, this binding tells Guice that when CreditCardProcessor is used in
  11. * a dependency, that should be satisfied with a PaypalCreditCardProcessor.
  12. */
  13. bind(CreditCardProcessor.class).to(PaypalCreditCardProcessor.class);
  14. }
  15. }

好了,现在万事俱备,就让我们一起看看怎么使用Guice进行依赖注入吧:

  1. public static void main(String[] args) {
  2. /*
  3. * Guice.createInjector() takes your Modules, and returns a new Injector
  4. * instance. Most applications will call this method exactly once, in their
  5. * main() method.
  6. */
  7. Injector injector = Guice.createInjector(new BillingModule());
  8. /*
  9. * Now that we've got the injector, we can build objects.
  10. */
  11. RealBillingService billingService = injector.getInstance(RealBillingService.class);
  12. ...
  13. }

以上就是使用Guice的一个完整的例子,很简单吧,不需要繁琐的配置,只需要定义一个Module来表述接口和实现类,以及父类和子类之间的关联关系的绑定。本文不对比guice和spring,只是单纯介绍Guice的用法。

三. 绑定方式的介绍

从上面我们可以看出,其实对于Guice而言,程序员所要做的,只是创建一个代表关联关系的Module,然后使用这个Module即可得到对应关联的对象。因此,主要的问题其实就是在如何关联实现类和接口(子类和父类)。

1. 在自定义的Module类中进行绑定

1.1 在configure方法中绑定

1.1.1 链式绑定

链式绑定是最简单,最直接,也是使用最多的绑定方式。

  1. protected void configure() {
  2. bind(TransactionLog.class).to(DatabaseTransactionLog.class);
  3. }

就是直接把一种类型的class对象绑定到另外一种类型的class对象,这样,当外界获取TransactionLog时,其实返回的就是一个DatabaseTransactionLog对象。当然,链式绑定也可以串起来,如:

  1. protected void configure() {
  2. bind(TransactionLog.class).to(DatabaseTransactionLog.class);
  3. bind(DatabaseTransactionLog.class).to(MySqlDatabaseTransactionLog.class);
  4. }

这样,当外界请求TransactionLog时,其实返回的就会是一个MySqlDatabaseTransactionLog对象。

1.1.2 注解(Annotations)绑定

链式绑定针对于同样的类型都绑定到同一种目标类型时,非常好用,但是对于一个接口有多种实现的时候,链式绑定就不好区分该用哪种实现了。可以把Annotations绑定方式看作是链式绑定的一种扩展,专门用来解决这种同一个接口有多种实现的问题。Annotations绑定又可以分为两种,一种是需要自己写Annotations,另外一种则简化了一些。

1.1.2.1 自己写Annotations的方式

首先,写一个注解

  1. import com.google.inject.BindingAnnotation;
  2. import java.lang.annotation.Target;
  3. import java.lang.annotation.Retention;
  4. import static java.lang.annotation.RetentionPolicy.RUNTIME;
  5. import static java.lang.annotation.ElementType.PARAMETER;
  6. import static java.lang.annotation.ElementType.FIELD;
  7. import static java.lang.annotation.ElementType.METHOD;
  8. @BindingAnnotation @Target({ FIELD, PARAMETER, METHOD }) @Retention(RUNTIME)
  9. public @interface PayPal {}

然后,使用这个注解去修饰目标字段或参数,如:

  1. public class RealBillingService implements BillingService {
  2. @Inject
  3. public RealBillingService(@PayPal CreditCardProcessor processor,
  4. TransactionLog transactionLog) {
  5. ...
  6. }

  1. public class RealBillingService implements BillingService {
  2. @Inject
  3. @Www
  4. private CreditCardProcessor processor;
  5. ...
  6. }

最后,在我们进行链式绑定时,就可以区分一个接口的不同实现了,如:

  1. bind(CreditCardProcessor.class)
  2. .annotatedWith(PayPal.class)
  3. .to(PayPalCreditCardProcessor.class);

这样,被Annotations PayPal?修饰的CreditCardProcessor就会被绑定到目标实现类PayPalCreditCardProcessor。如果有其他的实现类,则可把用不同Annotations修饰的CreditCardProcessor绑定到不同的实现类

1.1.2.2 使用@Named的方式

使用@Named的方式和上面自己写Annotation的方式很类似,只不过做了相应的简化,不再需要自己去写Annotation了。

  1. public class RealBillingService implements BillingService {
  2. @Inject
  3. public RealBillingService(@Named("Checkout") CreditCardProcessor processor,
  4. TransactionLog transactionLog) {
  5. ...
  6. }

直接使用@Named修饰要注入的目标,并起个名字,下面就可以把用这个名字的注解修饰的接口绑定到目标实现类了

  1. bind(CreditCardProcessor.class)
  2. .annotatedWith(Names.named("Checkout"))
  3. .to(CheckoutCreditCardProcessor.class);
1.1.3 实例绑定

上面介绍的链式绑定是把接口的class对象绑定到实现类的class对象,而实例绑定则可以看作是链式绑定的一种特例,它直接把一个实例对象绑定到它的class对象上。

  1. bind(String.class)
  2. .annotatedWith(Names.named("JDBC URL"))
  3. .toInstance("jdbc:mysql://localhost/pizza");
  4. bind(Integer.class)
  5. .annotatedWith(Names.named("login timeout seconds"))
  6. .toInstance(10);

需要注意的是,实例绑定要求对象不能包含对自己的引用。并且,尽量不要对那种创建实例比较复杂的类使用实例绑定,否则会让应用启动变慢

1.1.4 Provider绑定

在下面会介绍基于@Provides方法的绑定。其实Provider绑定是基于@Provides方法绑定的后续发展,所以应该在介绍完基于@Provides方法绑定之后再来介绍,不过因为Provider绑定也是在configure方法中完成的,而本文又是按照绑定的位置来组织的,因为就把Provider绑定放在这了,希望大家先跳到后面看过基于@Provides方法的绑定再回来看这段。

在使用基于@Provides方法绑定的过程中,如果方法中创建对象的过程很复杂,我们就会考虑,是不是可以把它独立出来,形成一个专门作用的类。Guice提供了一个接口:

  1. public interface Provider {
  2. T get();
  3. }

实现这个接口,我们就会得到专门为了创建相应类型对象所需的类:

  1. public class DatabaseTransactionLogProvider implements Provider {
  2. private final Connection connection;
  3. @Inject
  4. public DatabaseTransactionLogProvider(Connection connection) {
  5. this.connection = connection;
  6. }
  7. public TransactionLog get() {
  8. DatabaseTransactionLog transactionLog = new DatabaseTransactionLog();
  9. transactionLog.setConnection(connection);
  10. return transactionLog;
  11. }
  12. }

这样以来,我们就可以在configure方法中,使用toProvider方法来把一种类型绑定到具体的Provider类。当需要相应类型的对象时,Provider类就会调用其get方法获取所需的对象。

其实,个人感觉在configure方法中使用Provider绑定和直接写@Provides方法所实现的功能是没有差别的,不过使用Provider绑定会使代码更清晰。而且当提供对象的方法中也需要有其他类型的依赖注入时,使用Provider绑定会是更好的选择。

1.1.5 无目标绑定

无目标绑定是链接绑定的一种特例,在绑定的过程中不指明目标,如:

  1. bind(MyConcreteClass.class);
  2. bind(AnotherConcreteClass.class).in(Singleton.class);

如果使用注解绑定的话,就不能用无目标绑定,必须指定目标,即使目标是它自己。如:

  1. bind(MyConcreteClass.class).annotatedWith(Names.named("foo")).to(MyConcreteClass.class);
  2. bind(AnotherConcreteClass.class).annotatedWith(Names.named("foo")).to(AnotherConcreteClass.class).in(Singleton.class);

无目标绑定,主要是用于与被@ImplementedBy 或者 @ProvidedBy修饰的类型一起用。如果无目标绑定的类型不是被@ImplementedBy 或者 @ProvidedBy修饰的话,该类型一定不能只提供有参数的构造函数,要么不提供构造函数,要么提供的构造函数中必须有无参构造函数。因为guice会默认去调用该类型的无参构造函数。

1.1.6 指定构造函数绑定

在configure方法中,将一种类型绑定到另外一种类型的过程中,指定目标类型用那种构造函数生成对象。

  1. public class BillingModule extends AbstractModule {
  2. @Override
  3. protected void configure() {
  4. try {
  5. bind(TransactionLog.class).toConstructor(
  6. DatabaseTransactionLog.class.getConstructor(DatabaseConnection.class));
  7. } catch (NoSuchMethodException e) {
  8. addError(e);
  9. }
  10. }
  11. }

这种绑定方式主要用于不方便用注解@Inject修饰目标类型的构造函数的时候。比如说目标类型是第三方提供的类型,或者说目标类型中有多个构造函数,并且可能会在不同情况采用不同的构造函数。

1.1.7 Built-in绑定

Built-in绑定指的是不用程序员去指定,Guice会自动去做的绑定。目前,Guice所支持的Built-in绑定只有对java.util.logging.Logger的绑定。个人感觉,所谓的Built-in绑定,只是在比较普遍的东西上为大家带来方便的一种做法。

  1. @Singleton
  2. public class ConsoleTransactionLog implements TransactionLog {
  3. private final Logger logger;
  4. @Inject
  5. public ConsoleTransactionLog(Logger logger) {
  6. this.logger = logger;
  7. }
  8. public void logConnectException(UnreachableException e) {
  9. /* the message is logged to the "ConsoleTransacitonLog" logger */
  10. logger.warning("Connect exception failed, " + e.getMessage());
  11. }

1.2 在@Provides方法中进行绑定

当你只是需要在需要的时候,产生相应类型的对象的话,@Provides Methods是个不错的选择。方法返回的类型就是要绑定的类型。这样当需要创建一个该类型的对象时,该provide方法会被调用,从而得到一个该类型的对象。

  1. public class BillingModule extends AbstractModule {
  2. @Override
  3. protected void configure() {
  4. ...
  5. }
  6. @Provides
  7. TransactionLog provideTransactionLog() {
  8. DatabaseTransactionLog transactionLog = new DatabaseTransactionLog();
  9. transactionLog.setJdbcUrl("jdbc:mysql://localhost/pizza");
  10. transactionLog.setThreadPoolSize(30);
  11. return transactionLog;
  12. }
  13. }

需要注意的是Provide方法必须被@Provides所修饰。同时,@Provides方法绑定方式是可以和上面提到的注解绑定混合使用的,如:

  1. @Provides @PayPal
  2. CreditCardProcessor providePayPalCreditCardProcessor(
  3. @Named("PayPal API key") String apiKey) {
  4. PayPalCreditCardProcessor processor = new PayPalCreditCardProcessor();
  5. processor.setApiKey(apiKey);
  6. return processor;
  7. }

这样一来,只有被@PayPal修饰的CreditCardProcessor对象才会使用provide方法来创建对象,同时

2. 在父类型中进行绑定

2.1 @ImplementedBy

在定义父类型的时候,直接指定子类型的方式。
这种方式其实和链式绑定的效果是完全一样的,只是声明绑定的位置不同。
和链式绑定不同的是它们的优先级,@ImplementedBy实现的是一种default绑定,当同时存在@ImplementedBy和链式绑定时,链式绑定起作用。

  1. @ImplementedBy(PayPalCreditCardProcessor.class)
  2. public interface CreditCardProcessor {
  3. ChargeResult charge(String amount, CreditCard creditCard)
  4. throws UnreachableException;
  5. }

等价于:

  1. bind(CreditCardProcessor.class).to(PayPalCreditCardProcessor.class);

2.2 @ProvidedBy

在定义类型的时候直接指定子类型的Provider类。

  1. @ProvidedBy(DatabaseTransactionLogProvider.class)
  2. public interface TransactionLog {
  3. void logConnectException(UnreachableException e);
  4. void logChargeResult(ChargeResult result);
  5. }

等价于:

  1. bind(TransactionLog.class)
  2. .toProvider(DatabaseTransactionLogProvider.class);

并且,和@ImplementedBy类似,@ProvidedBy的优先级也比较低,是一种默认实现,当@ProvidedBy和toProvider函数两种绑定方式并存时,后者有效。

3. 在子类型中进行注入

3.1 构造函数注入

在构造函数绑定中,Guice要求目标类型要么有无参构造函数,要么有被@Inject注解修饰的构造函数。这样,当需要创建该类型的对象时,Guice可以帮助进行相应的绑定,从而生成对象。在Guice创建对象的过程中,其实就是调用该类型被@Inject注解修饰的构造函数,如果没要@Inject注解修饰的构造函数,则调用无参构造函数。在使用构造函数绑定时,无需再在Module中定义任何绑定关系。

这里需要注意的是,Guice在创建对象的过程中,无法初始化该类型的内部类(除非内部类有static修饰符),因为内部类会有隐含的对外部类的引用,Guice无法处理。

  1. public class PayPalCreditCardProcessor implements CreditCardProcessor {
  2. private final String apiKey;
  3. @Inject
  4. public PayPalCreditCardProcessor(@Named("PayPal API key") String apiKey) {
  5. this.apiKey = apiKey;
  6. }

3.2 属性注入

属性绑定的目的是告诉Guice,当创建该类型的对象时,哪些属性也需要进行依赖注入。用一个例子来看:
首先,有一个接口:

  1. package guice.test;
  2. import com.google.inject.ImplementedBy;;
  3. @ImplementedBy (SayHello.class)
  4. public interface Talk {
  5. public void sayHello();
  6. }

该接口指明了它的实现类SayHello

  1. package guice.test;
  2. public class SayHello implements Talk{
  3. @Override
  4. public void sayHello() {
  5. System.out.println("Say Hello!");
  6. }
  7. }

接下来就是属性注入的例子:

  1. package guice.test;
  2. import com.google.inject.Inject;
  3. public class FieldDI {
  4. @Inject
  5. private Talk bs;
  6. public Talk getBs() {
  7. return bs;
  8. }
  9. }

这里面,指明熟悉Talk类型的bs将会被注入。使用的例子是:

  1. package guice.test;
  2. import com.google.inject.Guice;
  3. import com.google.inject.Injector;
  4. public class Test {
  5. public static void main(String[] args) {
  6. Injector injector = Guice.createInjector(new BillingModule());
  7. FieldDI fdi = injector.getInstance(FieldDI.class);
  8. fdi.getBs().sayHello();
  9. }
  10. }

如果我们没有用@Inject修饰Talk bs的话,就会得到如下错误:

Exception in thread "main" java.lang.NullPointerException

如果我们用@Inject修饰Talk bs了,但是Talk本身没有被@ImplementedBy修饰的话,会得到如下错误:

  1. Exception in thread "main" com.google.inject.ConfigurationException: Guice configuration errors:
  2. 1) No implementation for guice.test.Talk was bound.
  3. while locating guice.test.Talk
  4. for field at guice.test.FieldDI.bs(FieldDI.java:5)
  5. while locating guice.test.FieldDI
  6. 1 error
  7. at com.google.inject.internal.InjectorImpl.getProvider(InjectorImpl.java:1004)
  8. at com.google.inject.internal.InjectorImpl.getProvider(InjectorImpl.java:961)
  9. at com.google.inject.internal.InjectorImpl.getInstance(InjectorImpl.java:1013)
  10. at guice.test.Test.main(Test.java:24)

另外,属性注入的一种特例是注入provider。如:

  1. public class RealBillingService implements BillingService {
  2. private final Provider processorProvider;
  3. private final Provider transactionLogProvider;
  4. @Inject
  5. public RealBillingService(Provider processorProvider,
  6. Provider transactionLogProvider) {
  7. this.processorProvider = processorProvider;
  8. this.transactionLogProvider = transactionLogProvider;
  9. }
  10. public Receipt chargeOrder(PizzaOrder order, CreditCard creditCard) {
  11. CreditCardProcessor processor = processorProvider.get();
  12. TransactionLog transactionLog = transactionLogProvider.get();
  13. /* use the processor and transaction log here */
  14. }
  15. }

其中,Provider的定义如下:

  1. public interface Provider {
  2. T get();
  3. }

3.3 Setter方法注入

有了上面的基础我们再来看Setter注入就非常简单了,只不过在setter方法上增加一个@Inject注解而已。 还是上面的例子,只是有一点修改:

  1. package guice.test;
  2. import com.google.inject.Inject;
  3. public class FieldDI {
  4. @Inject
  5. public void setBs(Talk bs) {
  6. this.bs = bs;
  7. }
  8. private Talk bs;
  9. public Talk getBs() {
  10. return bs;
  11. }
  12. }

四. 对象产生的Scopes

在默认情况下,每次通过Guice去请求对象时,都会得到一个新的对象,这种行为是通过Scopes去配置的。
Scope的配置使得我们重用对象的需求变得可能。目前,Guice支持的Scope有@Singleton, @SessionScoped, @RequestScoped。
同样,程序员也可以自定义自己的Scope,本文不涉及自定义scope,如果有兴趣,请参考:http://code.google.com/p/google-guice/wiki/CustomScopes

下面我们就以@Singleton举例说明怎么来告诉Guice我们要以@Singleton的方式产生对象:1. 在定义子类型时声明

  1. @Singleton
  2. public class InMemoryTransactionLog implements TransactionLog {
  3. /* everything here should be threadsafe! */
  4. }

2.在module的configure方法中做绑定时声明

 bind(TransactionLog.class).to(InMemoryTransactionLog.class).in(Singleton.class);

3.在module的Provides方法里声明

  1. @Provides @Singleton
  2. TransactionLog provideTransactionLog() {
  3. ...
  4. }

这里有一点需要注意的是,如果发生下面的情况:

  1. bind(Bar.class).to(Applebees.class).in(Singleton.class);
  2. bind(Grill.class).to(Applebees.class).in(Singleton.class);

这样一共会生成2个Applebees对象,一个给Bar用,一个给Grill用。如果在上面的配置的情况下,还有下面的配置,

  1. bind(Applebees.class).in(Singleton.class);

这样一来,Bar和Grill就会共享同样的对象了。

 
4

Google-Guice入门介绍的更多相关文章

  1. Google Guava入门教程

    以下资料整理自网络 一.Google Guava入门介绍 引言 Guava 工程包含了若干被Google的 Java项目广泛依赖 的核心库,例如:集合 [collections] .缓存 [cachi ...

  2. Guice入门

    参考链接:http://www.cnblogs.com/xd502djj/archive/2012/06/25/2561414.html Google Guice范例解说之使用入门 http://co ...

  3. 初识Hadoop入门介绍

    初识hadoop入门介绍 Hadoop一直是我想学习的技术,正巧最近项目组要做电子商城,我就开始研究Hadoop,虽然最后鉴定Hadoop不适用我们的项目,但是我会继续研究下去,技多不压身. < ...

  4. Java 微服务框架 Redkale 入门介绍

    Redkale 功能 Redkale虽然只有1.xM大小,但是麻雀虽小五脏俱全.既可作为服务器使用,也可当工具包使用.作为独立的工具包提供以下功能:1.convert包提供JSON的序列化和反序列化功 ...

  5. Google Guice学习

    学习动力:公司项目使用 官方文档:https://github.com/google/guice/wiki/Motivation 学习阶段:入门 主要部份: 简介 Bindings方式 Scopes设 ...

  6. 依赖注入框架Google Guice 对象图

    GettingStarted · google/guice Wiki https://github.com/google/guice/wiki/GettingStarted sameb edited ...

  7. Google Guice 之绑定1

    绑定和依赖注入区别 绑定,使用时 需要通过 injector 显示获取 依赖注入,只需要显示获取主类,他的依赖是通过@Injector 和 绑定关系 隐式注入的 http://blog.csdn.ne ...

  8. Android CoordinatorLayout 入门介绍

    Android CoordinatorLayout 入门介绍 CoordinatorLayout View 知道如何表现 在 2015 年的 I/O 开发者大会上,Google 介绍了一个新的 And ...

  9. Google Guava入门(一)

    Guava作为Java编程的助手,可以提升开发效率,对Guava设计思想的学习则极大的有益于今后的编程之路.故在此对<Getting Started with Google Guava>一 ...

随机推荐

  1. [转帖] tmux 的使用说明

    之前曾经看过 tmux 的简介 但是一直不会用 这次 看了下 原来是这么处理 不过 用windows 多了 还是感觉鼠标 操作多一些 全键盘操作的习惯 还是没有养成. 原贴地址: https://ww ...

  2. tensorflow的一些基础用法

    TensorFlow是一个采用数据流图,用于数值计算的开源软件库.自己接触tensorflow比较的早,可是并没有系统深入的学习过,现在TF在深度学习已经成了"标配",所以打算系统 ...

  3. ajax调用后台webservice返回JSON字符

    后台代码: [WebMethod] public static string LoginTest(string userCode, string password) { UserManageCente ...

  4. catch/finally中不应使用 writer.flush()

    在开发中遇到了一个问题,关闭流的时候会出现某种莫名其妙的错误.后来一个巧合看到了这个解决方法. 先看问题(知道答案以后,才知道是这里出错了) FileWriter writer = null; Str ...

  5. 【题解】 bzoj1191: [HNOI2006]超级英雄Hero (二分图)

    bzoj1191,懒得复制,戳我戳我 Solution: 二分图最大匹配板子题 Attention: 注意题干中的一句话 只有当选手正确回答一道题后,才能进入下一题,否则就被淘汰. Code: //I ...

  6. 【agc002f】Leftmost Ball(动态规划)

    [agc002f]Leftmost Ball(动态规划) 题面 atcoder 洛谷 题解 我们从前往后依次把每个颜色按顺序来放,那么如果当前放的是某种颜色的第一个球,那么放的就会变成\(0\)号颜色 ...

  7. COCI 2018/2019 CONTEST #2 Solution

    Problem1 Preokret 第一题一定不是什么难题. 第一个问题在读入的时候判断当前时间是不是在1440及以前就行 第二个问题考虑离线处理,由于每个时刻只能最多发生1个事件那么就弄个桶记录每一 ...

  8. sql server 小技巧 集锦

    sql server 小技巧(1) 导入csv数据到sql server sql server 小技巧(2) 删除sql server中重复的数据 sql server 小技巧(3) SQL Serv ...

  9. Problem A: 种树 解题报告

    Problem A: 种树 Description 很久很久以前,一个蒟蒻种了一棵会提问的树,树有\(n\)个节点,每个节点有一个权值,现在树给出\(m\)组询问,每次询问两个值:树上一组点对\((x ...

  10. bzoj4542: [Hnoi2016]大数(莫队)

    这题...离散化...$N$和$n$搞错了...查了$2h$...QAQ 考虑$s[l...r]$,可以由两个后缀$suf[l]-suf[r+1]$得到$s[l...r]$代表的数乘$10^k$得到的 ...