Guice简介
Guice 简介,本文中的内容也是参考该文档完成,如有不一致,以该文为准。
快速上手
作为示例,我们使用 BillingService,它依赖于 CreditCardProcessor 和 TransactionLog 两个接口。接下来我们看看如何使用Guice:
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 是特定的 bindings 的集合。
public class BillingModule extends AbstractModule {
@Override
protected void configure() {
/**
*这是告诉Guice,当遇到一个对象依赖于TransactionLog时,
*将DatabaseTransactionLog注入
*/
bind(TransactionLog.class).to(DatabaseTransactionLog.class);
/**同上*/
bind(CreditCardProcessor.class).to(PaypalCreditCardProcessor.class);
}
}
现在可以编写main方法了:
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);
...
}
大功告成!!!
Bindings
injector 的职责是确定系统中需要构造哪些对象,解析依赖,装配对象(将被依赖的对象注入依赖的对象)。那么如何指定依赖的解析方式,答案是使用 bindings 配置你的 injector
创建bindings
继承 AbstractModule 重写 configure 方法。在该方法中调用 bind() 便创建了一个binding。完成module之后,调用 Guice.createInjector(),将module作为参数传入,便可获得一个injector对象。
Linked Bindings
Linked bindings 将一个实现绑定到一个类型。
下面例子将 DatabaseTransactionLog 绑定到接口 TransactionLog
public class BillingModule extends AbstractModule {
@Override
protected void configure() {
bind(TransactionLog.class).to(DatabaseTransactionLog.class);
}
}
绑定之后,当你调用 injector.getInstance(TransactionLog.class) 或当injector遇到一个对象依赖与 TransactionLog,它便会使用 DatabaseTransactionLog。链接可以建立于接口和其实现类,或者子类和父类之间。Linked bindings 可以链式使用。
public class BillingModule extends AbstractModule {
@Override
protected void configure() {
bind(TransactionLog.class).to(DatabaseTransactionLog.class);
bind(DatabaseTransactionLog.class).to(MySqlDatabaseTransactionLog.class);
}
}
在这种情况下,当一个对象依赖于 TransactionLog,injector将会返回一个 MySqlDatabaseTransactionLog.
BindingAnnotations
Binding Annotations
有时候,你想要给特定的类型绑定多个实现。例如,你想给 CreditCardProcessor同时绑定 PaypalCreditCardProcessor 和 CheckoutCreditCardProcessor 两个实现. Guice 通过binding annotation满足上述需求。注解和类型共同确定了一个唯一的binding。 在这儿,注解和类型构成了Key。
首先我们定义一个注解:
import com.google.inject.BindingAnnotation;
import java.lang.annotation.Target;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
@BindingAnnotation
@Target({ FIELD, PARAMETER, METHOD })
@Retention(RUNTIME)
public @interface PayPal {}
然后使用我们定义的注解标示需要注入的类型。
public class RealBillingService implements BillingService {
@Inject
public RealBillingService(@PayPal CreditCardProcessor processor,
TransactionLog transactionLog) {
...
}
最后我们还需要创建相应的binding。
bind(CreditCardProcessor.class)
.annotatedWith(PayPal.class)
.to(PayPalCreditCardProcessor.class);
@Named
也并不是必须创建自己的注解,Guice提供了一个内建的注解@Named。用法如下:
public class RealBillingService implements BillingService {
@Inject
public RealBillingService(@Named("Checkout") CreditCardProcessor processor,
TransactionLog transactionLog) {
...
}
bind(CreditCardProcessor.class)
.annotatedWith(Names.named("Checkout"))
.to(CheckoutCreditCardProcessor.class);
因为编译器不能对String类型进行检查,所以不建议使用@Named
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 代替该方法。
Provides Methods
当你需要编写创建对象的代码,使用 @Provides 方法。该方法只能定义在module中。并且需要使用 @Provides 修饰。他的返回值是一个对象。当Injector需要某个类型的实例时,便会调用相应的@Provides 方法。
public class BillingModule extends AbstractModule {
@Override
protected void configure() {
...
}
@Provides
TransactionLog provideTransactionLog() {
DatabaseTransactionLog transactionLog = new DatabaseTransactionLog();
transactionLog.setJdbcUrl("jdbc:mysql://localhost/pizza");
transactionLog.setThreadPoolSize(30);
return transactionLog;
}
}
如果 @Provides 方法有binding annotation ,比如@Paypal 或者 @Named("Checkout"),Guice 也是可以的。所有的被依赖对象以参数形式传入该方法即可。
@Provides @PayPal
CreditCardProcessor providePayPalCreditCardProcessor(
@Named("PayPal API key") String apiKey) {
PayPalCreditCardProcessor processor = new PayPalCreditCardProcessor();
processor.setApiKey(apiKey);
return processor;
}
需要注意的是, Guice不允许 @Provides 方法抛出异常。
Provider Bindings
当 @Provides 方法比较复杂时,你也许会考虑将该方法转移到一个单独的类中。Provider类继承Guice的 Provider 接口。 Provider 接口定义如下:
public interface Provider<T> {
T get();
}
我们的Provider实现类有自己的依赖,所有的依赖是通过被@Inject 修饰的构造函数接收的。
public class DatabaseTransactionLogProvider implements Provider<TransactionLog> {
private final Connection connection;
@Inject
public DatabaseTransactionLogProvider(Connection connection) {
this.connection = connection;
}
public TransactionLog get() {
DatabaseTransactionLog transactionLog = new DatabaseTransactionLog();
transactionLog.setConnection(connection);
return transactionLog;
}
}
绑定
public class BillingModule extends AbstractModule {
@Override
protected void configure() {
bind(TransactionLog.class)
.toProvider(DatabaseTransactionLogProvider.class);
}
Untargeted Bindings
一些情况下,你需要创建bindings,但是却不能指定具体的实现。这个对于被@ImplementedBy 或者 @ProvidedBy 修饰的具体类或者类型特别有用。An untargetted binding informs the injector about a type, so it may prepare dependencies eagerly. Untargetted bindings have no to clause, like so:
bind(MyConcreteClass.class);
bind(AnotherConcreteClass.class).in(Singleton.class);
当指定binding annotation时,必须加上绑定的目标。
bind(MyConcreteClass.class)
.annotatedWith(Names.named("foo"))
.to(MyConcreteClass.class);
bind(AnotherConcreteClass.class)
.annotatedWith(Names.named("foo"))
.to(AnotherConcreteClass.class)
.in(Singleton.class);
Constructor Bindings
有时候, 我们需要绑定一个类型到任意的的构造函数。以下情况会有这种需求:@Inject 注解无法被应用到目标构造函数。或者该类型是一个第三方类。或者该类型中有多个构造函数参与依赖注入。
针对这个,Guice 有 @toConstructor() 类型的绑定方式。
public class BillingModule extends AbstractModule {
@Override
protected void configure() {
try {
bind(TransactionLog.class).toConstructor(
DatabaseTransactionLog.class.getConstructor(DatabaseConnection.class));
} catch (NoSuchMethodException e) {
addError(e);
}
}
}
JustInTimeBindings
Just-in-time Bindings
当Injector需要一个类型的实例的时候,它需要一个binding。 如果这个binding在一个module中被创建,那么这个binding是显式binding,此时injector在每次需要该类型实例时,都使用该实例。但是如果Injector需要一个类型的实例,但是这个类型并没有对应的显式binding。此时injector会尝试创建一个Just-in-time binding。也叫JIT binding或者隐式binding。
合格的构造函数
Guice会使用具体类型的可注入构造函数创建binding。可注入构造函数需要是非private,无参数的或者该构造函数被 @Inject 修饰。
public class PayPalCreditCardProcessor implements CreditCardProcessor {
private final String apiKey;
@Inject
public PayPalCreditCardProcessor(@Named("PayPal API key") String apiKey) {
this.apiKey = apiKey;
}
@ImplementedBy
告诉injector,该类型的默认实现类。
@ImplementedBy(PayPalCreditCardProcessor.class)
public interface CreditCardProcessor {
ChargeResult charge(String amount, CreditCard creditCard)
throws UnreachableException;
}
上述代码和一下代码是等价的:
bind(CreditCardProcessor.class).to(PayPalCreditCardProcessor.class);
如果某个类型同时使用 bind() 和 @ImplementedBy,bind() 会生效。
@ProvidedBy
告诉Injector,产生该类型的Provider类
@ProvidedBy(DatabaseTransactionLogProvider.class)
public interface TransactionLog {
void logConnectException(UnreachableException e);
void logChargeResult(ChargeResult result);
}
上述代码等价于:
bind(TransactionLog.class)
.toProvider(DatabaseTransactionLogProvider.class);
如果同时使用 @ProvidedBy 和 bind() , bind() 会生效。
Scopes
默认情况下,Guice 每次都会返回一个新创建的对象。不过这也是可以配置的,以便于我们重用实例。
配置Scopes
Guice 使用注解标示Scope。例如:
@Singleton
public class InMemoryTransactionLog implements TransactionLog {
/* everything here should be threadsafe! */
}
@Provides @Singleton
TransactionLog provideTransactionLog() {
...
}
上例中,@Singleton 标示该类型只能有一个实例。并且是线程安全的。
Scope也可以通过代码来配置:
bind(TransactionLog.class).to(InMemoryTransactionLog.class).in(Singleton.class);
如果某个类型已经被注解标注了scope,同时还使用bind() 方法配置了scope,那么后者生效。如果一个类型已经被注解配置了scope而你不想那样,你可以使用 bind() 覆盖。
预加载的单例
bind(TransactionLog.class).to(InMemoryTransactionLog.class).asEagerSingleton();
Injections
构造函数注入
这种情况下,需要用 @Inject 标注构造函数。构造函数同时需要将所依赖的类型作为其参数。通常情况下,都是将传入的参数复制给类的final成员。
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 会使用共有的无参构造函数(如果存在)。
方法注入
这种情况下,需要使用@Inject 标注方法,该方法的参数为需要注入的类型。
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;
}
字段注入
这种情况下,需要使用 @Inject 标注字段。
public class DatabaseTransactionLogProvider implements Provider<TransactionLog> {
@Inject Connection connection;
public TransactionLog get() {
return new DatabaseTransactionLog(connection);
}
}
可选的注入
有时候,我们的依赖项不是必须的,如果系统中存在依赖项则注入,如果不存在,也不强制要求注入。这种情况在方法注入和字段注入中都是适用的。 启用可选注入,只需要使用 @Inejct(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;
}
不过,如果混用可选注入和Just-in-time bindings,可能会产生奇怪的接口。例如:
@Inject(optional=true) Date launchDate;
上面代码中的date总是会被成功注入即使没有为他创建对应的显示binding,因为它有无参构造函数,Guice会为他创建Just-in-time bindings。
On-demand注入
方法和字段注入可以用来初始化一个现存的实例。我们可以使用Injector.injectMember()API:
public static void main(String[] args) {
Injector injector = Guice.createInjector(...);
CreditCardProcessor creditCardProcessor = new PayPalCreditCardProcessor();
injector.injectMembers(creditCardProcessor);
AOP
Guice 支持方法拦截器。这里直接看一个实例:
假如我们需要禁止在周末调用特定方法
为了标注我们在周末禁止调用的方法,我们定义一个注解类型:
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD)
@interface NotOnWeekends {}
然后使用该注解标注我们方法
public class RealBillingService implements BillingService {
@NotOnWeekends
public Receipt chargeOrder(PizzaOrder order, CreditCard creditCard) {
...
}
}
现在我们定义我们的拦截器,我们的拦截器需要实现org.aopalliance.intercept.MethodInterceptor 接口。
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();
}
}
然后配置我们的拦截器
public class NotOnWeekendsModule extends AbstractModule {
protected void configure() {
/**在这里,我们匹配所有的类,但是只匹配类中有NotOnWeekends的方法*/
bindInterceptor(Matchers.any(), Matchers.annotatedWith(NotOnWeekends.class),
new WeekendBlocker());
}
}
所有工作就完成了。
注入拦截器
如果需要注入拦截器,使用 `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)));
}
}
END