Google Guice之绑定方式

在Guice中,注入器的工做是装配对象图,当请求某一类型实例时,注入器根据对象图来判断如何建立实例、解析依赖。要肯定如何解析依赖就须要经过配置注入器的绑定方式。java

要建立绑定(Binding)对象,能够继承自AbstractModule类,而后覆盖其configure方法,在方法调用bind()方法来指来定每一次绑定,这些方法带有类型检查,若是你使用了错误的类型编译器就会报告编译错误。若是你已经写好了Module类,则建立一个Module类对象做为参数传递给Guice.createInjector()方法用于建立一个注入器。mysql

经过Module对象能够建立连接绑定(linked bindings)、实例绑定(instance bindings)、@Provides methods、提供者绑定(provider bindings)、构建方法绑定(constructor bindings)与无目标绑定(untargetted bindings)。这些绑定方式统称为内置绑定,相对应的还有种及时绑定,若是在解析一个依赖时若是在内置绑定中没法找到,那么Guice将会建立一个及时绑定。sql

1、连接绑定(LinkdedBindings)

连接绑定即映射一类型到它的实现类,例如映射TransactionLog接口到实现类DatabaseTransactionLogapi

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

这样,当你调用injector.getInstance(TransactionLog.class)方法,或者当注入器碰到TransactionLog依赖时,就会使用DatabaseTransactionLog对象。连接是从一类型到它任何的子类型,这包括接口实现类,类的子类;因此以下映射也是能够的:bind(DatabaseTransactionLog.class).to(MySqlDatabaseTransactionLog.class);
而且连接绑定支持链式写法:安全

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

在这种状况下,当请求一个TransactionLog类型对象时,注入器将返回一个MySqlDatabaseTransactionLog对象。ide

2、绑定注解

某些状况下你可能想为同一种类型设置多种绑定。这就能够经过绑定注解来实现,该注解与绑定的类型用于惟一结识一个绑定,合在一块儿称为Key。示例:ui

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描述到该注解时,就会把PayPal做为绑定注解。
而后在Moduleconfigure方法中使用annotatedWith语句,以下:
bind(CreditCardProcessor.class).annotatedWith(PayPal.class).to(PayPalCreditCardProcessor.class);
这样就把CreditCardProcessor映射到了PayPalCreditCardProcessorthis

用法以下:google

public class RealBillingService implements BillingService {

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

还有一种状况,咱们可使用Guice已经定义好的@Named注解,例如:url

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

由于编译器不会对字符串进行检查,Guice建议咱们少使用@Named注解,可是我我的认为,只要本身写代码时只要名称不要写错,经过这种方法式是最容易为同一类型映射多个绑定的,这很相似Spring中的实现方式,Spring的@Service, @Controller, @Repository不就能够指定名称吗?

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

使用方式以下 :

@Inject @Named("JDBC URL")
private String url

Guice建议咱们避免使用.toInstance来建立复杂的对象,由于这会延迟应用启动。相似地,可使用@Provides方法实现。

4、@Provides方法

当使用@Provides方法建立对象时,该方法必须定义在Module类中,而且它必须加以@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@Named("Checkout")绑定注解,Guice以绑定注解优先。Guice在调用@Provides方法以前会先解析该方法的依赖:

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

关于异常:Guice不容许在@Provides方法中抛出异常。若是有异常抛出,那么异常将会被包装在ProvisionException对象中。

5、提供者绑定(Provider Bindings)

若是@Provides方法愈来愈复杂,咱们可能会想把它们移到一个单独的类中。一个提供者类实现了Provider接口,它是一个用于提供值的简单通用接口。

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

若是提供者实现类有其本身的依赖时,能够经过在其构造方法上添加@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;
  }
}

最后使用.toProvider语句来绑定到提供者:

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

6、无目标绑定

Guice容许咱们建立绑定时不指定目标类,也就是没有to语句,这对于具体类或者使用了@ImplementedBy@ProvidedBy注解类型颇有用。例如:

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

然而,在使用绑定注解时,咱们依赖必须指定绑定目标,即它是一个具体类,例如:

bind(MyConcreteClass.class)
    .annotatedWith(Names.named("foo"))
    .to(MyConcreteClass.class);
bind(AnotherConcreteClass.class)
    .annotatedWith(Names.named("foo"))
    .to(AnotherConcreteClass.class)
    .in(Singleton.class);

7、构造方法绑定

有些时候你可能须要将某一类型绑定到任一构建方法,例如在@Inject注解没法添加到目标类构造方法,其缘由多是这个类是第三方提供的,或者说该类有多个构建方法参与依赖注入。此时@Provides方法是解决这个问题的最好方案,由于它能够明确指定调用哪一个构造方法,并且不须要使用反射机制。可是使用@Provides方法在某些地方有限制,例如:手动建立对象不能在AOP中使用。

正是由于这个缘由,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);
    }
  }
}

上这个例子中DatabaseTransactionLog类必须有一个带DatabaseConnection参数的构造方法,该构造方法中不须要使用@Inject注解Guice会自动调用该构造方法。每一条toConstructor()语句建立的绑定,其做用域是独立的,若是你建立了多个单例绑定而且使用目标类的同一个构造方法,每个绑定仍是拥有各自的实例。

8、及时绑定

当注入器须要某一类型实例的时候,它须要获取一个绑定。在Module类中的绑定叫作显示绑定,只要它们可用,注入器就可使用它们。若是须要某一类型实例,但它又不是显示绑定,那么注入器将试图建立一个及时绑定(Just-In-Time bindings),它也被称为JIT绑定与隐式绑定。

可用于建立及时绑定的状况以下:

  1. 有一个合适的构建方法,即非私有,不带参数或者标有@Inject注解的构造方法,例如:

    public class PayPalCreditCardProcessor implements CreditCardProcessor {
     private final String apiKey;
    
     @Inject
     public PayPalCreditCardProcessor(@Named("PayPal API key") String apiKey) {
       this.apiKey = apiKey;
     }
    }

    Guice不会建立内部类实例除非它有static修饰符,由于内部类含有一个指向外问类的隐式引用,而这个隐式引用没法注入。

  2. @ImplementedBy

    @ImplementedBy注解于用告诉注入器某类型的缺省实现类型是什么,这与连接绑定很类似。为某一类型绑定一子类型以下:

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

    @ImplementedBy(PayPalCreditCardProcessor.class)等效于下面的bind()语句:

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

    若是某一类型即有bind()语句又有@ImplementedBy注解,则bind()语句优先。使用@ImplementedBy请当心,由于它为接口添加了编译时依赖。

  3. @ProvidedBy

    @ProvidedBy注解用于告诉注入器,Provider类的实现是什么,例如:

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

    这等价于bind(TransactionLog.class).toProvider(DatabaseTransactionLogProvider.class);
    相似@ImplementedBy注解,若是某个类型既使用了bind()语句,又使用了@ProvidedBy注解,那么bind()语句优先。

    -------------------------------- END -------------------------------

及时获取更多精彩文章,请关注公众号《Java精讲》。

相关文章
相关标签/搜索