DI | Guice教程篇

Posted by Aiden on July 25, 2019

Google Guice 框架是一个基于Java 6以上的轻量级依赖注入框架,由Google开发。它相比于Spring IOC 来说,如果你只需在应用程序中实现依赖注入,那么你无需使用Spring容器。 Spring不仅仅是一个依赖注入框架,二期大多数Spring应用都使用了XML作为依赖注入的方式。而Guice却是一个相对更轻量级的框架,它的集成更少,有Java实例配置和运行时绑定。 通过使用Java绑定,可以获得编译时的类型检查和IDE自动完成功能。

Guice框架的运行速度也很快。默认情况下,Guice为每一个依赖(对应Spring的“prototype”范围)注入一个新的、单独的对象实例,而Spring则默认提供单态实例。 Guice框架将依赖注入提升到一个新的水平,充分利用Java类型的所有功能,特别是注释和泛型,使得构建的DI应用程序更加模块化,更容易编写,并且错误更少、易于维护。

GOOGLE GUICE 依赖:

<dependency>
    <groupId>com.google.inject</groupId>
    <artifactId>guice</artifactId>
    <version>3.0</version>
</dependency>

入门案例:

Guice 使用起来也很轻量化,不必要太多明显的配置方式。

只需要使用注解,或者几个依赖配置类就可以完成。

如下一个简单的使用Guice的案例: StartServer有两个字段依赖外部类:Service service, Configuration configuration, 我们通过 @Inject标识依赖使用动态注入方式。

Configuration中,我们使用@Singleton 把类交给 guice维护为一个单例类,使用时注入给 StartServer类。

StartModule中,我们通过显式方式指名对于Service的依赖使用PrintService 注入。

image.png


直接类依赖

直接绑定:

被依赖基类:

public class DatabaseTransactionLog extends TransactionLog{}
public class MySqlDatabaseTransactionLog extends DatabaseTransactionLog{}

直接依赖配置

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

如果请求 TransactionLog 时, 将返回 DatabaseTransactionLog

@Inject
private TransactionLog log;

@ImplementedBy

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

等效与

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

注释依赖:

Guice附带一个 @Named 带有字符串的内置绑定注释:

@Inject
@Named('dataLog')
private TransactionLog log;

要绑定特定名称,请使用Names.named()以创建要传递给的实例 annotatedWith

bind(TransactionLog.class)
    .annotatedWith(Names.named("dataLog"))
    .to(DatabaseTransactionLog.class);

注解依赖:

定义注解:

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;

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

说明:

@BindingAnnotation 告诉Guice这是一个绑定注释。如果多个绑定注释适用于同一成员,Guice将产生错误。

@Target({FIELD, PARAMETER, METHOD}) 对您的用户是礼貌的。它可以防止@PayPal在没有任何用途的情况下意外使用。

@Retention(RUNTIME) 使注释在运行时可用。

依赖:

@Inject
@PayPal
private CreditCardProcessor processor;

绑定配置:

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

通过方法构造的类依赖

当我们需要代码来创建对象时,则需要基于方法构造类依赖方式。

@Provides 注解

如果构造类的方法在 AbstractModule 派生类中,我们可以使用 @Provides 注解把返回对象托管给 Guice.

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 注解可以配合@Named, 或自定义依赖注解一起使用.

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

Provider<T> 派生类

当我们的 @Provides 方法开始变得复杂时,就可以定义类来进行封装。 提供程序类实现了Guice的Provider接口,这是一个用于提供值的简单通用接口:

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

我们实现了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;
  }
}

这个时候我们需要在 AbstractModule 派生类中绑定我们的类。

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

@ProvidedBy 注解

@ProvidedBy 告诉注入器一个Provider生成实例的类:

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

注释等同于 toProvider() 绑定:

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

Scopes

默认情况下,Guice每次提供一个值时都会返回一个新实例。

不过我们也可以通过注解来显式的配置依赖的生命周期。

注解 范围
@Singleton 整个应用程序生命周期范围(单例)
@SessionScoped 基于session的会话范围
@RequestScoped 基于每次请求的范围

应用范围

Guice使用注释来标识范围。通过将范围注释应用于实现类来指定类型的范围。除了功能性之外,此注释还可用作文档。例如,@Singleton表示该类旨在是线程安全的。

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

Scopes 也可以在bind语句中配置:

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

也可以通过@Provides注释方法:

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

Injections :

依赖注入模式将行为与依赖性解析分开。 该模式不是直接查找依赖项或从工厂查找依赖项,而是建议传入依赖项。 将依赖项设置为对象的过程称为注入。

构造器函数注入

构造函数注入将实例化与注入相结合。要使用它,请使用注释注释构造函数@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 no-arguments 构造函数(如果存在)。 首选注释,该类型参与依赖注入的文档。

函数注入

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

字段注入

Guice使用@Inject注释注入字段。这是最简洁的注射剂,但是最不可测试的

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

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

参考内容:

Google Guice 3.0开发

Google-Guice入门教程 - 掘金

Home · google/guice Wiki · GitHub