Guice 中的绑定(bindings)是 Guice Key 和 value 实例的一个对应关系,即 Guice Map 中的一项(entry)。不管是通过方法注解和是 DSL ,将 Guice Key 和实例进行关联便创建了一个绑定。

Guice 中存在多种绑定关系。

Linked Bindings

Linked Bindings 是将接口类型和接口的实现的类型关联起来的绑定。

Binding Annotations

https://blog.kicey.site/guice-review-map/ 中提到可以将类型和注解组合起来作为一个 Guice Key ,这里的注解需要符合 Guice 的规范,即是一个 Binding Annotations ,注解的声明上需要注解上 @Qualifier 或者 @BindingAnnotation 来表明这是这个注解可以用来和类型组合作为 Guice Key (注解还需要在运行时可用,如果自定义的注解带有属性,那么还需要实现注解的 equals()hashCode() 方法)。

为了简化 Binding Anotations 的使用,Guice 提供了 @Name() 注解,使用如下:

声明需要注入的实例:

public RealBillingService implements BillingService {
	@Inject
    public RealBillingService(@Named("Checkout") CreditCardProcessor processor, TransactionLog transactionLog){
    ...
    }
}

关联 Guice Key 和实例:

final class CreditCardProcessorModule extends AbstractModule {
	@Override
    protected void configure() {
    	bind(CreditCardProcessor.class)
        .annotateWith(Names.named("Checkout"))
        .to(CheckoutCreditCardProcessor.class);
    }
}

关联 Guice Key 和实例时也可以使用:

final class CreditCardProcessorModule extends AbstractModule {
	@Provides
    @Named("Checkout")
    CreditCardProcessor provideCheckoutCreditCardProcessor() {
    	return new CheckoutCreditCardProcessor();
    }
}

Instance Bindings

通常我们定义 Guice 中的一对 key/value 时,并不会直接指定一个实例作为 value,而是指定实例的具体类型,和一个返回实例的方法,让 Guice 来创建这个实例。我们也可以直接指定实例来创建这个 key/value 关系,例如:

...
bind(String.class)
	.annotatedWith(Names.named("JDBC URL"))
    .toInstance("jdbc:mysql://localhost/pizza");
...

@Provides Methods

在 Module 中定义 @Provides 方法时,将会把方法注解和返回类型作为 Guice Key ,将实际的返回实例作为 value,并且自动的创建一个绑定。

Provider Bindings

除了在 Module 中定义 @Provides 的方法外,我们也可以直接定义一个实现了 Provider<T> 接口的类。二者的作用是相同的。不过直接定义接口的方式需要多一个使用 DSL 进行绑定的步骤,如下:

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

Untargeted Bindings

在定义一个 Binding 时只提供类型作为 Guice Key ,而具体的实例由类型上的 @ImplementedBy 或者 @ProvideBy 注解值指定。

Constructor Bindings

偶尔我们需要将第三方的类型加入 Guice 的管理,这时我们没法改动第三方库中的类型的源码(没法加入 @Inject 注解),这时候我们可以使用 DSL 中的 toConstructor() 将实例和第三方类型的构造方法绑定,例如:

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

Built-in Bindings

Guice 不需要任何配置就在启动时包含了部分绑定(类似于 spring-boot-starter 会在 spring 的容器中自动的创建一些 bean)。

  1. Loggers ,每次注入新创建一个实例,将被注入的类名作为 Loggers 的名称
  2. Injector ,Guice 中的注解器,可以直接获取依赖,类似于 spring 的 BeanFactory
  3. Providers ,所有自动和手动创建的 Providers
  4. TypeLiterals ,如果你注入了泛型参数,可以同时注入 TypeLiterals 来获取具体的参数类型
  5. Stage ,表明当前是开发或生产环境
  6. MembersInjectors,进行字段注入使用的实例

Just-in-time Bindings

Guice 不仅能使用显示定义的绑定,也可以在自动根据需要注入的类型创建绑定(隐式绑定),这种自动创建的绑定对实例的构造方法有一定的要求,需要满足下面条件中的一个:

  1. 实例的构造方法以 @Inject 注解了
  2. 实例具有无参的,并且是 public 的构造方法

Guice 可以通过配置只允许创建第一种类型的绑定:

binder().requireAtInjectRequired()

在 Guice 3.0 以后使用

