基础教程Guice 注入 用户指南(2)

阿超 发表于 2009-08-22 06:41 | 来源: | 阅读 338 次

       这章我们来学习更多的标注只要有可能,Guice 就允许你使用标注来替代显式地绑定对象,以减少更多的程式化代码。回到我们的例子,如果你需要一个接口来简化单元测试,而你又不介意编译时的依赖,你可以直接从你的接口指向一个缺省的实现。

@ImplementedBy(ServiceImpl.class)
public interface Service {
  void go();
}

        这时,如果客户需要一个 Service 对象,且 Guice 无法找到显式绑定,Guice 就会注入一个 ServiceImpl 的实例。 缺省情况下,Guice 每次都注入一个新的实例。如果你想指定不同的作用域规则,你也可以对实现类进行标注。

@Singleton
public class ServiceImpl implements Service {
  public void go() {
    ...
  }
}

架构概览:我们可以将 Guice 的架构分成两个不同的阶段:启动和运行。你在启动时创建一个注入器 Injector,在运行时用它来注入对象。

启动:你通过实现 Module 来配置 Guice。你传给 Guice 一个模块对象,Guice 则将一个绑定器 Binder 对象传入你的模块,然后,你的模块使用绑定器来配置绑定。一个绑定通常包含一个从接口到具体实现的映射。例如:

public class MyModule implements Module {
  public void configure(Binder binder) {
    // Bind Foo to FooImpl. Guice will create a new
    // instance of FooImpl for every injection.
    binder.bind(Foo.class).to(FooImpl.class);

    // Bind Bar to an instance of Bar.
    Bar bar = new Bar();
    binder.bind(Bar.class).toInstance(bar);
  }
}

在这个阶段,Guice 会察看你告诉它的所有类,以及任何与这些类有关系的类,然后通知你是否有依赖关系的缺失。例如,在一个 Struts 2 应用中,Guice 知道你所有的动作类。Guice 会检查你的动作类以及它们依赖的所有类,如果有问题会及早报错。

创建一个 Injector 涉及以下步骤:

Guice

1.首先创建你的模块类实例,并将其传入 Guice.createInjector().
2.Guice 创建一个绑定器 Binder 并将其传入你的模块。
3.你的模块使用绑定器来定义绑定。
4.基于你所定义的绑定,Guice 创建一个注入器 Injector 并将其返回给你。
5.你使用注入器来注入对象。

运行:现在你可以使用第一阶段创建的注入器来注入对象并内省(introspect)我们的绑定了。Guice 的运行时模型由一个可管理一定数量绑定的注入器组成。

dd2fhx4z_30f3dppc
     键 Key 唯一地确定每一个绑定。 Key 包含了客户代码所依赖的类型以及一个可选的标注。你可以使用标注来区分指向同一类型的多个绑定。 Key 的类型和标注对应于注入时的类型和标注。

       每个绑定有一个提供者 provider,它提供所需类型的实例。你可以提供一个类,Guice 会帮你创建它的实例。你也可以给 Guice 一个你要绑定的类的实例。你还可以实现你自己的 provider,Guice 可以向其中注入依赖关系。

      每个绑定还有一个可选的作用域。缺省情况下绑定没有作用域,Guice 为每一次注入创建一个新的对象。一个定制的作用域可以使你控制 Guice 是否创建新对象。例如,你可以为每一个 HttpSession 创建一个实例。

自举(Bootstrapping)你的应用: 自举(bootstrapping)对于依赖注入非常重要。总是显式地向 Injector 索要依赖,这就将 Guice 用作了服务定位器,而不是一个依赖注入框架。

       你的代码应该尽量少地和 Injector 直接打交道。相反,你应该通过注入一个根对象来自举你的应用。容器可以更进一步地将依赖注入根对象所依赖的对象,并如此迭代下去。最终,在理想情况下,你的应用中应该只有一个类知道 Injector,每个其他类都应该使用注入的依赖关系。

         例如,一个诸如 Struts 2 的 Web 应用框架通过注入你的所有动作类来自举你的应用。你可以通过注入你的服务实现类来自举一个 Web 服务框架。

       依赖注入是传染性的。如果你重构一个有大量静态方法的已有代码,你可能会觉得你正在试图拉扯一根没有尽头的线。这是好事情。它表明依赖注入正在帮助你改进代码的灵活性和可测试性。

        如果重构工作太复杂,你不想一次性地整理完所有代码,你可以暂时将一个 Injector 的引用存入某个类的一个静态的字段,或是使用静态注入。这时,请清楚地命名包含该字段的类:比如 InjectorHack 和GodKillsAKittenEveryTimeYouUseMe。记住你将来可能不得不为这些类提供伪测试类,你的单元测试则不得不手工安装一个注入器。记住,你将来需要清理这些代码。

  绑定依赖关系 :Guice 是如何知道要注入什么东西的呢?对启动器来说,一个包含了类型和可选的标注的 Key 唯一地指明了一个依赖关系。Guice 将 key 和实现之间的映射标记为一个 Binding。一个实现可以包含一个单独的对象,一个需要由 Guice 注入的类,或一个定制的 provider。

      当注入依赖关系时,Guice 首先寻找显式绑定,即你通过绑定器 Binder 指明的绑定。Binder API 使用生成器(Builder)模式来创建一种领域相关的描述语言。根据约束适用方法的上下文的不同,不同方法返回不同的对象。 例如,为了将接口 Service 绑定到一个具体的实现 ServiceImpl,调用:

