关于java:Google-开源的依赖注入库比-Spring-更小更快

4次阅读

共计 12467 个字符,预计需要花费 32 分钟才能阅读完成。

Guice 是 Google 开源的一个依赖注入类库,相比于 Spring IoC 来说更小更快。Elasticsearch 大量应用了 Guice,本文简略的介绍下 Guice 的基本概念和应用形式。

学习指标

概述:理解 Guice 是什么,有什么特点;
疾速开始:通过实例理解 Guice;
外围概念:理解 Guice 波及的外围概念,如绑定(Binding)、范畴(Scope)和注入(Injection);
最佳实际:官网举荐的最佳实际;

Guice 概述

Guice 是 Google 开源的依赖注入类库,通过 Guice 缩小了对工厂办法和 new 的应用,使得代码更易交付、测试和重用;
Guice 能够帮忙咱们更好地设计 API,它是个轻量级非侵入式的类库;
Guice 对开发敌对,当有异样产生时能提供更多有用的信息用于剖析;

疾速开始
假如一个在线预订 Pizza 的网站,其有一个计费服务接口:

public interface BillingService {
 /**
  * 通过信用卡领取。无论领取胜利与否都须要记录交易信息。
  *
  * @return 交易回执。领取胜利时返回胜利信息,否则记录失败起因。
  */
  Receipt chargeOrder(PizzaOrder order, CreditCard creditCard);
}

应用 new 的形式获取信用卡领取处理器和数据库交易日志记录器:

public class RealBillingService implements BillingService {
  public Receipt chargeOrder(PizzaOrder order, CreditCard creditCard) {
    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());
    }
  }
}

应用 new 的问题是使得代码耦合,不易保护和测试。比方在 UT 里不可能间接用实在的信用卡领取,须要 Mock 一个 CreditCardProcessor。相比于 new,更容易想到的改良是应用工厂办法,然而工厂办法在测试中仍存在问题(因为通常应用全局变量来保留实例,如果在用例中未重置可能会影响其余用例)。更好的形式是通过构造方法注入依赖:

public class RealBillingService implements BillingService {
  private final CreditCardProcessor processor;
  private final TransactionLog 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());
    }
  }
}

对于实在的网站利用能够注入真正的业务解决服务类:

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

而在测试用例中能够注入 Mock 类:

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

那通过 Guice 怎么实现依赖注入呢?首先咱们须要通知 Guice 如果找到接口对应的实现类,这个能够通过模块 来实现:

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

这里的模块只须要实现 Module 接口或继承自 AbstractModule,而后在 configure 办法中设置绑定(前面会持续介绍)即可。而后只需在原有的构造方法中减少 @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());
    }
  }
}

最初,再看看 main 办法中是如何调用的:

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

绑定
连贯绑定
连贯绑定是最罕用的绑定形式,它将一个类型和它的实现进行映射。上面的例子中将 TransactionLog 接口映射到它的实现类 DatabaseTransactionLog。

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

连贯绑定还反对链式,比方上面的例子最终将 TransactionLog 接口映射到实现类 MySqlDatabaseTransactionLog。

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

注解绑定
通过一个类型可能存在多个实现,比方在信用卡领取处理器中存在 PayPal 的领取和 Google 领取,这样通过连贯绑定就搞不定。这时咱们能够通过注解绑定来实现:

@BindingAnnotation
@Target({FIELD, PARAMETER, METHOD})
@Retention(RUNTIME)
public @interface PayPal {}

public class RealBillingService implements BillingService {

  @Inject
  public RealBillingService(@PayPal CreditCardProcessor processor,
      TransactionLog transactionLog) {
    …
  }
}

// 当注入的办法参数存在 @PayPal 注解时注入 PayPalCreditCardProcessor 实现
bind(CreditCardProcessor.class).annotatedWith(PayPal.class).to(PayPalCreditCardProcessor.class);

能够看到在模块的绑定时用 annotatedWith 办法指定具体的注解来进行绑定,这种形式有一个问题就是咱们必须减少自定义的注解来绑定,基于此 Guice 内置了一个 @Named 注解满足该场景:

public class RealBillingService implements BillingService {

  @Inject
  public RealBillingService(@Named(“Checkout”) CreditCardProcessor processor,
      TransactionLog transactionLog) {
    …
  }
}

// 当注入的办法参数存在 @Named 注解且值为 Checkout 时注入 CheckoutCreditCardProcessor 实现
bind(CreditCardProcessor.class).annotatedWith(Names.named(“Checkout”)).to(CheckoutCreditCardProcessor.class);

实例绑定
将一个类型绑定到一个具体的实例而非实现类,这个通过是在无依赖的对象(比方值对象)中应用。如果 toInstance 蕴含简单的逻辑会导致启动速度,此时应该通过 @Provides 办法绑定。

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

