Guice文档翻译【Part1-用户指南】

  • 此文为本人学习guice的过程中,翻译的官方文档,如有不对的地方,欢迎指出。另外还有一些附件说明、吐槽、疑问点,持续学习,持续更新~
  • 有一些不太好翻译或者没太看懂的地方,就放原文啦。
  • 内容比较枯燥,如果刚入门的话,可以先了解一下什么是IoC,转到IoC/DI?
  • Guice官方文档(用户指南)在此:https://github.com/google/guice/wiki/Motivation

1. User's Guide

1.1 Motivation

Motivation(动机)

在应用程序开发过程中,把所有东西都整合到一起是很讨厌的一种体验。好在有很多方法能够帮助你将数据、服务和展现层关联起来,为了让你更清晰的看出这些方法的区别, 我们为一个披萨在线订购网站编写了“开账单”的代码:

public interface BillingService {
  /**
   * BillingService 接口定义了账单服务要做的事情:它将尝试通过信用卡支付pizza订单,然后无论交易是否成功,都这笔交易都将被记录下来
   * @return 返回交易账单。若支付成功,交易账单记录一笔成功的交易信息,否则将记录交易失败原因。
   */
  Receipt chargeOrder(PizzaOrder order, CreditCard creditCard);
}

接下来,我们将实现这个接口,并编写对应的测试用例,进行单元测试。在单元测试中,我们需要定义一个 FakeCreditCardProcessor ,以避免使用真实的信用卡进行支付。

注:FakeCreditCardProcessor 顾名思义:“假的信用卡处理器”。如果不用 FakeCreditCardProcessor ,那么就要每次进行单元测试时,都需要从真实的信用卡中刷钱,qa小姐姐哭晕在厕所~

Direct constructor calls(直接构造)

首先考虑最简单直接的实现方式:在 chargeOrder 中 new 一个 credit card process 和一个transaction logger 来进行信用卡扣款和交易记录操作,代码大概会是下面这个样子:

public class RealBillingService implements BillingService {
  public Receipt chargeOrder(PizzaOrder order, CreditCard creditCard) {

    // 看这里,new 一个Processor实例,和一个 TransactionLog 实例 ,then do something
    CreditCardProcessor processor = new PaypalCreditCardProcessor();
    TransactionLog transactionLog = new DatabaseTransactionLog();

    try {
      ChargeResult result = processor.charge(creditCard, order.getAmount());
      transactionLog.logChargeResult(result);
      return result.wasSuccessful()
          ? Receipt.forSuccessfulCharge(order.getAmount())
          : Receipt.forDeclinedCharge(result.getDeclineMessage());
     } catch (UnreachableException e) {
      transactionLog.logConnectException(e);
      return Receipt.forSystemFailure(e.getMessage());
    }
  }
}

上面的代码模块化和可测试性都非常差。最直接的体现是,编译时 chargeOrder 方法内部直接依赖 credit card processor 实例(new 了一个 PaypalCreditCardProcessor),这意味着无论测试用例怎么写,都会从真实 PayPal 的信用卡中扣款。除此之外,对“信用卡扣款请求被拒绝”、或“信用卡扣款服务不可用”这样的case,没办法测试。

Factories(工厂)

为了解决上面的问题,可以引入一个工厂类,来简单完成“客户端”和“实现类”之间的解耦。

首先定义一个简单的工厂类,提供 setInstancegetInstance 静态方法,允许外部调用方通过 setInstancegetInstance 方法设置或获取一个接口实现类的实例,这个实现类可以是真实的,也可以是伪造的。
工厂定义代码示例如下:

public class CreditCardProcessorFactory {
  private static CreditCardProcessor instance;
  // 设置一个 CreditCardProcessor 接口类型实例
  public static void setInstance(CreditCardProcessor processor) {
    instance = processor;
  }
  // 获取 CreditCardProcessor 接口类型实例
  public static CreditCardProcessor getInstance() {
    if (instance == null) {
      return new SquareCreditCardProcessor();
    }   
    return instance;
  }
}

然后,在我们的 BillingService 里,只需要将 new 替换成工厂的 getInstance() 就行了,代码示例如下:

public class RealBillingService implements BillingService {
  public Receipt chargeOrder(PizzaOrder order, CreditCard creditCard) {

    // 这里,使用工厂的 getInstance() 方法分别获取 processor 和 transactionLog 实例
    CreditCardProcessor processor = CreditCardProcessorFactory.getInstance();
    TransactionLog transactionLog = TransactionLogFactory.getInstance();

    try {
      ChargeResult result = processor.charge(creditCard, order.getAmount());
      transactionLog.logChargeResult(result);
      return result.wasSuccessful()
          ? Receipt.forSuccessfulCharge(order.getAmount())
          : Receipt.forDeclinedCharge(result.getDeclineMessage());
     } catch (UnreachableException e) {
      transactionLog.logConnectException(e);
      return Receipt.forSystemFailure(e.getMessage());
    }
  }
}

有了这个工厂,编写一个正确的测试用例就容易多了:

public class RealBillingServiceTest extends TestCase {
  // 伪造 订单信息,和信用卡信息
  private final PizzaOrder order = new PizzaOrder(100);
  private final CreditCard creditCard = new CreditCard("1234", 11, 2010);

  // 定义 内存交易记录器 和 假信用卡处理器
  private final InMemoryTransactionLog transactionLog = new InMemoryTransactionLog();
  private final FakeCreditCardProcessor processor = new FakeCreditCardProcessor();

  // 在 setUp() 方法里,通过工厂方法,将上面的两个实例 set 到工厂里
  @Override public void setUp() {
    TransactionLogFactory.setInstance(transactionLog);
    CreditCardProcessorFactory.setInstance(processor);
  }

  // 定义 tearDown() 方法,从工厂里释放实例。
  @Override public void tearDown() {
    TransactionLogFactory.setInstance(null);
    CreditCardProcessorFactory.setInstance(null);
  }