binder.bind(Service.class).to(ServiceImpl.class);

该绑定与下面的方法匹配:

@Inject
void injectService(Service service) {
  ...
}

      注: 与某些其他的框架相反,Guice 并没有给 “setter” 方法任何特殊待遇。不管方法有几个参数,只要该方法含有 @Inject 标注,Guice 就会实施注入,甚至对基类中实现的方法也不例外。 不要重复自己 对每个绑定不断地重复调用 “binder” 似乎有些乏味。Guice 提供了一个支持 Module 的类,名为 AbstractModule,它隐含地赋予你访问 Binder 的方法的权力。例如,我们可以用扩展AbstractModule 类的方式改写上述绑定:

bind(Service.class).to(ServiceImpl.class);

在本手册的余下部分中我们会一直使用这样的语法。 标注绑定 如果你需要指向同一类型的多个绑定,你可以用标注来区分这些绑定。例如,将接口 Service 和标注 @Blue 绑定到具体的实现类 BlueService 的代码如下:

bind(Service.class)
  .annotatedWith(Blue.class)
  .to(BlueService.class);

这个绑定会匹配以下方法:

@Inject
void injectService(@Blue Service service) {
  ...
}

注意,标注 @Inject 出现在方法前,而绑定标注(如 @Blue)则出现在参数前。对构造函数也是如此。使用字段注入时,两种标注都直接应用于字段,如以下代码: @Inject @Blue Service service; 创建绑定标注刚才提到的标注 @Blue 是从哪里来的?你可以很容易地创建这种标注,但不幸的是,你必须使用略显复杂的标准语法:

