Guice 多重实现,带依赖的参数化构造函数

Guice multiple implementations, parameterized constructor with dependencies

我正在努力解决一个特定的依赖项注入问题,但我似乎无法弄清楚。仅供参考:我是 guice 的新手,但我有使用其他 DI 框架的经验 - 这就是为什么我认为这不应该太复杂来实现。

我在做什么: 我正在研究 Lagom 多模块项目并使用 Guice 作为 DI。

我想达到的目标: 将某些接口实现的多个命名实例(我们称之为发布者,因为它将向 kafka 主题发布消息)注入我的服务。 'publisher' 注入了一些 Lagom 和 Akka 相关服务(ServiceLocator、ActorSystem、Materializer 等)。

现在我想要这样的发布者的两个实例,每个都会将消息发布到不同的主题(因此每个主题一个发布者实例)。

我将如何实现? 我对同一主题的一个实例或多个实例没有问题,但如果我想为每个实例注入不同的主题名称,我就有问题了。

所以我的发布者实现构造函数看起来像这样:

@Inject
public PublisherImpl(
    @Named("topicName") String topic,
    ServiceLocator serviceLocator,
    ActorSystem actorSystem,
    Materializer materializer,
    ApplicationLifecycle applicationLifecycle) {
...
}

如果我想创建一个实例,我会在我的 ServiceModule 中这样做:

public class FeedListenerServiceModule extends AbstractModule implements ServiceGuiceSupport {
    @Override
    protected void configure() {
        bindService(MyService.class, MyServiceImpl.class);
        bindConstant().annotatedWith(Names.named("topicName")).to("topicOne");
        bind(Publisher.class).annotatedWith(Names.named("publisherOne")).to(PublisherImpl.class);
    }
}

我如何为自己的主题绑定多个发布者?

我正在尝试实现另一个私有模块:

public class PublisherModule extends PrivateModule {

    private String publisherName;
    private String topicName;

    public PublisherModule(String publisherName, String topicName) {
        this.publisherName = publisherName;
        this.topicName = topicName;
    }

    @Override
    protected void configure() {
        bindConstant().annotatedWith(Names.named("topicName")).to(topicName);
        bind(Publisher.class).annotatedWith(Names.named(publisherName)).to(PublisherImpl.class);
    }
}

但这让我无处可去,因为你无法在你的模块配置方法中获得注入器:

Injector injector = Guice.createInjector(this); // This will throw IllegalStateException : Re-entry is not allowed
injector.createChildInjector(
    new PublisherModule("publisherOne", "topicOne"),
    new PublisherModule("publisherTwo", "topicTwo"));

唯一简单有效的解决方案是我将我的 PublisherImpl 更改为抽象,为其添加抽象 'getTopic()' 方法并添加另外两个具有主题覆盖的实现。

但是这个解决方案很蹩脚。为代码重用添加额外的继承并不是最佳实践。另外我相信Guice肯定支持这样的功能。

欢迎任何建议。 KR, Nejc

Guice 的依赖注入方法是 DI 框架补充您的实例化逻辑,而不是取代它。在可能的情况下,它会为您实例化一些东西,但它不会试图对此太聪明。它还不会将配置(主题名称)与依赖注入混淆——它只做一件事,DI,并且把那件事做好。所以你不能用它来配置东西,比如你可以用 Spring 的方式。

因此,如果您想用两个不同的参数实例化一个对象,那么您可以用两个不同的参数实例化该对象 - 即,您调用 new 两次。这可以通过使用提供程序方法来完成,记录在此处:

https://github.com/google/guice/wiki/ProvidesMethods

在您的情况下,它可能类似于将以下方法添加到您的模块中:

@Provides
@Named("publisherOne")
@Singleton
Publisher providePublisherOne(ServiceLocator serviceLocator,
    ActorSystem actorSystem,
    Materializer materializer,
    ApplicationLifecycle applicationLifecycle) {
  return new PublisherImpl("topicOne", serviceLocator, 
      actorSystem, materializer, applicationLifecycle);
}

此外,如果要添加生命周期挂钩,您可能希望它是单例,否则每次实例化时添加新挂钩时都可能 运行 导致内存泄漏。

不要在配置方法中创建新的注入器。相反,install the new modules you create. No child injectors needed—as in the PrivateModule 文档,"Private modules are implemented using parent injectors",所以无论如何都涉及到一个子注射器。

install(new PublisherModule("publisherOne", "topicOne"));
install(new PublisherModule("publisherTwo", "topicTwo"));

你使用 PrivateModule 的技术是我在这种情况下使用的技术,特别是考虑到希望通过绑定注释使绑定可用,特别是如果全套主题在运行。您甚至可以循环调用 install

但是,如果您需要任意数量的实现,您可能想要创建一个可注入工厂或提供程序,您可以在运行时向其传递字符串集。

public class PublisherProvider {
  // You can inject Provider<T> for all T bindings in Guice, automatically, which
  // lets you configure in your Module whether or not instances are shared.
  @Inject private final Provider<ServiceLocator> serviceLocatorProvider;
  // ...

  private final Map<String, Publisher> publisherMap = new HashMap<>();

  public Publisher publisherFor(String topicName) {
    if (publisherMap.containsKey(topicName)) {
      return publisherMap.get(topicName);
    } else {
      PublisherImpl publisherImpl = new PublisherImpl(
          topicName, serviceLocatorProvider.get(), actorSystemProvider.get(),
          materializerProvider.get(), applicationLifecycleProvider.get());
      publisherMap.put(topicName, publisherImpl);
      return publisherImpl;
    }
  }
}

您可能希望使上述线程安全;此外,您可以通过使用 assisted injection (FactoryModuleBuilder) or AutoFactory 来避免显式构造函数调用,它会自动传递显式参数,例如 topicName,同时注入 DI 提供程序,例如 ServiceLocator(希望它有一个特定的目的,因为您可能不需要太多的服务定位无论如何都在 DI 框架内!)。

(旁注:不要忘记 expose 你的 PrivateModule 注释绑定。如果你没有发现自己在其他任何地方注入你的 topicName,你也可以考虑使用个人 @Provides 方法与上面的辅助注入或 AutoFactory 方法,但如果您希望每个 Publisher 需要一个不同的对象图,您可以选择 PrivateModule 方法。)