首页 文章

Guice多个实现,带有依赖关系的参数化构造函数

提问于
浏览
1

我正在努力解决特定的依赖注入问题,而我似乎无法弄明白 . 仅供参考:我是新手,但我有其他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 回答

  • 4

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

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

  • 1

    不要在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方法 . )

相关问题