@Provides 办法绑定
模块中定义的、带有 @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 @PayPal
  CreditCardProcessor providePayPalCreditCardProcessor(@Named(“PayPal API key”) String apiKey) {
    PayPalCreditCardProcessor processor = new PayPalCreditCardProcessor();
    processor.setApiKey(apiKey);
    return processor;
  }
}

Provider 绑定
如果应用 @Provides 办法绑定逻辑越来越简单时就能够通过 Provider 绑定(一个实现了 Provider 接口的实现类)来实现。

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

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

无指标绑定
当咱们想提供对一个具体的类给注入器时就能够采纳无指标绑定。

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

结构器绑定
3.0 新增的绑定,实用于第三方提供的类或者是有多个结构器参加依赖注入。通过 @Provides 办法能够显式调用结构器,然而这种形式有一个限度:无奈给这些实例利用 AOP。

public class BillingModule extends AbstractModule {
  @Override
  protected void configure() {
    try {
      bind(TransactionLog.class).toConstructor(DatabaseTransactionLog.class.getConstructor(DatabaseConnection.class));
    } catch (NoSuchMethodException e) {
      addError(e);
    }
  }
}

范畴
默认状况下,Guice 每次都会返回一个新的实例,这个能够通过范畴(Scope)来配置。常见的范畴有单例(@Singleton)、会话(@SessionScoped)和申请(@RequestScoped),另外还能够通过自定义的范畴来扩大。

范畴的注解能够应该在实现类、@Provides 办法中,或在绑定的时候指定(优先级最高):

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

// scopes apply to the binding source, not the binding target
bind(TransactionLog.class).to(InMemoryTransactionLog.class).in(Singleton.class);

@Provides @Singleton
TransactionLog provideTransactionLog() {
    …
}

另外,Guice 还有一种非凡的单例模式叫饥饿单例(绝对于懒加载单例来说):

// Eager singletons reveal initialization problems sooner,
// and ensure end-users get a consistent, snappy experience.
bind(TransactionLog.class).to(InMemoryTransactionLog.class).asEagerSingleton();

注入
依赖注入的要求就是将行为和依赖拆散,它倡议将依赖注入而非通过工厂类的办法去查找。注入的形式通常有结构器注入、办法注入、属性注入等。

// 结构器注入
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;
  }
}

// 办法注入
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;
  }
}

// 属性注入
public class DatabaseTransactionLogProvider implements Provider<TransactionLog> {
  @Inject Connection connection;

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

// 可选注入:当找不到映射时不报错
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;
  }
}

辅助注入
辅助注入(Assisted Inject)属于 Guice 扩大的一部分,它通过 @Assisted 注解主动生成工厂来增强非注入参数的应用。

// RealPayment 中有两个参数 startDate 和 amount 无奈间接注入
public class RealPayment implements Payment {
  public RealPayment(
        CreditService creditService,  // from the Injector
        AuthService authService,  // from the Injector
        Date startDate, // from the instance’s creator
        Money amount); // from the instance’s creator
  }
  …
}

// 一种形式是减少一个工厂来结构
public interface PaymentFactory {
  public Payment create(Date startDate, Money amount);
}

public class RealPaymentFactory implements PaymentFactory {
  private final Provider<CreditService> creditServiceProvider;
  private final Provider<AuthService> authServiceProvider;

  @Inject
  public RealPaymentFactory(Provider<CreditService> creditServiceProvider,
      Provider<AuthService> authServiceProvider) {
    this.creditServiceProvider = creditServiceProvider;
    this.authServiceProvider = authServiceProvider;
  }

  public Payment create(Date startDate, Money amount) {
    return new RealPayment(creditServiceProvider.get(),
      authServiceProvider.get(), startDate, amount);
  }
}

bind(PaymentFactory.class).to(RealPaymentFactory.class);

// 通过 @Assisted 注解能够缩小 RealPaymentFactory
public class RealPayment implements Payment {
  @Inject
  public RealPayment(
        CreditService creditService,
        AuthService authService,
        @Assisted Date startDate,
        @Assisted Money amount);
  }
  …
}

// Guice 2.0
//bind(PaymentFactory.class).toProvider(FactoryProvider.newFactory(PaymentFactory.class, RealPayment.class));
// Guice 3.0
install(new FactoryModuleBuilder().implement(Payment.class, RealPayment.class).build(PaymentFactory.class));

最佳实际

最小化可变性:尽可能注入的是不可变对象;
只注入间接依赖:不必注入一个实例来获取真正须要的实例,减少复杂性且不易测试;
防止循环依赖
防止动态状态:动态状态和可测试性就是天敌;
采纳 @Nullable:Guice 默认状况下禁止注入 null 对象;
模块的解决必须要快并且无副作用
在 Providers 绑定中当心 IO 问题:因为 Provider 不查看异样、不反对超时、不反对重试;
不必在模块中解决分支逻辑
尽可能不要裸露结构器

起源:zhuanlan.zhihu.com/p/24924391

正文完
 0