`
rq2_79
  • 浏览: 242577 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

Guice 1.0 用户指南

阅读更多

Guice (读作"juice")是超轻量级的,下一代的,为Java 5及后续版本设计的依赖注入容器。

简介
        Java 企业应用开发社区在连接对象方面花了很大功夫。你的Web应用如何访问中间层服务?你的服务如何连接到登录用户和交易管理器?关于这个问题你会发现很多通 用的和特定的解决方案。有一些方案依赖于模式,另一些则使用框架。所有这些方案都会不同程度地引入一些难于测试或者程式化代码重复的问题。你马上就会看 到,Guice 在这方面是全世界做得最好的:非常容易进行单元测试,最大程度的灵活性和可维护性,以及最少的代码重复。
        我们使 用一个假想的、简单的例子来展示 Guice 优于其他一些你可能已经熟悉的经典方法的地方。下面的例子过于简单,尽管它展示了许多显而易见的优点,但其实它还远没有发挥出 Guice 的全部潜能。我们希望,随着你的应用开发的深入,Guice 的优越性也会更多地展现出来。   
        在这个例子中,一个客户对象依赖于一个服务接口。服务接口可以提供任意的服务。我们只是调用这个服务而已。
java 代码
 
  1. public interface Service {  
  2.   void go();  
  3. }  
       
        对于这个服务接口,我们有一个缺省的实现,但客户对象不应该直接依赖于这个缺省实现。如果我们将来打算使用一个不同的服务实现,我们不希望回过头来修改所有的客户代码。
java 代码
 
  1. public class ServiceImpl implements Service {  
  2.   public void go() {  
  3.     ...  
  4.   }  
  5. }   
   
        我们还有一个虚拟的、可用于单元测试的服务实现。
java 代码
 
  1. public class MockService implements Service {  
  2.   
  3.   private boolean gone = false;  
  4.   
  5.   public void go() {  
  6.     gone = true;  
  7.   }  
  8.   
  9.   public boolean isGone() {  
  10.     return gone;  
  11.   }  

简单工厂模式

        在发现依赖注入之前,最常用的是工厂模式。除了服务接口之外,你还有一个既可以向客户提供服务对象,也可以向测试程序提供模拟对象的工厂接口。在这里我们会将服务实现为一个单件模式,以便让示例尽量简化。

java 代码
 
  1. public class ServiceFactory {  
  2.   
  3.   private ServiceFactory() {}  
  4.      
  5.   private static Service instance = new ServiceImpl();  
  6.   
  7.   public static Service getInstance() {  
  8.     return instance;  
  9.   }  
  10.    
  11.   public static void setInstance(Service service) {  
  12.     instance = service;  
  13.   }  
  14. }  

客户程序每次需要服务对象时就直接从工厂获取。

java 代码
 
  1. public class Client {  
  2.   
  3.   public void go() {  
  4.     Service service = ServiceFactory.getInstance();  
  5.     service.go();  
  6.   }  
  7. }  

客户程 序足够简单。但客户程序的单元测试代码必须将一个虚拟对象传入工厂,同时要记得在测试后清理。在我们这个简单的例子里,这不算什么难事儿。但当你增加了越 来越多的客户和服务代码后,所有这些虚拟代码和清理代码会让单元测试的开发一团糟。此外,如果你忘记在测试后清理,其他测试可能会得到与预期不符的结果。 更糟的是,测试的成功与失败可能取决于他们被执行的顺序。

java 代码
  1. public void testClient() {  
  2.   Service previous = ServiceFactory.getInstance();  
  3.   try {  
  4.     final MockService mock = new MockService();  
  5.     ServiceFactory.setInstance(mock);  
  6.     Client client = new Client();  
  7.     client.go();  
  8.     assertTrue(mock.isGone());  
  9.   }  
  10.   finally {  
  11.     ServiceFactory.setInstance(previous);  
  12.   }  
  13. }  

最后,注意服务工厂的API把我们限制在了单件这一种应用模式上。即便 getInstance() 可以返回多个实例, setInstance() 也会束缚我们的手脚。转换到非单件模式也意味着转换到了一套更复杂的API。

手工依赖注入

依赖注入模式的目标之一是使单元测试更简单。我们不需要特殊的框架就可以实践依赖注入模式。依靠手工编写代码,你可以得到该模式大约80%的好处。

当上例中的客户代码向工厂对象请求一个服务时,根据依赖注入模式,客户代码希望它所依赖的对象实例可以被传入自己。也就是说:不要调用我,我会调用你。

java 代码
 
  1. public class Client {  
  2.      
  3.   private final Service service;  
  4.   
  5.   public Client(Service service) {  
  6.     this.service = service;  
  7.   }  
  8.   
  9.   public void go() {  
  10.     service.go();  
  11.   }  
  12. }  

这让我们的单元测试简化了不少。我们可以只传入一个虚拟服务对象,在结束后也不需要多做什么。

public void testClient() {
  MockService mock = new MockService();
  Client client = new Client(mock);
  client.go();
  assertTrue(mock.isGone());
}

我们也可以精确地说出客户代码依赖于哪些API。

现在,我们如何把客户代码连接到服务对象呢?手工实现依赖注入的时候,我们可以将所有依赖逻辑都移动到工厂类中。也就是说,我们还需要有一个工厂类来创建客户对象。

public static class ClientFactory {

  private ClientFactory() {}

  public static Client getInstance() {
    Service service = ServiceFactory.getInstance();
    return new Client(service);
  }
}

手工实现依赖注入需要的代码行数和简单工厂模式差不多。

用 Guice 实现依赖注入

手工为每一个服务与客户实现工厂类和依赖注入逻辑是一件很麻烦的事情。其他一些依赖注入框架甚至需要你显式将服务映射到每一个需要注入的地方。

Guice 希望在不牺牲可维护性的情况下去除所有这些程式化的代码。

使用 Guice,你只需要实现模块类。Guice 将一个绑定器传入你的模块,你的模块使用绑定器来连接接口和实现。以下模块代码告诉 Guice 将 Service 映射到单件模式的 ServiceImpl

public class MyModule implements Module {
  public void configure(Binder binder) {
    binder.bind(Service.class)
      .to(ServiceImpl.class)
      .in(Scopes.SINGLETON);
  }
}

模块类告诉 Guice 我们想注入什么东西。那么,我们该如何告诉 Guice 我们想注入到哪里呢?使用 Guice,你可以使用 @Inject 标注你的构造器,方法或字段:

public class Client {

  private final Service service;

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

  public void go() {
    service.go();
  }
}

@Inject 标注使程序员可以很容易地在类中指明哪些方法需要被注入。

为了让 Guice 向 Client 中注入,我们必须直接让 Guice 帮我们创建 Client 的实例,或者,其他类必须包含被注入的 Client 实例。

Guice vs. 手工依赖注入

如你所见,Guice 省去了写工厂类的麻烦。你不需要编写将客户连接到它们所依赖的对象的代码。如果你忘了提供一个依赖关系,Guice 在启动时就会失败。Guice 也会自动处理循环依赖关系。

Guice 允许你通过声明指定对象的作用域。例如,你不用为了将对象存入 HttpSession 而反复编写同样的代码。

在现实世界,不到运行的时候,你通常并不知道具体要使用哪一个实现类。你需要元工厂类或是服务定位器来增强你的工厂模式。Guice 用最少的代价解决了这些问题。

手工实现依赖注入时,你很容易退回到使用直接依赖的旧习惯,特别是当你对依赖注入的概念还不那么熟悉的时候。使用 Guice 可以避免这种问题,可以让你更容易地把事情做对。Guice 使你保持正确的方向。

更多的标注

只要有可能,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 涉及以下步骤:


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

运行

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



键 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 和实现之间的映射记为一个绑定。一个实现可以包含一个单独的对象,一个需要由 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 })
@BindingAnnotation
public @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 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>,然后在需要的时候调用</t> Provider<t></t>.get(),例如:

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

正如你所看到的那样, Provider 接口简单得不能再简单了,它不会为简单的单元测试添加任何麻烦。

注入常数值

 
对于常数值,Guice 对以下几种类型做了特殊处理:

  • 基本类型(int, char, ...)
  • 基本 wrapper 类型(Integer, Character, ...)
  • Strings
  • Enums
  • Classes

首先,当绑定到这些类型的常数值的时候,你不需要指定你要绑定到的类型。Guice 可以根据值判断类型。例如,一个绑定标注名为 TheAnswer:

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

它的效果等价于:

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

当需要注入这些类型的数值时,如果 Guice 找不到显式指向基本数据类型的绑定,它会找一个指向相应的 wrapper 类型的绑定,反之亦然。

转换字符串

 

如果 Guice 仍然无法找到一个上述类型的显式绑定,它会去找一个拥有相同绑定标识的常量 String 绑定,并试图将字符串转换到相应的值。例如:

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

会匹配:

@Inject @TheAnswer int answer;

转换时,Guice 会用名字去查找枚举和类。Guice 在启动时转换一次,这意味着它提前做了类型检查。这个特性特别有用,例如,当绑定值来自一个属性文件的时候。

定制的提供者

有时你需要手工创建你自己的对象,而不是让 Guice 创建它们。例如,你可能不能为来自第三方的实现类添加 @Inject 标注。在这种情况下,你可以实现一个定制的 Provider。Guice 甚至可以注入你的提供者类。例如:

class WidgetProvider implements Provider<widget> {</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 实例,我们的代码也没有问题。通过在工厂类上使用作用域标注,或为工厂类创建单独的绑定,你可以为定制的工厂指定不同的作用域。

示例:与 JNDI 集成

例如我们需要绑定从 JNDI 得到的对象。我们可以仿照下面的代码实现一个可复用的定制的提供者。注意我们注入了 JNDI Context:

package mypackage;

import com.google.inject.*;
import javax.naming.*;

class JndiProvider<t> implements Provider<t> {

  @Inject Context context;
  final String name;
  final Class<t> type;

  JndiProvider(Class<t> type, String name) {
    this.name = name;
    this.type = type;
  }

  public T get() {
    try {
      return type.cast(context.lookup(name));
    }
    catch (NamingException e) {
      throw new RuntimeException(e);
    }
  }

  /**
   * Creates a JNDI provider for the given
   * type and name.
   */
  static <t> Provider<t> fromJndi(
      Class<t> type, String name) {
    return new JndiProvider<t>(type, name);
  }
}
</t></t></t></t></t></t></t></t>

感谢范型擦除(generic type erasure)技术。我们必须在运行时将依赖传入类中。你可以省略这一步,但在今后跟踪类型转换错误会比较棘手(当 JNDI 返回错误的类型的时候)。

我们可以使用定制的 JndiProvider 来将 DataSource 绑定到来自 JNDI 的一个对象:

import com.google.inject.*;
import static mypackage.JndiProvider.fromJndi;
import javax.naming.*;
import javax.sql.DataSource;

...

// Bind Context to the default InitialContext.

bind(Context.class).to(InitialContext.class);

// Bind to DataSource from JNDI.
bind(DataSource.class)
    .toProvider(fromJndi(DataSource.class, "..."));

限制绑定的作用域

缺省情况下,Guice 为每次注入创建一个新的对象。我们把它称为“无作用域”。你可以在配制绑定时指明作用域。例如,每次注入相同的实例:

bind(MySingleton.class).in(Scopes.SINGLETON);

另一种做法是,你可以在实现类中使用标注来指明作用域。Guice 缺省支持 @Singleton

@Singleton
class MySingleton {
  ...
}

是用标注的方法对于隐式绑定也同样有效,但需要 Guice 来创建你的对象。另一方面,调用 in() 适用于几乎所有绑定类型(显然,绑定到一个单独的实例是个例外)并且会忽略已有的作用域标注。如果你不希望引入对于作用域实现的编译时依赖,in() 还可以接受标注。

可以使用 Binder.bindScope() 为定制的作用域指定标注。例如,对于标注 @SessionScoped 和一个 Scope 的实现 ServletScopes.SESSION:


binder.bindScope(SessionScoped.class, ServletScopes.SESSION);

创建作用域标注

 

用于指定作用域的标注必须:

  • 有一个 @Retention(RUNTIME) 标注,从而使我们可以在运行时看到该标注。
  • 有一个 @Target({TYPE}) 标注。作用域标注只用于实现类。
  • 有一个 @ScopeAnnotation 元标注。一个类只能使用一个此类标注。

例如:

/**
 * Scopes bindings to the current transaction.
 */
@Retention(RUNTIME)
@Target({TYPE})
@ScopeAnnotation
public @interface TransactionScoped {}

尽早加载绑定

Guice 可以等到你实际使用对象时再加载单件对象。这有助于开发,因为你的应用程序可以快速启动,只初始化你需要的对象。但是,有时你总是希望在启动时加载一个对象。你可以告诉 Guice,让它总是尽早加载一个单件对象,例如:

bind(StartupTask.class).asEagerSingleton();
我们经常在我们的程序中使用这个方法实现初始化逻辑。你可以通过在 Guice 必须首先初始化的单件上创建依赖关系来控制初始化顺序。

在不同作用域间注入

你可以安全地将来自大作用域的对象注入到来自小作用域或相同作用域的对象中。例如,你 可以将一个作用域为 HTTP 会话的对象注入到作用域为 HTTP 请求的对象中。但是,在来自较大作用域的对象中注入就是另一件事了。例如,如果你把一个作用域为 HTTP 请求的对象注入到一个单件对象中,最好情况下,你会得到无法在 HTTP 请求中运行的错误信息,最坏情况下,你的单件对象总会引用一个来自第一次请求的对象。在这些时候,你应该注入一个 Provider<t>,然后在需要的时候使用它从较小的作用域中获取对象。这时,你必须确保,在 T 的作用域之外,永远不要调用这个提供者(例如,当目前没有 HTTP 请求且  T 的作用域为请求的时候)。</t>

开发阶段

Guice 明白你的应用开发需要经历不同的阶段。你可以在创建容器时告诉它应用程序运行在哪一个阶段。Guice 目前支持“开发”和“产品”两个阶段。我们发现测试通常属于其中某一个阶段。

在开发阶段,Guice 会在需要时加载单件对象。这样,你的应用可以快速启动,只加载你正在测试的部分。

在产品阶段,Guice 会在启动时加载全部单件对象。这帮助你尽早捕获错误,提前优化性能。

你的模块也可以使用方法拦截和其他基于当前阶段的绑定。例如,一个拦截器可能会在开发阶段检查你是否在作用域之外使用对象。

拦截方法

Guice 使用 AOP Alliance API 支持简单的方法拦截。你可以在模块中使用 Binder 绑定拦截器。例如,对标注有 @Transactional 的方法使用交易拦截器:

import static com.google.inject.matcher.Matchers.*;

...

binder.bindInterceptor(
  any(),                              // Match classes.
  annotatedWith(Transactional.class), // Match methods.
  new TransactionInterceptor()        // The interceptor.
);


尽量让匹配代码多做些过滤工作,而不是在拦截器中过滤。因为匹配代码只在启动时运行一次。

静态注入

静态字段和方法会增加测试和复用的难度,但有的时候你唯一的选择就是保留一个静态的指向 Injector 的引用。
 
在 这些情况下,Guice 支持注入可访问性较少的静态方法。例如,HTTP 会话对象经常需要被串行化,以支持复制机制。但是,如果你的会话对象依赖于一个作用域为容器生命周期的对象,该怎么办呢?我们可以保留一个易变的引用指向 该对象,但在反串行化的时候,我们该如何再次找到该对象呢?

我们发现更实用的解决方案是使用静态注入:
@SessionScoped
class User {

  @Inject
  static AuthorizationService authorizationService;
  ...
}

Guice 从不自动实施静态注入。你必须使用 Binder 显式请求 Injector 在启动后注入你的静态成员:

binder.requestStaticInjection(User.class);

静态注入是一个需要保留的祸害,它会使测试难度加大。如果有办法避开它,你多半会很高兴的。
 

可选注入

有时你的代码应该在无论绑定是否存在的时候都能工作。在这些情况下,你可以使用 @Inject(optional=true),Guice 会在有绑定可用时,用一个绑定的实现覆盖你的缺省实现。例如:

@Inject(optional=true) Formatter formatter = new DefaultFormatter();
如果谁为 Formatter 创建了一个绑定,Guice 会基于该绑定注入实例。否则,如果 Formatter 不能被注入(参见隐式绑定),Guice 会忽略可选成员。

可选注入只能应用于字段和方法,而不能用于构造器。对于方法,如果一个参数的绑定找不到,Guice 就不会注入该方法,即便其他参数的绑定是可用的。

绑定到字符串

只要有可能,我们就尽量避免使用字符串,因为它们容易受错误拼写的影响,对工具不友好,等等。但使用字符串而不是创建定制的标注对于快而脏的代码来说仍是有用的。在这些情况下,Guice 提供了@Named 和 Names。例如,一个到字符串名字的绑定:

import static com.google.inject.name.Names.*;

...

bind(named("bob")).to(10);

会匹配下面的注入点:

@Inject @Named("bob") int score;

Struts 2支持

要在 Struts 2.0.6 或更高版本中安装 Guice Struts 2 插件,只要将 guice-struts2-plugin-1.0.jar 包含在你的 Web 应用的 classpath 中,并在 struts.xml 中选择 Guice 作为你的 ObjectFactory 实现即可:

<constant value="guice" name="struts.objectFactory"></constant>

Guice 会注入所有你的 Struts 2 对象,包括动作和拦截器。你甚至可以设置动作类的作用域。你也可以在你的 struts.xml 文件中指定 Guice 的 Module

<constant value="mypackage.MyModule" name="guice.module">

</constant>
如果你的所有绑定都是隐式的,你就根本不用定义模块了。

一个计数器的例子

例如,我们试图统计一个会话中的请求数目。定义一个在会话中存活的 Counter 对象:

@SessionScoped
public class Counter {

  int count = 0;

  /** Increments the count and returns the new value. */
  public synchronized int increment() {
    return count++;
  }
}

接下来,我们可以将我们的计数器注入到动作中:

public class Count {

  final Counter counter;

  @Inject
  public Count(Counter counter) {
    this.counter = counter;
  }

  public String execute() {
    return SUCCESS;
  }

  public int getCount() {
    return counter.increment();
  }
}

然后在 struts.xml 文件中为动作类创建映射:

<
分享到:
评论

相关推荐

    永磁同步电机(PMSM)三闭环控制系统仿真与参数优化 - MATLAB/Simulink实现

    内容概要:本文详细介绍了永磁同步电机(PMSM)三闭环控制系统的仿真建模方法及其参数优化技巧。首先阐述了三闭环控制的整体架构,即位置环、速度环和电流环的层级关系,并解释了每个环节的作用。接着展示了各环的具体实现代码,如电流环的PI控制器、速度环的前馈控制以及位置环的限幅处理。文中强调了调参的重要性和注意事项,提供了具体的参数选择依据和调试建议。最后分享了一些实用的仿真技巧,如死区补偿、故障注入等,确保模型能够应对实际工况。 适合人群:从事电机控制研究的技术人员、研究生及以上水平的学生,特别是对永磁同步电机三闭环控制感兴趣的读者。 使用场景及目标:适用于需要深入了解PMSM三闭环控制原理并进行仿真实验的研究人员和技术开发者。目标是帮助读者掌握如何构建高效的三闭环控制系统,提高电机性能,降低能耗,增强系统的鲁棒性和可靠性。 其他说明:文中提供的代码片段和参数配置均基于MATLAB/Simulink平台,建议读者在实践中结合实际情况调整参数,以获得最佳效果。同时,附带的参考资料也为进一步学习提供了指导。

    环境流体力学仿真:风能与水能仿真.zip

    光电材料仿真,电子仿真等;从入门到精通教程;含代码案例解析。

    基于PFC3D5.0的滑坡致灾与建筑物易损性分析代码实现及应用

    内容概要:本文详细介绍了利用PFC3D5.0进行滑坡致灾与建筑物易损性分析的完整代码实现。首先,通过Python和Fish语言构建了滑坡体和建筑物的模型,设置了关键参数如密度、刚度、摩擦系数等,确保滑坡体能够真实模拟滑坡行为。其次,针对建筑物的不同部位(楼板、墙体、支柱),采用不同的材料特性进行建模,并加入了实时监测系统,用于记录滑坡过程中各部件的应力、应变以及冲击力的变化情况。此外,还实现了冲击力监测、损伤评估等功能,能够自动触发应急分析并在模拟结束后生成详细的损伤报告。最后,通过对多次模拟结果的数据处理,生成了建筑物的易损性曲线,验证了模型的有效性和准确性。 适合人群:从事地质灾害研究、土木工程、结构安全评估的研究人员和技术人员。 使用场景及目标:适用于滑坡灾害预测、建筑设计优化、抗震防灾等领域。通过模拟不同条件下滑坡对建筑物的影响,帮助研究人员更好地理解滑坡致灾机理,为制定有效的防护措施提供科学依据。 其他说明:文中提供了大量实用的小技巧,如调整参数以获得更好的模拟效果、优化计算效率等。同时强调了模型验证的重要性,确保研究成果具有较高的可信度。

    编译qt httpserver 的步骤

    编译httpserver 通过后记录的

    光电子集成器件仿真:集成激光器仿真.zip

    光电材料仿真,电子仿真等;从入门到精通教程;含代码案例解析。

    Android平台上基于多尺度多角度模板匹配的图像识别技术及其在不同ARM架构的应用

    内容概要:本文详细探讨了在Android平台上进行图像模板匹配的技术挑战和解决方案,特别是在处理不同尺寸和旋转角度的目标物时的方法。文中介绍了使用OpenCV构建图像金字塔、处理旋转模板以及利用NEON指令集优化性能的具体实现。此外,文章还讨论了在armeabi-v7a和arm64-v8a这两种主要ARM架构下的优化技巧,如内存对齐、SIMD指令优化、RenderScript并行处理等。作者分享了许多实践经验,包括如何避免常见的性能瓶颈和兼容性问题。 适合人群:有一定Android开发经验,尤其是熟悉OpenCV和NDK编程的中级及以上开发者。 使用场景及目标:适用于需要在移动设备上进行高效图像识别的应用开发,如实时视频流中的物体检测、游戏内的道具识别等。目标是提高模板匹配的速度和准确性,同时确保在不同硬件配置下的稳定性和兼容性。 其他说明:文章提供了丰富的代码片段和实际案例,帮助读者更好地理解和应用所介绍的技术。特别强调了在不同ARM架构下的优化策略,为开发者提供了宝贵的参考资料。

    光电系统仿真:光电传感系统仿真.zip

    光电材料仿真,电子仿真等;从入门到精通教程;含代码案例解析。

    COMSOL多物理场耦合模拟电晕放电离子风及其应用

    内容概要:本文详细介绍了利用COMSOL软件模拟电晕放电离子风的过程。首先解释了电晕放电的基本概念,即在高压电场下电极周围空气被电离形成离子风。接着阐述了如何在COMSOL中建立针-板电极结构的三维模型,涉及静电、层流和稀物质传递三个物理场的设置。文中提供了具体的MATLAB代码片段用于初始化模型、定义几何体、设置边界条件、配置物理参数、进行网格划分以及求解模型。此外,还讨论了求解过程中可能出现的问题及解决方法,如收敛技巧、网格划分策略等。最后强调了通过模拟获得的电场分布、气流速度和离子浓度等结果对于理解和优化电晕放电离子风设备的重要性。 适用人群:对电晕放电现象感兴趣的科研人员和技术开发者,尤其是那些希望深入了解多物理场耦合仿真技术的人群。 使用场景及目标:适用于需要研究电晕放电离子风特性的场合,如空气净化装置、散热设备等领域的产品设计与性能评估。目标是帮助用户掌握如何使用COMSOL软件构建并求解电晕放电离子风模型,从而更好地理解相关物理机制。 其他说明:文中提到的实际操作细节和遇到的技术挑战有助于新手避免常见错误,提高建模效率。同时,提供的具体参数设置和代码示例也为进一步深入研究奠定了基础。

    多模态属性级情感分析:技术详解、代码实现与实战应用

    内容概要:本文详细介绍了多模态属性级情感分析的技术原理及其应用场景。首先解释了多模态属性级情感分析的意义,即通过结合文本和图像信息来更全面地理解用户情感。接着阐述了数据预处理方法,如使用BERT进行文本编码和ResNet处理图像。然后深入探讨了模型架构,包括双流网络结构和特征融合策略,以及如何通过跨模态注意力机制实现更好的特征对齐。此外,文中还分享了多个实战案例,如电商广告投放系统中如何利用该技术提高转化率,以及在处理用户评价时遇到的问题和解决办法。最后讨论了一些常见的技术挑战,如模态间权重调整、背景干扰物处理等。 适合人群:从事自然语言处理、计算机视觉研究的专业人士,尤其是希望将这两种技术结合起来进行情感分析的研究者和技术开发者。 使用场景及目标:适用于电商平台、社交媒体平台等需要分析用户反馈的场景,旨在帮助企业更好地理解消费者的真实想法,从而优化产品和服务。通过这种方式,企业可以发现潜在的市场机会并改进营销策略。 其他说明:文章不仅提供了理论指导,还包括具体的代码实现示例,有助于读者快速上手实践。同时强调了实际应用中的注意事项,如数据清洗、模型调优等方面的经验教训。

    5MW海上永磁风电直驱系统Simulink仿真:矢量控制与混合储能关键技术解析

    内容概要:本文详细介绍了5MW海上永磁风电直驱系统的Simulink仿真过程,涵盖矢量控制、混合储能系统以及并网逆变器的设计与调试。首先,文章解释了系统架构,包括永磁电机、两电平并网变流器和混合储能模块。接着,深入探讨了矢量控制中的坐标变换、PI参数设置及其对电网波动的影响。对于混合储能系统,文章讨论了滑动平均滤波用于功率分配的方法,确保超级电容和锂电池的有效协同工作。此外,文章还涉及并网逆变器的控制策略,特别是变参数PI控制和死区时间补偿,以应对复杂的电网环境。最后,通过仿真结果展示了系统的高效性和稳定性。 适合人群:从事电力电子工程、风电系统设计与仿真的工程师和技术研究人员。 使用场景及目标:适用于希望深入了解海上风电系统仿真技术的专业人士,旨在提高对矢量控制、混合储能和并网逆变器的理解,从而优化实际应用中的系统性能。 其他说明:文中提供了多个MATLAB代码片段,帮助读者更好地理解和复现相关控制算法。同时,强调了仿真过程中遇到的实际问题及解决方案,如风速突变、电网电压跌落等情况下的系统响应。

    光电系统仿真:光电通信系统仿真.zip

    光电材料仿真,电子仿真等;从入门到精通教程;含代码案例解析。

    natsort-5.4.0-py2.py3-none-any.whl

    该资源为natsort-5.4.0-py2.py3-none-any.whl,欢迎下载使用哦!

    双馈风力发电机DFIG矢量控制仿真模型及其定子侧与转子侧控制策略详解

    内容概要:本文详细介绍了双馈风力发电机(DFIG)的矢量控制仿真模型,特别是定子侧和转子侧的控制策略。定子侧采用电压定向矢量控制,通过双闭环结构(外环控制直流侧电压,内环控制电流),确保功率因数为1。转子侧采用磁链定向矢量控制,同样基于双闭环结构(外环控制功率,内环控制电流),并引入前馈电压补偿提高响应速度。文中提供了具体的PI控制器代码实现,并讨论了仿真模型的搭建方法,如使用Python的scipy库进行动态响应模拟。此外,文章还提到了一些常见的仿真问题及解决方案,如crowbar保护电路、最大功率跟踪算法和低电压穿越模块等。 适合人群:从事风电系统设计、控制算法开发的研究人员和技术人员,以及对电力电子控制系统感兴趣的工程师。 使用场景及目标:适用于希望深入了解DFIG矢量控制原理和实现细节的专业人士,帮助他们掌握定子侧和转子侧的具体控制策略,优化仿真模型,解决实际工程中的问题。 其他说明:文章不仅提供了理论分析,还包括了大量的代码片段和实践经验,有助于读者更好地理解和应用相关技术。

    煤矿瓦斯气驱技术中二氧化碳与氮气应用的Python自动化解决方案

    内容概要:本文详细介绍了利用Python进行煤矿瓦斯气驱过程中二氧化碳和氮气的应用方法和技术细节。首先展示了如何通过Python脚本处理气驱压力监测数据并绘制对比图,接着讲解了注气速率控制的PID算法实现及其注意事项。文中还涉及裂隙气体扩散模拟、湿度对氮气驱替的影响以及基于状态机的注气控制系统设计。此外,提供了实时气体浓度监控、数据滤波、阈值报警等功能的具体实现方式,并强调了数据可视化的应用价值。最后讨论了注气孔布置优化和注气压力控制的实际操作要点。 适合人群:从事煤矿开采及相关领域的技术人员、工程师,尤其是具有一定编程基础并对自动化控制感兴趣的从业者。 使用场景及目标:适用于煤矿瓦斯气驱项目的规划、实施与维护阶段,旨在提高瓦斯抽采效率,确保安全生产,同时减少人为因素导致的操作失误。通过学习本文提供的代码示例和技术方案,读者能够掌握如何运用Python解决实际工程问题的方法。 其他说明:文中提到的所有代码均为简化版本,用于解释相关概念和技术原理,在实际项目中可能需要进一步完善和优化。对于希望深入了解该领域的读者而言,本文不仅提供了实用的技术指导,也为后续研究奠定了良好的基础。

    故障诊断技术:基于神经网络的故障诊断.zip

    光电材料仿真,电子仿真等;从入门到精通教程;含代码案例解析。

    呼和浩特市_托克托县_街道级边界_150122_Shapefile.zip

    街道级行政区划边界,wgs84坐标系,shp数据,直接分析使用。

    字节码.md

    字节码.md

    Maven.md

    Maven.md

    基于NARX的多变量时间序列预测及Matlab实现详解

    内容概要:本文详细介绍了如何使用带有外源输入的非线性自回归网络(NARX)进行多变量时间序列预测,并提供了完整的Matlab代码实现。文章首先解释了NARX的基本概念,强调其能够同时考虑时间序列自身的历史值和其他相关变量(如政策变量)。接着逐步展示了从数据准备、创建NARX网络、训练网络到最后的预测与评估的具体步骤。文中还讨论了一些常见的挑战,如数据预处理、参数选择和模型优化技巧。此外,通过具体的例子演示了NARX在网络结构配置、训练方法选择等方面的应用细节。 适合人群:对时间序列预测感兴趣的研究人员和技术开发者,尤其是有一定Matlab基础并希望通过实例加深对NARX网络理解的人群。 使用场景及目标:适用于需要处理多变量时间序列预测任务的实际工程项目,如经济预测、电力系统负荷预测、空气质量预测等领域。目的是帮助读者掌握NARX网络的工作原理及其在不同应用场景下的具体实现方法。 其他说明:文章不仅提供理论指导,还包括大量实用的操作指南和代码片段,有助于读者快速上手实践。同时指出NARX虽然有效但对于长期依赖问题不如LSTM,但在特定条件下仍然是工业界的优选方案。

    阵列信号处理,极化敏感阵列天线,空域极化域联合谱估计Matlab代码.rar

    1.版本:matlab2014/2019a/2024a 2.附赠案例数据可直接运行matlab程序。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。

Global site tag (gtag.js) - Google Analytics