如何使工厂与Guice良好配合?

时间:2019-04-19 14:36:46

标签: dependency-injection guice factory

在我的项目中,我在所有地方都使用了依赖注入,并且在两种情况下都使用了临时工厂。首先,当我想精确控制创建实例的时间时,我注入了一个工厂而不是实例:

// 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。到目前为止,我的解决方案是存储我在调用工厂时创建的实例,并@Provids提供给其他人使用。有没有办法让这个单例的控制权回到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);
  }
}

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

2 个答案:

答案 0 :(得分:4)

混合注入值和运行时值意味着您应该查看“辅助注入”,它使您可以声明某些特定注入值将在运行时由调用站点提供,并生成将仅公开那些作为参数的工厂。在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);

按照书面规定,它将被懒惰地创建-仅在首次请求后才存在,但是每次请求时,它都是同一实例,不会从头开始创建。

答案 1 :(得分:2)

要回应Colin's correct answer,必须使用辅助注入,而Guice从2.0开始就提供了辅助注入(通过单独的依赖项/ JAR)。您可以在Guice wiki AssistedInject page上阅读有关Guice的实现的更多信息,但是除了Colin编写的内容之外,我没有其他示例。

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


关于问题#1,我将与Colin背道而驰,说您正在寻找的内容本质上有些危险:如果在应用程序的生存期内存在@Singleton个对象,但是WidgetA Factory需要一个容器,那么您的WidgetA可能在容器准备就绪之前就存在,或者在容器被销毁之后就存在。

如果您的WidgetA容器也是@Singleton,那么您可以在没有工厂的情况下创建WidgetA,并且一切顺利:您可以跳过工厂,绑定容器,正常绑定WidgetA并注入{{1} }(无需额外配置即可使用)以延迟WidgetA的创建,直到准备就绪为止。

如果您的真正要求是在容器存在的同时就存在WidgetA,但要让WidgetA / B / C当时都使用相同的Container和WidgetA,则可以考虑将a child injector绑定到您的容器和小部件。这样,每个Container都会获得自己的WidgetA,在该容器中每次注入WidgetA都是一致的,并且在获得新Container时将处置WidgetA。当然,如果您的Container仅在Injector正常工作后才开始可用,并且在此之后保持稳定,则可以将该子注射器用作主注射器,然后让WidgetA工作。

如果您的WidgetA依赖于尚未启动的容器,请当心:这可能是“范围扩大的注入”,因为即使您的容器在WidgetA中仍将以Provider<WidgetA>的形式存在否则将被垃圾收集。充其量这可能是内存泄漏,并且在您的应用程序中存在多个容器的情况下,可能会引起奇怪的错误。您可以像往常一样使用有状态模块,但是无论如何都要非常小心。