如何让工厂与Guice合作?

问题描述 投票:0回答:2

在我的项目中,我在任何地方使用依赖注入,并且我在两个案例中使用临时工厂。首先,当我想要精确控制创建实例的时间时,我会注入工厂而不是实例:

// 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);
  }
}

但我没有发现很多人这样做的情况。是因为它太重了,还是在依赖注入的良好做法之外?什么是更好的方式?

dependency-injection guice factory
2个回答
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);

如上所述,它将被懒惰地创建 - 它将仅在首次请求之后存在,但是每次请求它时,它将是相同的实例并且不会从头开始创建。


2
投票

为了回应Colin's correct answer,辅助注射是最佳选择,Guice自2.0以来就提供了辅助注射(通过单独的依赖/ JAR)。你可以在Guice wiki AssistedInject page上阅读更多关于Guice的实现,但除了科林所写的内容之外我不会有任何实例。

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


关于你的问题#1,我将与科林分道扬声,说你正在寻找的东西本来就有些危险:如果@Singleton对象在你的应用程序的生命周期中存在,但你的WidgetA工厂需要一个容器,那么它可能是您的WidgetA在Container准备好之前存在,或者在Container被销毁之后存在。

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

如果您的实际请求是WidgetA确实存在,只要Container存在,但是对于WidgetA / B / C,所有人都使用相同的Container和WidgetA,您可以考虑a child injector绑定Container和Widgets的位置。这样每个Container都有自己的WidgetA,WidgetA的每次注入在该容器中都是一致的,当你得到一个新的Container时,你将处理WidgetA。当然,如果您的容器仅在您的Injector工作后才开始可用并且在此之后保持一致,您可以使用该子注射器作为主注射器并在此之后使用WidgetA。

如果你的WidgetA依赖于一个没有开始的容器,那么要小心:这可能是一个“范围扩大注入”,因为你的容器将在WidgetA中以@Singleton的形式存在,即使它会被垃圾收集。这可能是内存泄漏,并且可能导致应用程序中存在多个容器的奇怪错误。您可以使用有状态的模块,但在任何情况下都要非常小心。

© www.soinside.com 2019 - 2024. All rights reserved.