Guice基本用法

来源:互联网 发布:3799游戏盒软件 编辑:程序博客网 时间:2024/05/17 02:19

本文适合对依赖注入有相对了解的读者,文章中对于部分名词未作详细解释。对于没有恰当的中文与之对应的英文内容,遂未翻译

Guice简介

Guice 简介,本文中的内容也是参考该文档完成,如有不一致,以该文为准。

快速上手

作为示例,我们使用 BillingService,它依赖于 CreditCardProcessorTransactionLog 两个接口。接下来我们看看如何使用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同时绑定 PaypalCreditCardProcessorCheckoutCreditCardProcessor 两个实现. 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()@ImplementedBybind() 会生效。

@ProvidedBy

告诉Injector,产生该类型的Provider类

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

上述代码等价于:

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

如果同时使用 @ProvidedBybind() , bind() 会生效。

Scopes

默认情况下,Guice 每次都会返回一个新创建的对象。不过这也是可以配置的,以便于我们重用实例。

配置Scopes

Guice 使用注解标示Scope。例如:

@Singletonpublic class InMemoryTransactionLog implements TransactionLog {  /* everything here should be threadsafe! */}@Provides @SingletonTransactionLog 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

本人有意进一步阅读Guice源码,但是个人能力有限,如有感兴趣的读者,希望可以一起研究。

0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 ipadmini密码忘了怎么办 旧ipad特别卡怎么办 苹果ipad反应慢怎么办 手机垃圾多了怎么办 ipad2内存过低怎么办 苹果平板ipad内存不足怎么办 手机dns配置错误怎么办 蓝牙已停止运行怎么办 ipad看电视闪退怎么办 ipad为什么看电视会闪退怎么办 微淘直播延迟怎么办 手机淘宝进群领金币怎么办 做淘客冲销量停止淘客后怎么办 微信中零钱提现怎么办 淘宝买家不签收怎么办 小龙虾没人下单怎么办 淘宝直播不浮现怎么办 淘宝直播看不了怎么办 理财客户说没钱怎么办 投资不给钱了怎么办 工作中遇到挫折怎么办 手机qq出现异常怎么办 农行卡出现异常怎么办 淘宝长期不发货怎么办 快递一直不发货怎么办 申请退款被拒绝怎么办 淘宝的垃圾短信怎么办 如果淘宝不退款怎么办 客服遇到客户骂怎么办 商场保证金不退怎么办 淘宝被投诉侵权怎么办 电脑wifi链接不见了怎么办 无线设备坏了怎么办 电脑网页默认了怎么办 淘宝商家停止服务怎么办? 淘宝商家停止了怎么办 做淘宝不会美工怎么办 做客服打字慢怎么办 淘宝客服学不会怎么办 代购不给退货怎么办 淘宝不能发照片怎么办