  // 测试
  public void testSuccessfulCharge() {
    RealBillingService billingService = new RealBillingService();
    Receipt receipt = billingService.chargeOrder(order, creditCard);
    assertTrue(receipt.hasSuccessfulCharge());
    assertEquals(100, receipt.getAmountOfCharge());
    assertEquals(creditCard, processor.getCardOfOnlyCharge());
    assertEquals(100, processor.getAmountOfOnlyCharge());
    assertTrue(transactionLog.wasSuccessLogged());
  }
}

这段代码看起来怎么样呢?呃,非常的“臃肿”!全局变量持有 mock 实现类的管理权限,这就使得我们就必须非常小心的进行实现类的 “设置” 和 “销毁” 操作。如果 tearDown() 销毁操作失败,全局变量将继续指向我们的测试实例。这可能会导致其他测试用例出现问题,还有可能会影响到多个测试用例的并行执行。

但是最大的问题是,类之间的依赖关系是隐藏在代码里的。如果我们需要在业务逻辑中新加一个 CreditCardFraudTracker 依赖,就不得不重新进行测试用例的编写来发现代码中可能存在的问题。

另外,一旦我们在生产环境中忘记对服务中的工厂进行初始化,这个问题会被隐藏掉,直到有收费请求时,才会暴露出来。而随着应用程序业务需求的不断增长和变更,作为“临时保姆”角色的“工厂们”会越来越无力从心,反而成为应用程序中沉重的负担。

对这种代码,尽管QA有能力通过各种手段发现其中的质量问题,但实际上,我们可以做的更好。

PS:梳理一下例子中用到的接口和类:
BillingService,是登记账单的接口。
RealBillingService,是 BillingService 的实现类。
CreditCardProcessor,是信用卡处理器接口。
PaypalCreditCardProcessor,是 CreditCardProcessor 的实现类,是真实的 Paypal 信用卡处理器,它会从真实的信用卡中扣钱。
FakeCreditCardProcessor,是 CreditCardProcessor 的实现类,是mock的信用卡处理器,不会使用真实的信用卡,测试使用。
TransactionLog,是交易记录器接口。
DatabaseTransactionLog ,是 TransactionLog 接口的实现类,是真实的数据库交易记录器,会将交易信息记录到数据库中。
InMemoryTransactionLog ,是 TransactionLog 接口的实现类,是内存交易记录器,会将交易信息记录到内存中,不落库,测试使用。
关系BillingService实现类chargeOrder() 方法,需要使用 CreditCardProcessor实现类 从信用卡中扣钱,用 TransactionLog实现类 记录交易信息。

Dependency Injection(依赖注入)

依赖注入和工厂类似,也是一种设计模式。依赖注入的核心原则是:将行为与依赖解析过程分开。在我们的例子中,RealBillingService 本身不负责查找TransactionLogCreditCardProcessor ,而是通过构造函数接受相关参数,然后将其作为内部成员变量使用,此时,依赖关系需要Service的调用方管理。

public class RealBillingService implements BillingService {
  private final CreditCardProcessor processor;
  private final TransactionLog transactionLog;

  // 带参构造函数,接受CreditCardProcessor 和 TransactionLog 类型参数
  public RealBillingService(CreditCardProcessor processor, 
      TransactionLog transactionLog) {
    this.processor = processor;
    this.transactionLog = transactionLog;
  }
  public Receipt chargeOrder(PizzaOrder order, CreditCard creditCard) {
    try {
      ChargeResult result = processor.charge(creditCard, order.getAmount());
      transactionLog.logChargeResult(result);
      return result.wasSuccessful()
          ? Receipt.forSuccessfulCharge(order.getAmount())
          : Receipt.forDeclinedCharge(result.getDeclineMessage());
     } catch (UnreachableException e) {
      transactionLog.logConnectException(e);
      return Receipt.forSystemFailure(e.getMessage());
    }
  }
}

我们不再需要任何工厂类,也可以扔掉 setUptearDown 方法来简化测试用例。

public class RealBillingServiceTest extends TestCase {

  private final PizzaOrder order = new PizzaOrder(100);
  private final CreditCard creditCard = new CreditCard("1234", 11, 2010);

  private final InMemoryTransactionLog transactionLog = new InMemoryTransactionLog();
  private final FakeCreditCardProcessor processor = new FakeCreditCardProcessor();

  public void testSuccessfulCharge() {
    RealBillingService billingService
        = new RealBillingService(processor, transactionLog);
    Receipt receipt = billingService.chargeOrder(order, creditCard);
    assertTrue(receipt.hasSuccessfulCharge());
    assertEquals(100, receipt.getAmountOfCharge());
    assertEquals(creditCard, processor.getCardOfOnlyCharge());
    assertEquals(100, processor.getAmountOfOnlyCharge());
    assertTrue(transactionLog.wasSuccessLogged());
  }
}

现在,当 BillingService 的依赖项有增加或删除时,编译器都会提醒我们哪些测试用例需要fix。依赖关系向上暴露到API接口层。

不幸的是,BillingService 不再需要自己管理 TransactionLog 和 CreditCardProcessor 依赖,但 BillingService 的调用方需要却需要管理 BillingService 依赖了。此时,可以再次应用这个模式来解决问题,即:BillingService 调用方也定义一个带参构造方法,接受类型为 BillingService 的参数。

然而对顶层类来说,它可能更需要一个框架帮助它进行依赖管理,而不是不断的进行 new、constructor 操作:

  public static void main(String[] args) {
    CreditCardProcessor processor = new PaypalCreditCardProcessor();
    TransactionLog transactionLog = new DatabaseTransactionLog();
    BillingService billingService
        = new RealBillingService(processor, transactionLog);
    ...
  }

Dependency Injection with Guice(基于guice的依赖注入)
依赖注入模式使得代码的模块化和可测性更好,guice 能够帮助你更容易的写出这样的代码。为了在我们的记账例子中使用 guice,首先需要学习如何将 接口 和 接口的实现 map 起来。Guice 使用 module 进行配置,只要定义一个类实现 Module 接口就可以了:

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

