- 此文为本人学习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(工厂)
为了解决上面的问题,可以引入一个工厂类,来简单完成“客户端”和“实现类”之间的解耦。
首先定义一个简单的工厂类,提供 setInstance
和 getInstance
静态方法,允许外部调用方通过 setInstance
和 getInstance
方法设置或获取一个接口实现类的实例,这个实现类可以是真实的,也可以是伪造的。
工厂定义代码示例如下:
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
本身不负责查找TransactionLog
和 CreditCardProcessor
,而是通过构造函数接受相关参数,然后将其作为内部成员变量使用,此时,依赖关系需要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());
}
}
}
我们不再需要任何工厂类,也可以扔掉 setUp
和 tearDown
方法来简化测试用例。
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
构造方法接受它依赖的两个接口 CreditCardProcessor
和 TransactionLog
参数。为了让例子看的更清楚,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) {
...
}
}
现在我们想用 PaypalCreditCardProcessor
和 DatabaseTransactionLog
构造一个 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 bindings
、instance bindings
、@Provides methods
、provider bindings
、constructor bindings
和 untargetted 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 内部抛出异常。运行时异常会被包装为ProvisionException
或 CreationException
,通常这种异常会影响到 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
,同时继承了 Bar
和 Grill
接口,此时 Applebees
可以定义两个绑定实例,一个是绑定 Bar
,另一个是绑定 Grill
:
bind(Bar.class).to(Applebees.class).in(Singleton.class);
bind(Grill.class).to(Applebees.class).in(Singleton.class);
这两个bind语句的作用域限定都是对绑定类型(Bar
、Grill
)的,而不是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.