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更改为abstract,添加抽象'getTopic()'方法并添加两个主题覆盖实现 .

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

欢迎任何建议 . KR,Nejc

回答(2)

2 years ago

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

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

2 years ago

不要在configure方法中创建新的Injector . 相反,install您创建的新模块 . 不需要儿童注射器 - 如PrivateModule文档,"Private modules are implemented using parent injectors",所以无论如何都要涉及儿童注射器 .

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

您使用PrivateModule的技术是我在这种情况下使用的技术,特别是考虑到希望通过绑定注释使绑定可用,特别是如果在运行时已知完整的主题集 . 您甚至可以将循环调用 install .

但是,如果需要任意数量的实现,则可能需要创建可在运行时传递String集的可注入工厂或提供程序 .

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 injectionFactoryModuleBuilder)或AutoFactory来避免显式构造函数调用,这将自动传递显式参数(如topicName),同时注入像ServiceLocator这样的DI提供程序(希望具有特定目的,因为您可能不需要太多服务 - 无论如何都要在DI框架内定位!) .

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