然后,在 RealBillingService 的构造器前面加上 @Inject 注解,这个注解用于引导 guice 使用它。guice 会检查所有加了 @Inject 注解的构造方法,并为这些构造方法中的每一个参数找到对应类型的值。

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) {
    try {
      ChargeResult result = processor.charge(creditCard, order.getAmount());
      transactionLog.logChargeResult(result);
      return result.wasSuccessful()
          ? Receipt.forSuccessfulCharge(order.getAmount())
          : Receipt.forDeclinedCharge(result.getDeclineMessage());
     } catch (UnreachableException e) {
      transactionLog.logConnectException(e);
      return Receipt.forSystemFailure(e.getMessage());
    }
  }
}

最后,就可以开始客户端代码的编写了。Guice.createInjector将按照Module中定义的规则生成一个 Injector,然后我们可以在Injector中获取任何已配置的类实例。

  public static void main(String[] args) {
    Injector injector = Guice.createInjector(new BillingModule());
    BillingService billingService = injector.getInstance(BillingService.class);
    ...
  }

1.2 Getting Started

Getting Started

使用 DI 模式,对象可以通过它们的构造方法接受依赖项的注入。为了构造某一个对象,你需要先完成它的依赖项们的构造。然后,你可能还需要构造依赖项的依赖项,也就是说,你实际上要构造的是一个 对象关系图

手工建立对象关系图显然是个吃力不讨好的活儿:工作量大、易出错、难测试。相反,Guice 可以让帮你建立对象关系图,前提是:你需要配置好你想要个什么样的图。

举个例子,BillingService 构造方法接受它依赖的两个接口 CreditCardProcessorTransactionLog 参数。为了让例子看的更清楚,BillingService 构造由guice完成,并给 BillingService 加了 @Inject 注解:

class BillingService {
  private final CreditCardProcessor processor;
  private final TransactionLog transactionLog;
  @Inject
  BillingService(CreditCardProcessor processor, 
      TransactionLog transactionLog) {
    this.processor = processor;
    this.transactionLog = transactionLog;
  }
  public Receipt chargeOrder(PizzaOrder order, CreditCard creditCard) {
    ...
  }
}

现在我们想用 PaypalCreditCardProcessorDatabaseTransactionLog 构造一个 BillingService 实例。guice 使用 bindings 来映射接口及其实现类之间的关系,一个Module是一系列流式绑定操作语句的集合:

public class BillingModule extends AbstractModule {
  @Override 
  protected void configure() {

     /*
      * 这段是告诉guice,只要看到TransactionLog依赖,就给它一个DatabaseTransactionLog实例
      */
    bind(TransactionLog.class).to(DatabaseTransactionLog.class);

     /*
      * 类似的,只要看到CreditCardProcessor依赖,就给一个PaypalCreditCardProcessor实例
      */
    bind(CreditCardProcessor.class).to(PaypalCreditCardProcessor.class);
  }
}

这些 module 构造了injector构造器,也就是 guice 的对象关系图生成器。先创建 injector ,然后就可以使用这个 injector 生成 BillingService 实例了:

 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.
     */
    BillingService billingService = injector.getInstance(BillingService.class);
    ...
  }

1.3 Bindings

Bindings
注射器 injector 的工作是:生成对象关系图。当你请求一个指定类型的实例时, injector 会自动构建实例、解析依赖,并进行组装操作。为了了解 injector 的依赖解析过程,请先使用 bindings 来配置 injector 规则。

Creating Bindings
具体做法是:继承 AbstractModule 并实现 configure 方法。在方法内,调用 bind() 指明每一条绑定链路。这些方法都是带类型检查的,所以如果你使用了错误的类型,编译器将报错。modules 创建完成后,就可以将它们作为参数传给 Guice.createInjector ,guice 会根据 modules 定义的规则生成 injector 注入器。

使用 module,可以创建:linked bindingsinstance bindings@Provides methodsprovider bindingsconstructor bindingsuntargetted bindings

More Bindings
除了通过 module 定义 injector 绑定规则外,injector 还有内置绑定 build-in bindings。当外部请求一个依赖项,但 injector 找不到该对象的实例时,injector 会试图创建一个即时绑定just-in-time binding。注入器 injector 也包括 providers 和 其他 bindings 的绑定配置(The injector also includes bindings for the providers of its other bindings.)。

1.3.1 Linked Bindings(链式绑定)

链式绑定 linked bindings 用于绑定一个类型/接口道它的实现类。下面这个例子就是把接口 TransactionLog 绑定到它的具体实现类 DatabaseTransactionLog

public class BillingModule extends AbstractModule {
  @Override 
  protected void configure() {
    bind(TransactionLog.class).to(DatabaseTransactionLog.class);
  }
}

现在,当你调用 injector.getInstance(TransactionLog.class) 或者 injector 遇到某一个依赖 TransactionLog 的类时,它都会使用 DatabaseTransactionLog 替代 TransactionLog。从类型 链接到任意子类型,比如接口和接口的实现类,或类和类的继承子类。你也可以使用链式绑定,把 DatabaseTransactionLog 绑定到它的子类上去,比如:bind(DatabaseTransactionLog.class).to(MySqlDatabaseTransactionLog.class);

Linked bindings 也可以串连起来:

public class BillingModule extends AbstractModule {
  @Override 
  protected void configure() {
    bind(TransactionLog.class).to(DatabaseTransactionLog.class);
    bind(DatabaseTransactionLog.class).to(MySqlDatabaseTransactionLog.class);
  }
}

在这种情况下,injector 会把所有 TransactionLog 替换为 MySqlDatabaseTransactionLog

1.3.2 Binding Annotations(绑定注解)

Binding Annotations
有的时候,你可能希望给一个类型做多个绑定。比如,你可能需要一个 Paypal credit card processor 和一个 Google Checkout processor。Bindings 提供一个可选绑定注解 ,用于实现该功能。注解和类型唯一标识一个绑定规则,注解+类型,合在一起叫做key。

