1

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

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

我想要实现的目标:向我的服务注入一些接口实现的多个命名实例(我们称之为发布者,因为它会将消息发布到 kafka 主题)。这个“发布者”已经注入了一些 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 肯定必须支持这样的功能。

欢迎任何建议。韩国,NEJC

4

2 回答 2

4

不要在配置方法中创建新的注入器。相反,install您创建的新模块。不需要子注入器——就像在PrivateModule文档中一样,“私有模块是使用父注入器实现的”,所以无论如何都会涉及到一个子注入器。

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

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

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

于 2017-05-26T18:49:58.940 回答
1

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

此外,如果您要添加生命周期挂钩,您可能希望它成为单例,否则每次实例化时添加新挂钩时,您可能会遇到内存泄漏。

于 2017-05-26T14:10:57.600 回答