如何让工厂与 Guice 协同工作?

How to make factories work well with Guice?

在我的项目中,我处处使用依赖注入,并且有两种情况使用了ad hoc factories。首先,当我想精确控制一个实例何时被创建时,我注入了一个工厂而不是一个实例:

// WidgetA must be created before WidgetB, because of the side-effects
// on the container.
WidgetAFactory.make(container);
WidgetBFactory.make(container);

另一种情况是构造函数混合使用可注入值和运行时值。而不是使用:

@Inject
WidgetC(
    Container,
    @WidgetCFont Font,
    @WidgetCColor Color,
    @Named("flag") String flag) {
  ...
}

我使用:

@Inject
WidgetCFactory(
    @WidgetCFont Font font,
    @WidgetCColor Color color,
    @Named("flag") String flag) {
  ...
}

WidgetCFactory.make(Container container) {
 return new WidgetC(container, font, color, flag);
}

但是我在使用工厂时遇到两个限制:

  1. 在我的第一个例子中,我还需要 WidgetA 成为其他 @Injected 构造函数需要的 @Singleton。到目前为止,我的解决方案是把我在调用工厂时创建的实例存储起来,@Provides给其他人使用。有没有办法让这个单例的控制权回到 guice 而不必自己维护该实例?

  2. 在我的第二个示例中,管理注入的依赖项是一团糟:WidgetCFactory 必须使用一长串注入值调用 WidgetC 构造函数,必须针对依赖项中的每次更改更新该列表,没有注释检查。有没有办法为 Guice 提供运行时参数,并让它处理其他依赖项?

感觉对于这两种情况,我都可以使用将被赋予运行时值的子注入器,并让 Guice 成为工厂:

public static class WidgetCFactory {
  private final Injector injector;

  @Inject
  public WidgetCFactory(Injector injector) {
    this.injector = injector;
  }

  public WidgetC make(Container container) {
    Injector childInjector = injector.createChildInjector(new AbstractModule() {
      @Override protected void configure() {
        bind(Container.class).toInstance(container);
      }
    });
    return childInjector.getInstance(WidgetC.class);
  }
}

但我没有发现很多人这样做的案例。是因为它太重了,还是超出了依赖注入的良好实践?什么是更好的方法?

混合注入值和运行时值意味着您应该查看 "assisted injection",它允许您声明某些注入值由调用站点在运行时提供,并生成一个工厂,该工厂将仅公开那些作为一个参数。从 https://github.com/google/guice/wiki/AssistedInject 开始,您需要为要以这种方式处理的每种类型安装一个模块,例如

// this goes in your existing Module.configure()
install(new FactoryModuleBuilder()
     // you can add more than one type here in this way
     .implement(WidgetC.class, WidgetC.class)
     .build(WidgetFactory.class));

//...

public interface WidgetFactory {
    // you can add more than one method here 
    WidgetC createWidgetC(Container container);
}

@AssistedInject
WidgetC(
    @Assisted Container,
    @WidgetCFont Font,
    @WidgetCColor Color,
    @Named("flag") String flag) {
  ...
}

请特别注意对 WidgetC 构造函数的更改,构造函数上的不同注释(因为通过正常注入构造实际上并不安全)和 Container 参数(这将由工厂提供,而不是 IoC 容器。


要使 WidgetA 成为单例,您可以使用 @Singleton 修饰类型,或者在 configure() 方法中绑定它:

bind(WidgetA.class).in(Singleton.class);

正如所写,它会被懒惰地创建——它只会在第一次被请求后才存在,但之后每次被请求时,它都是同一个实例,不会从头开始创建。

回应 , Assisted Injection is the way to go, and Guice has offered Assisted Injection (through a separate dependency/JAR) since 2.0. You can read more about Guice's implementation on the Guice wiki AssistedInject page,但除了 Colin 写的内容,我没有任何示例。

您可以考虑的另一种选择是 AutoFactory,它会为您生成代码工厂实现。 (它是 Google Auto 的一部分,Auto 是 Java 的一套代码生成器,可以创建注释实现、服务、不可变值对象和工厂。)它是 Dagger 的事实标准,但适用于任何 JSR -330 框架,包括 Guice。


关于您的问题 #1,我将与 Colin 不同,您正在寻找的东西本质上有些危险:如果 @Singleton 个对象在您的应用程序的生命周期内存在,但您的 WidgetA Factory接受一个 Container,那么你的 WidgetA 有可能在你的 Container 准备好之前存在,或者它在你的 Container 被销毁之后存在。

如果你的WidgetA Container也是@Singleton,那么你可以创建没有Factory的WidgetA,一切顺利:你可以跳过Factory,绑定Container,正常绑定WidgetA,注入一个Provider<WidgetA>(无需额外配置即可使用)延迟 WidgetA 的创建,直到您准备好。

如果您的真正要求是 WidgetA 与 Container 存在的时间一样长,但 WidgetA/B/C 到那时都使用相同的 Container 和 WidgetA,您可以考虑 a child injector 其中你绑定你的容器和小部件。这样每个 Container 都有自己的 WidgetA,WidgetA 的每次注入在那个容器内都是一致的,当你得到一个新的 Container 时你将处理掉 WidgetA。当然,如果您的 Container 在您的 Injector 工作后才开始可用,并且在那之后是一致的,您可以使用该子注入器作为您的主注入器,然后让 WidgetA 工作。

如果您的 WidgetA 依赖的 Container 启动时不可用,请注意:这可能是一个 "scope-widening injection",因为您的 Container 将在 WidgetA 中作为 @Singleton 存在,即使否则它将被垃圾收集。这充其量可能是内存泄漏,并且可能会在您的应用程序中存在多个容器时导致奇怪的错误。您可以像一直使用的那样使用有状态模块,但无论如何都要非常小心。