定义绑定注解只需要两行代码(附加几个引用)。把这个放在 .java 文件里,或者放在它要注解的类型内部。

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 ,这是一个绑定注解。如果试图对同一个对象,标记多个注解,Guice会报错。
  • @Target({FIELD, PARAMETER, METHOD}) 是为了让你的注解对用户友好。它可以防止 @PayPal 注解被意外的应用到无法使用的地方。
  • @Retention(RUNTIME) 使得这个注解可以在运行时被获取。

为了使用注解绑定,需要把这个注解应用到注入参数上:

public class RealBillingService implements BillingService {
  @Inject
  public RealBillingService(@PayPal CreditCardProcessor processor,
      TransactionLog transactionLog) {
    ...
  }

最后,使用这个注解创建一个绑定。在 bind() 使用 annotatedWith 声明:

   bind(CreditCardProcessor.class)
        .annotatedWith(PayPal.class)
        .to(PayPalCreditCardProcessor.class);
  • 同一个注解,可以标记不同类型的参数。(@Paypal 可以标记 CreditCardProcessor,也可以标记TransactionLog
  • 但是,一个参数只能被一个注解标记。(RealBillingService 构造方法中的 CreditCardProcessor 只能被一个注解标记,否则会编译失败)

@Named
Guice 有一个内置注解 @Named 接收一个字符串参数:

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

由于编译器不检查字符串,所以我们建议尽量少用 @Named 。自己定义专属注解能够提供更好的类型检查安全性。

Binding Annotations with Attributes(带参绑定注解)

Guice 支持带参绑定注解(例如 @Named )。在极少数情况下,当你需要这样的注解的时候(并且不能使用 @Provides 方法),我们建议你使用 Auto/Value 项目中的 @AutoAnnotation 注解,因为正确的实现一个带参绑定注解是很容易出错的。如果你决定要手动创建一个自定义带参绑定注解,那么请务必正确的实现 Annotation Javadoc 中说明的规范实现 equals()hashCode() 。然后将对应的实例传给 annotatedWith() 绑定子句。

PS:Auto/Value项目 应该是指google的另一个项目 auto

1.3.3 Instance Bindings(实例绑定)

你可以把一个类型绑定到它的具体实例上(类和类实例)。这一般适用于类型本身不依赖任何其他类型的场景,例如值对象:

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

注意不要使用 .toInstance 绑定一个复杂的对象,因为它会降低程序启动速度。如果有必要的话,请使用 @Provides 方法来做这件事情。

  • 链式绑定,绑定的是 接口 和 接口的实现类。
  • 实例绑定,绑定的是 类型 和 该类型的值对象(类和类实例也可以,但是这个类不要太复杂)。

1.3.4 @Provides Methods(@Provides 方法)

@Provides Methods
当你需要代码帮你自动创建对象的时候,可以使用 @Provides 方法。这个方法必须在 module 内定义,并带有 @Provides 注解,方法返回的类型就是绑定类型。当 injector 需要一个该类型的实例的时候,就会调用该方法。

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 以绑定注解优先,绑定注解配置找不到的话,才找 @Provides 方法。 @Provides 方法可以以参数形式接受依赖注入,injector 会在调用 @Provides 方法之前,先解析该方法的方法的依赖。

  @Provides @PayPal
  CreditCardProcessor providePayPalCreditCardProcessor(
      @Named("PayPal API key") String apiKey) {
    PayPalCreditCardProcessor processor = new PayPalCreditCardProcessor();
    processor.setApiKey(apiKey);
    return processor;
  }

Throwing Exceptions
Guice 不允许在 @Provides 方法内部抛出异常。如果 @Provides 方法内部有异常抛出,则该异常将被包装成 ProvisionException 类型。在 @Provides 方法内抛出任何类型(运行时/检查时)的异常都是不好的实践。如果你有一些不得已的原因非要在 @Provides 方法中抛出异常,你可以使用 ThrowingProviders extension@CheckedProvides 方法。

1.3.5 Provider Bindings(Provider绑定)

Provider Bindings

当你开始使用 @Provides 方法编写更复杂的代码时,你可以考虑把这些 @Provides方法 提到一个单独的类里,继承 Provider接口即可。Provider 接口是一个简单通用的提供getter方法的接口:

public interface Provider<T> {
  T get();
}

Provider 实现类一般通过 @Inject 注解标注过的构造函数接收相关依赖,并通过实现 Provider 接口来返回类型安全的复杂对象实例:

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 子句来完成 provider 绑定。

public class BillingModule extends AbstractModule {
  @Override
  protected void configure() {
    bind(TransactionLog.class)
        .toProvider(DatabaseTransactionLogProvider.class);
  }

如果你的 provider 很复杂,记得进行测试。

Throwing Exceptions
Guice 不允许在 Providers 内部抛出异常。运行时异常会被包装为ProvisionExceptionCreationException,通常这种异常会影响到 injector 的创建。如果你因为某种原因需要在 Provider 内抛出异常,可以使用 ThrowingProviders extension

1.3.6 Untargeted Bindings(无目标绑定)

你可以在创建绑定的时候不指定目标。这对具体实现类,和 @ImplementedBy@ProvidedBy 标记的接口类型非常有用。无目标绑定讲这个类型的存在告知 injector,从而使得 injector 可以对相关依赖进行即时预加载。

无目标绑定没有 to 子句:

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

当你的绑定规则带有注解配置的时候,就算它本身就是实现类也必须带 to 子句,此时 绑定关系的两端是同一个实现类,举例:

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

1.3.7 Constructor Bindings(构造器绑定)

Constructor Bindings
Guice 3.0 新特性。

某些场景下,你可能需要把某个类型绑定到任意一个构造函数上。以下情况会有这种需求:1、 @Inject 注解无法被应用到目标构造函数;2、目标类是一个第三方类;3、目标类有多个构造函数参与DI。

@Provides methods @Provides 方法是解决该问题的最佳方案,直接调用你需要的目标构造函数,不需要做反射,也不会踩到反射相关的陷阱。但是 @Provides 方法有它的局限性,手工创建对象不能再 AOP 中使用。

PS:不是很理解 manually constructed instances do not participate in AOP 的原因。

为了解决这个问题,guice 提供了 toConstructor() bindings ,它需要你指定要使用的确切的某个目标构造函数,并处理 "constructor annot be found" 异常:

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 必须有一个只带1个 DatabaseConnection 类型参数的构造函数)。此时,DatabaseTransactionLog 的构造函数不需要标注 @Inject 注解。guice 会自动查找并调用指定的构造函数,来完成绑定。

每一个 toConstructor() 子句创建的绑定,其作用域是独立的,如果你创建了多个单例绑定并且使用的是目标类的同一个构造方法,那么每一个绑定还是各自持有一个实例。

  • 当你直接指定 injector 去调用类的某个构造函数进行实例获取的时候,就不需要再在该类的构造函数前标记 @Inject 注解了。前面Provider的例子也是这样。
  • 如果你需要某个类实例成为单例的话,写一个绑定就ok了,不要做多余的事情。

1.3.8 Built-in Bindings(内置绑定)

Built-in Bindings
除了显示绑定和 just-in-time bindings,剩下的绑定都属于injector的内置绑定。这些绑定只能由injector自己创建,不允许外部调用。

Loggers
Guice 有一个 java.util.logging.Logger 的内置绑定,旨在保存一些日志模板。The binding automatically sets the logger's name to the name of the class into which the Logger is being injected..

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

The Injector
在框架代码里,有时你可能直到运行时才知道你需要的类型是什么。在这种特殊情况下,你需要注入injector。当你注入injector的时候,程序不会进行injector的依赖检查,因此尽量不要用这种方法。

PS:没有很懂,inject injector是怎么玩的?

Providers
For every type Guice knows about, it can also inject a Provider of that type. Injecting Providers describes this in detail.

TypeLiterals
Guice has complete type information for everything it injects. If you're injecting parameterized types, you can inject a TypeLiteral<T> to reflectively tell you the element type.

PS:这个怎么翻译都很怪,大概意思就是说,如果你的接口/类型本身带参,那么就可以用 TypeLiteral<T> 来描述接口/类型的构成元数据类型。其中T是你自定义的复杂类型/接口。

The Stage
Guice 内置一个 stage 枚举类型,用于区分开发环境和生产环境。

这个枚举类型叫做Stage,有3个枚举值:TOOL、DEVELOPMENT、PRODUCTION。

MembersInjectors
When binding to providers or writing extensions, you may want Guice to inject dependencies into an object that you construct yourself. To do this, add a dependency on a MembersInjector<T> (where T is your object's type), and then call membersInjector.injectMembers(myNewObject).

1.3.9 Just-In-Time Bindings(即时绑定)

由Guice 自动创建绑定。

Just-in-time Bindings
当 injector 需要某一个类型的实例的时候,它需要获取一个绑定。在Module类中的绑定叫做显式绑定,只要他们可用,injector 就会在任何时候使用它们。如果需要某一类型的实例,但是又没有显式绑定,那么injector将会试图创建一个即时绑定(Just-in-time Bindings),也被称为JIT绑定 或 隐式绑定。

Eligible Constructors
Guice 可以使用某个具体类的 injectable constructor 可注入构造函数 来创建该类的实例。这个构造函数要么是非私有不带参的,要么是 @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 的作用是告诉 injector 它所标记的类型对应的默认实现类型是哪一个。从行为上看,@ImplementedBy 的作用比较像链式绑定,为某个类型绑定其子类型:

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

上面的注解和下面的 bind() 子句等价:

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

如果某个类型,既有 bind() 字句(作为第一个参数),又有 @ImplementedBy 注解标记,那么会使用 bind() 的配置。即:这个注解推荐的默认实现,是可以被 bind() 配置覆盖的。请小心使用 @ImplementedBy 注解,因为它在接口和实现类之间添加了编译时依赖。

疑问:所以和链式绑定到底有啥本质上的区别呢?

@ProvidedBy
@ProvidedBy 注解用于告诉 injector 它所标记的类型要使用哪一个Provider进行实例创建:

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

上面的注解等价于:

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

@ImplementedBy 相似,如果使用该注解的同时配置了 bind() ,则以 bind() 配置为准。

1.4 Scopes

Scopes
默认情况下,Guice 每次都提供一个新的对象实例给 getInstance() 调用方。这个作用域可以通过 scopes 来进行配置。作用域允许你复用实例:应用程序整个生命周期内(@Singleton),一个 session 内(@SessionScoped),一个请求内(@RequestScoped)。Guice 包含了一个 servlet 扩展,用于定义 web app 的作用域。Custom scopes can be written for other types of applications.

Applying Scopes
Guice 使用注解来定义作用域。为某个类型的实例定义作用域的方法是:直接将 scope 注解加在其实现类的类定义前。 As well as being functional, this annotation also serves as documentation.
例如, @Singleton 注解意味着必须要保证这个类是线程安全的。

@Singleton
public class InMemoryTransactionLog implements TransactionLog {
  /* everything here should be threadsafe! */
}

作用域也可以在 bind 声明中定义:

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

还可以在 @Providers 方法上加注解定义作用域:

  @Provides @Singleton
  TransactionLog provideTransactionLog() {
    ...
  }

如果某个类型的作用域配置有冲突(多种方式配置了作用域),那么以 bind() 配置为准。如果某个类型上你配置了你不想要的作用域,那么你可以在 bind() 语句中配置作用域为 Scopes.NO_SCOPE 就可以覆盖掉类型上的作用域配置。

在链式绑定里,作用域应用到 source 类型,而不是 target。假设有一个类 Applebees,同时继承了 BarGrill 接口,此时 Applebees 可以定义两个绑定实例,一个是绑定 Bar ,另一个是绑定 Grill

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

这两个bind语句的作用域限定都是对绑定类型(BarGrill)的,而不是Applebees。如果你需要要 Applebees 只能创建一个实例,那么可以使用 @Singleton 来标记类定义,或者再加一个绑定语句:

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

有了这个绑定定义后,前面的两个 .in(Singleton.class) 就是不必要的了。
in() 子句可以接受作用域注解(例如:RequestScoped.class)作为参数,也接受 Scope 实例作为参数(例如:ServletScopes.REQUEST)。

  bind(UserPreferences.class)
      .toProvider(UserPreferencesProvider.class)
      .in(ServletScopes.REQUEST);

注解做参数是首选的,因为它允许 modules 在不同类型的应用程序内重用。例如:定义一个 @RequestScoped 对象,它在webapp程序中一个http请求一个实例,在api服务中一个rpc调用一个实例。

Eager Singletons

  • 单例模式有两种:饿汉模式,懒汉模式。
    • 饿汉模式是指,在类加载时就完成初始化。这种模式下,类加载慢,但是获取对象的速度快。(需要加同步锁)
    • 懒汉模式是指,在类加载时不初始化,第一次请求的时候才初始化。这种模式下,类加载速度较快,但运行时获取对象速度慢。
  • 这个 eager-singletons 对应的是饿汉模式; lazy-singletons 对应的是懒汉模式。

Guice 有一个特殊的句法来定义“饿汉模式单例”:

  bind(TransactionLog.class).to(InMemoryTransactionLog.class).asEagerSingleton();

饿汉模式单例能够更早的发现问题,保证终端用户得到一致的、优雅的使用体验。懒汉模式单例能够加快 编辑-编译-运行 的开发周期。使用 Stage 枚举值来指定需要使用哪种单例模式。

PRODUCTION DEVELOPMENT
.asEagerSingleton() eager eager
.in(Singleton.class) eager lazy
.in(Scopes.SINGLETON) eager lazy
@Singleton eager* lazy
  • Guice 默认都使用饿汉模式加载单例。这些单例都是在你的 modules 中定义过的类型,还有这些类型的相关依赖。

Choosing a scope
如果某个对象是有状态的,那么其作用域应该是显而易见的。单个应用内唯一配置 @Singleton,单个请求中唯一配置 @RequestScoped 等等。如果某个对象是无状态的,并且创建开销不那么昂贵,那么可以不必指定作用域。如果不指定作用域的话,Guice 会在每一次需要对象实例的时候都创建一个新的。

Singletons are popular in Java applications but they don't provide much value, especially when dependency injection is involved. Although singletons save object creation (and later garbage collection), initialization of the singleton requires synchronization; getting a handle to the single initialized instance only requires reading a volatile.

单例在以下场景最有用:

  • 有状态的对象,例如配置或计数器。
  • 对象构建开销大的。
  • 对象绑定到某些资源的,比如数据库连接池。

Scopes and Concurrency(作用域和并发)
使用 @Singleton@SessionScoped 标记的类必须是线程安全的,同时,注入到这些类中的依赖,也必须是线程安全的。
Minimize mutability to limit the amount of state that requires concurrency protection.

@RequestScoped 对象不需要关注线程安全问题。Guice不允许 @ Singleton@ SessionScoped 对象依赖 @ RequestScoped 对象,会直接报错。如果你需要在很小的作用域内使用某个对象,那就可以给这个对象注入一个 Provider 。

疑问:为啥?Provider注入和其他模式注入产生的对象作用域有啥区别吗?

1.5 Injections

Guice 是怎么帮你完成对象初始化的呢?

Injections
DI 模式将行为与依赖解析过程分离开来。不同于直接在工厂查找依赖项,DI 建议将依赖从外部传递进来。把依赖从外部传到对象内部的过程就叫做 “注入 injection”。

Constructor Injection(构造子注入)
构造子注入将注入与实例化放到一起。为了使用构造子注入,你需要使用 @Inject 注解标记类的构造方法,这个构造方法需要接受类依赖作为参数。大多数构造子将会把接收到的参数分派给内部成员变量。

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

如果类定义中没有 @Inject 标注的构造函数,Guice 会使用一个 public 无参构造方法(如果存在的话)。注解能够明确指出哪些类型参与了 DI 依赖注入(原文:Prefer the annotation, which documents that the type participates in dependency injection.)。

构造子注入方式便于进行单元测试。如果你的类在单个构造函数内接受所有的依赖项,那么你在构建对象时不会遗漏任何一个依赖。当类中引入了新的依赖时,相关调用链路会断掉(原文:all of the calling code conveniently breaks)!修复编译错误后,就能够确认一切都ok了。

Method Injection(方法注入)
Guice 可以向标注了 @Inject 的方法中注入依赖。依赖项以参数的形式传给方法,Guice 会在调用注入方法前完成依赖项的构建。注入方法可以有任意数量的参数,并且方法名对注入操作不会有任何影响(即:你不必使用setXXX的格式进行命名,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 Injection(字段注入)
Guice 支持使用 @Inject 注解标记字段。这是最简洁的注入方式,但是不可测试。

public class DatabaseTransactionLogProvider implements Provider<TransactionLog> {
  @Inject Connection connection;

  public TransactionLog get() {
    return new DatabaseTransactionLog(connection);
  }
}

不要在 final 字段上加注解,which has weak semantics

Optional Injections(可选注入)
有的时候,你可能需要一个依赖项存在则进行注入,不存在则不注入,这样是比较方便的。此时可以使用方法注入或字段注入来做这件事,当依赖项不可用的时候Guice 就会忽略这些注入。如果你需要配置可选注入的话,使用 @Inject(optional = true) 注解就可以了:

public class PayPalCreditCardProcessor implements CreditCardProcessor {
  private static final String SANDBOX_API_KEY = "development-use-only";

  private String apiKey = SANDBOX_API_KEY;

  @Inject(optional=true)
  public void setApiKey(@Named("PayPal API key") String apiKey) {
    this.apiKey = apiKey;
  }

同时使用可选注入和JIT绑定可能会产生意外的结果。
例如,下面的代码,即使 Date 没有被显示绑定,也会总是被注入到字段中。这是因为 Date 有一个public无参构造方法,它正好能够被用来做JIT绑定。

  @Inject(optional=true) Date launchDate;

也就是说,即使Date是optional的,但是Date有一个public无参构造方法,所以Guice能够使用它JIT构建Date实例,所以无论怎样都会有Date注入的,那个optional=true就没啥实际含义了。

On-demand Injection(按需注入)
方法注入和字段注入可以可以用来初始化现有实例,你可以使用 Injector.injectMembers API:

  public static void main(String[] args) {
    Injector injector = Guice.createInjector(...);
    
    CreditCardProcessor creditCardProcessor = new PayPalCreditCardProcessor();
    injector.injectMembers(creditCardProcessor);

疑问:什么场景需要这么干?

Static Injections(静态注入)
When migrating an application from static factories to Guice, it is possible to change incrementally. Static injection is a helpful crutch here. It makes it possible for objects to partially participate in dependency injection, by gaining access to injected types without being injected themselves. Use requestStaticInjection() in a module to specify classes to be injected at injector-creation time:

  @Override public void configure() {
    requestStaticInjection(ProcessorFactory.class);
    ...
  }

Guice 会自动注入带 @Inject 注解的静态成员到类中:

class ProcessorFactory {
  @Inject static Provider<Processor> processorProvider;

  /**
   * @deprecated prefer to inject your processor instead.
   */
  @Deprecated
  public static Processor getInstance() {
    return processorProvider.get();
  }
}

Static members will not be injected at instance-injection time. This API is not recommended for general use because it suffers many of the same problems as static factories: it's clumsy to test, it makes dependencies opaque, and it relies on global state.

PS:不建议使用静态注入,偷个懒不翻译了

Automatic Injection
Guice 会对以下情形做自动注入:

  • 在绑定语句里,通过 toInstance() 注入实例。
  • 在绑定语句里,通过 toProvider() 注入 Provider 实例。这些对象会在注入器创建的时候被创建并注入容器。如果它们需要满足其他启动注入,Guice 会在它们被使用前将他们注入进去。

1.5.1 Injecting Providers

Injecting Providers
在常规依赖注入中,每一个类型都仅仅能获得依赖项的某1个实例。 RealBillingService 获得一个 CreditCardProcessor 实例和一个 TransactionLog 实例。但有时候你可能需要某一个类型的多个实例。此时可以提供一个 provider 给 Guice。Provider 在每次 get() 方法被调起时都生成一个新的值:

public interface Provider<T> {
  T get();
}

Provider 类型是可参数话的,例如 Provider<TransactionLog>Provider<CreditCardProcessor> 。任何可以注入 value 的地方,都可以使用 provider for value 替代。


public class RealBillingService implements BillingService {
  private final Provider<CreditCardProcessor> processorProvider;
  private final Provider<TransactionLog> transactionLogProvider;

  @Inject
  public RealBillingService(Provider<CreditCardProcessor> processorProvider,
      Provider<TransactionLog> 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 */
  }
}

对于任何绑定、注解,或者什么否没有,injector 注入器都会给它的 Provider 提供一个内置绑定。

Providers for multiple instances
当你需要同一类型的多个实例时,可以使用 Provider
。假设,当pizza 订单支付失败时,你需要保存一份摘要和一份明细清单,通过使用Provider,你可以在任何你需要的时候获取一个新的记录:

public class LogFileTransactionLog implements TransactionLog {

  private final Provider<LogFileEntry> logFileProvider;

  @Inject
  public LogFileTransactionLog(Provider<LogFileEntry> logFileProvider) {
    this.logFileProvider = logFileProvider;
  }

  public void logChargeResult(ChargeResult result) {
    LogFileEntry summaryEntry = logFileProvider.get();
    summaryEntry.setText("Charge " + (result.wasSuccessful() ? "success" : "failure"));
    summaryEntry.save();

    if (!result.wasSuccessful()) {
      LogFileEntry detailEntry = logFileProvider.get();
      detailEntry.setText("Failure result: " + result);
      detailEntry.save();
    }
  }

Providers for lazy loading
如果你需要一个构建代价很高的依赖,那么你可以使用 provider 对这些依赖进行懒加载。如果你不是一直都需要用到这个依赖的话,那么这种方法这会对你非常有用:

public class DatabaseTransactionLog implements TransactionLog {
  
  private final Provider<Connection> connectionProvider;

  @Inject
  public DatabaseTransactionLog(Provider<Connection> connectionProvider) {
    this.connectionProvider = connectionProvider;
  }

  public void logChargeResult(ChargeResult result) {
    /* only write failed charges to the database */
    if (!result.wasSuccessful()) {
      Connection connection = connectionProvider.get();
    }
  }

Providers for Mixing Scopes
依赖一个作用域范围很小的对象可能会报错。假设,你有一个 @Singleton transactionLog,需要依赖一个 @ RequestScoped 的当前用户。加入你直接把 用户信息注入到transactionLog,就乱套了,因为每一个请求的用户信息是不一样的。而 Provider可以按需注入,所以它们能够帮你安全的将作用域不同的依赖绑定到一起:

@Singleton
public class ConsoleTransactionLog implements TransactionLog {
  
  private final AtomicInteger failureCount = new AtomicInteger();
  private final Provider<User> userProvider;

  @Inject
  public ConsoleTransactionLog(Provider<User> userProvider) {
    this.userProvider = userProvider;
  }

  public void logConnectException(UnreachableException e) {
    failureCount.incrementAndGet();
    User user = userProvider.get();
    System.out.println("Connection failed for " + user + ": " + e.getMessage());
    System.out.println("Failure count: " + failureCount.incrementAndGet());
  }

1.6 AOP

Guice 的拦截方法

Aspect Oriented Programming(面向切面编程)
除了依赖注入,Guice 也支持方法拦截。这个特性允许你编写一段代码,用于在某些特定方法执行前执行。它适用于关注切面(“aspects”)的场景,比如:事务、安全和日志记录。拦截器从切面的角度看待问题,而不是对象,所以这种用法被称为“面向切面编程(AOP)”。

Most developers won't write method interceptors directly; but they may see their use in integration libraries like Warp Persist. Those that do will need to select the matching methods, create an interceptor, and configure it all in a module.

Matcher 是一个简单的接口,要么接收一个值,要么拒绝一个值。在 Guice AOP 中,你需要2个 matcher:一个用来定义哪些类参与 AOP;另一个用来定义这些类的哪些方法参与 AOP。为了简化这项工作,这里有一个 factory class 可以满足该场景:

任何时候,当某一个匹配的方法被调用的时候,都会执行 MethodInterceptors ,它们会检查调用操作:方法、参数、接收的实例,然后执行切面逻辑,然后委托到底层方法。最后,MethodInterceptors 可以检查返回值、异常或其他返回信息。由于拦截器可能被应用到很多方法上,并处理相关的请求,因此,拦截器必须是有效且非入侵性的。

Example: Forbidding method calls on weekends
为了演示拦截器方法在Guice中的使用,我们举个例子,要求拒绝周末的pizza订单。送货员只在周一到周五工作,所以我们需要防止pizza在不能送货时被订购。类似这种结构的,都可以使用AOP进行授权管理。

为了将选择方法标记为工作日,我们定义一个注解:

@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD)
@interface NotOnWeekends {}

然后将这个注解应用到一个需要拦截的方法上:

public class RealBillingService implements BillingService {

  @NotOnWeekends
  public Receipt chargeOrder(PizzaOrder order, CreditCard creditCard) {
    ...
  }
}

接下来,通过实现 rg.aopalliance.intercept.MethodInterceptor 接口定义拦截器。当请求通过授权,可以转到底层方法的时候,调用 invocation.proceed() 来完成请求下发:

public class WeekendBlocker implements MethodInterceptor {
  public Object invoke(MethodInvocation invocation) throws Throwable {
    Calendar today = new GregorianCalendar();
    if (today.getDisplayName(DAY_OF_WEEK, LONG, ENGLISH).startsWith("S")) {
      throw new IllegalStateException(
          invocation.getMethod().getName() + " not allowed on weekends!");
    }
    return invocation.proceed();
  }
}

最后,进行相关的配置。在这里为需要拦截的类和方法定义 matchers。在这个例子里,我们会匹配所有标记了 @NotOnWeekends 注解的类:

public class NotOnWeekendsModule extends AbstractModule {
  protected void configure() {
    bindInterceptor(Matchers.any(), Matchers.annotatedWith(NotOnWeekends.class), 
        new WeekendBlocker());
  }
}

把它们放到一起,(然后等到星期六),执行这段代码就会发现,我们的订单会被拦截并拒绝了:

Exception in thread "main" java.lang.IllegalStateException: chargeOrder not allowed on weekends!
    at com.publicobject.pizza.WeekendBlocker.invoke(WeekendBlocker.java:65)
    at com.google.inject.internal.InterceptorStackCallback.intercept(...)
    at com.publicobject.pizza.RealBillingService$$EnhancerByGuice$$49ed77ce.chargeOrder(<generated>)
    at com.publicobject.pizza.WeekendExample.main(WeekendExample.java:47)

** Limitations**
方法拦截是通过运行时生成字节码来实现的。Guice 通过重写方法来动态创建一个子类应用到拦截器中。如果你的代码所应用的平台不支持字节码生成(例如Android),那么 Guice 不能为你提供 AOP 支持。

这种方法对需要被拦截的类和方法提出以下要求:

  • 类必须是公共的或包内私有的。
  • 类必须是非final的。
  • 方法必须是public, package-private or protected
  • 方法必须是非final的。
  • 实例必须是由 Guice 创建的(通过 @Inject 注解或无参构造函数)。Guice 的AOP 不会对 非Guice 创建的实例进行拦截。

Injecting Interceptors

如果你需要向一个拦截器中注入依赖,可以使用 requestInjection API:

public class NotOnWeekendsModule extends AbstractModule {
  protected void configure() {
    WeekendBlocker weekendBlocker = new WeekendBlocker();
    requestInjection(weekendBlocker);
    bindInterceptor(Matchers.any(), Matchers.annotatedWith(NotOnWeekends.class), 
       weekendBlocker);
  }
}

另一种方法是,使用 Binder.getProvider 然后通过构造方法注入依赖项到拦截器中:

public class NotOnWeekendsModule extends AbstractModule {
  protected void configure() {
    bindInterceptor(any(),
                    annotatedWith(NotOnWeekends.class),
                    new WeekendBlocker(getProvider(Calendar.class)));
  }
}

使用拦截器时一定要格外小心。If your interceptor calls a method that it itself is intercepting, you may receive a StackOverflowException due to unending recursion.

AOP Alliance
The method interceptor API implemented by Guice is a part of a public specification called AOP Alliance. This makes it possible to use the same interceptors across a variety of frameworks.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,100评论 18 139
  • Dagger2 转载请注明原作者,如果你觉得这篇文章对你有帮助或启发,可以关注打赏。 前言本文翻译自Google ...
    轻云时解被占用了阅读 6,581评论 4 31
  • 原文链接:https://github.com/google/guice/wiki/Bindings Bindin...
    2fc2a81494ac阅读 2,038评论 0 0
  • Guice是谷歌推出的一个轻量级依赖注入框架,帮助我们解决Java项目中的依赖注入问题。如果使用过Spring的话...
    乐百川阅读 25,974评论 6 25
  • 小组时间: 活动名称:小手不要碰 活动目标: 1.知道保护小手,了解保护小手的方法。 2.知道生活中哪些危险的物品...
    yoniyoniyoni阅读 278评论 0 0