我正在努力解决一个特定的依赖注入问题,我似乎无法弄清楚。仅供参考:我是 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) {
...
}
如果我想创建一个实例,我会在我的服务模块中这样做:
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肯定必须支持这样的功能。
欢迎任何建议。 韩国,内杰克
不要在配置方法中创建新的注入器。相反,install
您创建的新模块。不需要子注入器 — 如 PrivateModule 文档中的"私有模块是使用父注入器实现的",因此无论如何都涉及一个子注入器。
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;
}
}
}
您可能希望使上述线程安全;此外,您可以使用辅助注入(FactoryModuleBuilder)或AutoFactory来避免显式构造函数调用,这将在注入像ServiceLocator这样的DI提供程序时自动传递显式参数(如topicName)(希望有一个特定的目的,因为无论如何你可能不需要在DI框架中定位太多服务!)。
(旁注:不要忘记expose
PrivateModule 的注释绑定。如果您没有发现自己在其他任何地方注入topicName
,您也可以考虑将单独的@Provides
方法与上面的辅助注入或 AutoFactory 方法一起使用,但如果您希望每个发布服务器需要不同的对象图,则无论如何都可以选择 PrivateModule 方法。
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);
}
此外,如果要添加生命周期挂钩,您可能希望它是单例,否则每次实例化新钩子时都可能遇到内存泄漏。