/**
 * Indicates we want the blue version of a binding.
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.PARAMETER})
@BindingAnnotation
public @interface Blue {}

幸运的是,我们不需要理解这些代码,只要会用就可以了。对于好奇心强的朋友,下面是这些程式化代码的含义:
 •@Retention(RUNTIME) 使得你的标注在运行时可见。
•@Target({FIELD, PARAMETER}) 是对用户使用的说明;它不允许 @Blue 被用于方法、类型、局部变量和其他标注。
•@BindingAnnotation 是 Guice 特定的信号,表示你希望该标注被用于绑定标注。当用户将多于一个的绑定标注应用于同一个可注入元素时,Guice 会报错。

          有属性的标注如果你已经会写有属性的标注了,请跳到下一节。 你也可以绑定到标注实例,即,你可以有多个绑定指向同样的类型和标注类型,但每个绑定拥有不同的标注属性值。如果 Guice 找不到拥有特定属性值的标注实例,它会去找一个绑定到该标注类型的绑定。 例如,我们有一个绑定标注 @Named,它有一个字符串属性值。

@Retention(RUNTIME)@Target({ FIELD, PARAMETER })@BindingAnnotationpublic @interface Named {  String value();}如果我们希望绑定到 @Named("Bob"),我们首先需要一个 Named 的实现。我们的实现必须遵守关于 Annotation 的约定,特别是 hashCode() 和 equals() 的实现。

class NamedAnnotation implements Named {  final String value;  public NamedAnnotation(String value) {    this.value = value;  }  public String value() {    return this.value;  }  public int hashCode() {    // This is specified in java.lang.Annotation.    return 127 * "value".hashCode() ^ value.hashCode();  }  public boolean equals(Object o) {    if (!(o instanceof Named))      return false;    Named other = (Named) o;    return value.equals(other.value());  }  public String toString() {    return "@" + Named.class.getName() + "(value=" + value + ")";  }  public Class>? extends Annotation> annotationType() {    return Named.class;  }}现在我们可以使用这个标注实现来创建一个指向 @Named 的绑定。

bind(Person.class)
  .annotatedWith(new NamedAnnotation("Bob"))
  .to(Bob.class);

         与其它框架使用基于字符串的标识符相比,这显得有些繁琐,但记住,使用基于字符串的标识符,你根本无法这样做。而且,你会发现你可以大量复用已有的绑定标注。 因为通过名字标记一个绑定非常普遍,以至于 Guice 在 com.google.inject.name 中提供了一个十分有用的 @Named 的实现。 隐式绑定正如我们在简介中看到的那样,你并不总是需要显式地声明绑定。如果缺少显式绑定
       Guice 会试图注入并创建一个你所依赖的类的新实例。如果你依赖于一个接口,Guice 会寻找一个指向具体实现的 @ImplementedBy 标注。例如,下例中的代码显式绑定到一个具体的、可注入的名为 Concrete 的类。它的含义是,将 Concrete 绑定到 Concrete。这是显式的声明方式,但也有些冗余。

    bind(Concrete.class);

删除上述绑定语句不会影响下面这个类的行为:

class Mixer {

  @Inject
  Mixer(Concrete concrete) {
    ...
  }
}

         好吧,你自己来选择:显式的或简略的。无论何种方式,Guice 在遇到错误时都会生成有用的信息。注入提供者有时对于每次注入,客户代码需要某个依赖的多个实例。其它时候,客户可能不想在一开始就真地获取对象,而是等到注入后的某个时候再获取。对于任意绑定类型 T,你可以不直接注入 T 的实例,而是注入一个 Provider<T>,然后在需要的时候调用 Provider<T>.get(),例如:

@Inject
void injectAtm(Provider<Money> atm) {
  Money one = atm.get();
  Money two = atm.get();
  ...
}

         正如你所看到的那样, Provider 接口简单得不能再简单了,它不会为简单的单元测试添加任何麻烦。 注入常数值 对于常数值,Guice 对以下几种类型做了特殊处理:
•基本类型(int, char, …)
•基本封装类型(Integer, Character, …)
•Strings
•Enums
•Classes
首先,当绑定到这些类型的常数值的时候,你不需要指定你要绑定到的类型。Guice 可以根据值判断类型。例如,一个绑定标注名为 TheAnswer:

bindConstant().annotatedWith(TheAnswer.class).to(42);

它的效果等价于:

bind(int.class).annotatedWith(TheAnswer.class).toInstance(42);

        当需要注入这些类型的数值时,如果 Guice 找不到指向基本数据类型的显式绑定,它会找一个指向相应的封装类型的绑定,反之亦然。 转换字符串如果 Guice 仍然无法找到一个上述类型的显式绑定,它会去找一个拥有相同绑定标注的常量 String 绑定,并试图将字符串转换到相应的值。例如:

bindConstant().annotatedWith(TheAnswer.class).to("42"); // String!

会匹配:

@Inject @TheAnswer int answer;

        转换时,Guice 会用名字去查找枚举和类。Guice 在启动时转换一次,这意味着它提前做了类型检查。这个特性特别有用,例如,当绑定值来自一个属性文件的时候。定制的提供者有时你需要手工创建你自己的对象,而不是让 Guice 创建它们。例如,你可能不能为来自第三方的实现类添加 @Inject 标注。在这种情况下,你可以实现一个定制的 Provider。Guice 甚至可以注入你的提供者类。例如:

class WidgetProvider implements Provider<Widget> {

  final Service service;

  @Inject
  WidgetProvider(Service service) {
    this.service = service;
  }

  public Widget get() {
    return new Widget(service);
  }
}

你可以像这样把
Widget 绑定到 WidgetProvider:
bind(Widget.class).toProvider(WidgetProvider.class);
注入定制的提供者可以使 Guice 提前检查类型和依赖关系。定制的提供者可以在任意作用域中使用,而不依赖于他们所创建的类的作用域。缺省情况下,Guice 为每一次注入创建一个新的提供者实例。在上例中,如果每个 Widget 需要它自己的 Service 实例,我们的代码也没有问题。通过在工厂类上使用作用域标注,或为工厂类创建单独的绑定,你可以为定制的工厂指定不同的作用域。

喜欢Java豆技术站点的文章,那就通过 RSS Feed 功能订阅阅读吧!

我要评论

*

* 绝不会泄露



返回首页 | 关于我们 | 联系我们 | 广告合作 | 网站地图 | 友情链接 | 版权声明 | 模板设计