我正在努力解决特定的依赖注入问题,而我似乎无法弄明白。仅供参考:我是新手,但我有其他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
答案 0 :(得分:4)
不要在configure方法中创建新的Injector。相反,install
您创建的新模块。不需要儿童注射器 - 如PrivateModule文档中所述,#34;私人模块是使用父注射器实现的,#34;因此无论如何都有儿童注射器。
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)或AutoFactory来避免显式构造函数调用,这会自动传递显式参数(如topicName),同时注入像ServiceLocator这样的DI提供程序(希望有一个特定的目的,因为你可能不需要在DI框架中进行太多的服务定位!)。
(旁注:不要忘记expose
您的私人模块的注释绑定。如果您发现自己没有在其他任何地方注入topicName
,您也可以考虑使用上面使用辅助注入或AutoFactory方法的单个@Provides
方法,但是如果您希望每个Publisher都需要不同的对象图,则无论如何都可以选择PrivateModule方法。)
答案 1 :(得分: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);
}
另外,如果你要添加一个生命周期钩子,你可能希望它是一个单例,否则你每次添加一个新的钩子时都会遇到内存泄漏。