binder().requireExplicitBindins()

Multibindings

多绑定:一个 Guice Key 绑定一个实例集合 ,用于插件式的框架结构中。多绑定依靠 3 个类: MultibinderMapBinderOptionalBinder

例如以下代码将 UriSummarizer 和它的多个实现绑定。

public class FlickerPluginModule extends AbstractModule {
	public void configure() {
    	Multibinder<UriSummarizer> uriBinder = MultiBinder.newSetBinder(binder(), UriSummarizer.class);
        userBinder.addBinding().to(FlickPhotoSummarizer.class);
        userBinder.addBinding().to(FakeUriSummarizer.class);
    }
}

注入构造方法时以集合的形式注入 Set<UriSummarizer>

值得注意的是 MultiBinder.newSetBinder(binder, type) 这个方法并不是为一个 Guice Key 对应类型创建一个新的集合,而是会在原有的集合上进行操作。

默认情况下实例集合是不运行出现重复的具体类型的实例的,不过可以使用 multiBinder.permitDuplicates() 解除这个限制。

除了将多个实例聚合成集合外,还可以将实例聚合成一个 Map,例如 Map<String, UrlSummarizer> ,相应的,创建绑定的 DSL 有一些变化,如下:

public class FlickerPluginModule extends AbstractModule {
	public void configure() {
    	Multibinder<UriSummarizer> uriBinder = MapBinder.newMapBinder(binder(), UriSummarizer.class);
        userBinder.addBinding("flick").to(FlickPhotoSummarizer.class);
        userBinder.addBinding("fake).to(FakeUriSummarizer.class);
    }
}

注入时使用 Map<String, UriSummarizer>

和 Set 的情况一样,Map 也存在重复绑定的问题,不同的时 Map 的重复限制在 Map 的 key 值上,同样可以使用 binder.permitDuplicates() 解除限制。

在上面的两个样例代码中,第一份代码创建了一个 MultiBinder,第二个则使用了 MapBinder

OptionalBinder

如果在构造方法中使用形如 Optional<MyInterface> 的类型,那么 Guice 将创建相应的 OptionalBinder 。可以为这个 OptionalBinder 设置默认值,如下:

public class FrameworkLoggingModule extends AbstractModule {
	protected void configure() {
    	OptionalBinder.newOptionalBinder(binder(), RequestLogger.class)
        .setDefault()
        .to(DefaultRequestLoggerImpl.class);
    }
}

在指定默认值之后只能使用 setBinding 覆写默认值,例如:

public class ConsoleLoggingModule extends AbstractModule {
	@Override
    protected void configure() {
    	OptionalBinder.newOptionalBinder(binder(), RequestLogger.class)
        .setBinding()
        .to(ConsoleLogger.class);
    }
}

@Provides-like

在使用多绑定的情况下,除了通过 DSL 显示的绑定之外,还可以使用 @ProvidesIntoSet@ProvidesIntoMapProvidesIntoOptional 注解方法的形式进行。

@ProvidesIntoSet

public class FlickrPluginModule extends AbstractModule {
  @ProvidesIntoSet
  UriSummarizer provideFlickerUriSummarizer() {
    return new FlickrPhotoSummarizer(...);
  }
}

@ProvidesIntoMap

public class FlickrPluginModule extends AbstractModule {
  @StringMapKey("Flickr")
  @ProvidesIntoMap
  UriSummarizer provideFlickrUriSummarizer() {
    return new FlickrPhotoSummarizer(...);
  }
}

除了使用 String 作为 key 之外,还可以使用注解和注解的 value() 返回值(以 unwrapValue 的布尔值做区分)作为 key。

@MapKey(unwrapValue=true)
@Retention(RUNTIME)
public @interface MyCustomEnumKey {
  MyCustomEnum value();
}

@ProvidersIntoOptional

public class FrameworkModule extends AbstractModule {
  @ProvidesIntoOptional(ProvidesIntoOptional.Type.DEFAULT)
  @Singleton
  RequestLogger provideConsoleLogger() {
    return new DefaultRequestLoggerImpl();
  }
}
public class RequestLoggingModule extends AbstractModule {
  @ProvidesIntoOptional(ProvidesIntoOptional.Type.ACTUAL)
  @Singleton
  RequestLogger provideConsoleLogger() {
    return new ConsoleLogger(System.out);
  }
}