如何绑定动态@Named绑定 [英] How to bind a dynamic @Named binding
问题描述
我想创建一个可配置模块,该模块将绑定一些不同的@Named东西.使用该模块的应用程序/注入将提前知道@Name,但是模块本身直到运行时实例化才知道.
我在示例代码中使用了kotlin,但很高兴获得Java答案.
由于所有@Named批注都需要引用常量字符串,而不是运行时变量(An annotation argument must be a compile-time constant
),因此无法编译:
class DbModule(val configPath: String) : KotlinModule() {
@Provides
@Named(configPath) // <-- can't do this
fun provideDbConfig(loader: ConfigLoader): DbConfig {
// note ConfigLoader is separately bound,
// but a needed depenency of DbConfig
return DbConfig(loader, configPath)
}
@Provides
@Named(configPath) // <-- can't do this
fun provideDataSource(
@Named(configPath) // <-- can't do this
dbConfig: DbConfig): DataSource
{
return dbConfig.dataSource
}
}
我可以通过添加提供程序来使DbConfig绑定起作用:
private class ConfigProvider
@Inject constructor(
val loader: ConfigLoader,
@Named("configPath") val configPath: String
) : Provider<DbConfig> {
override fun get(): DbConfig {
return DbConfig(loader, configPath)
}
}
class DbModule(val configPath: String) : KotlinModule() {
override configure() {
bindConstant().annotatedWith(Names.named("configPath"))
.to(configPath)
bind<DbConfig>().annotatedWith(Names.named(configPath))
.toProvider(ConfigProvider::class.java)
}
}
但是我不确定如何获取具有正确的configPath
带注释的DbConfig()
的Provider<DataSource>
以便它可以使DataSource
脱离配置?我也许可以拥有一个DataSourceProvider
来构造自己的DbConfig(configPath)
,其方式与ConfigProvider
相同,但似乎最好是让guice通过ConfigProvider
创建dbconfig并能够在DataSourceProvider
?
最后,我希望能够注入以下内容:
class BusinessObject1
@Inject constructor(
@Named("secondaryDb") val dbConfig: DbConfig
)
class BusinessObject2
@Inject constructor(
@Named("secondaryDb") val dataSource: DataSource
)
假定这些对象是由注入器创建的:
Guice.createInjector(DbModule("secondaryDb"))
(还请注意,上面的代码不允许同时创建DbModule("secondaryDb")
和DbModule("tertiaryDb")
,但是可以使用私有模块来解决,为避免额外的复杂性,我已经离开了该模块)
您已经将 KotlinPrivateModule .
Guice的文档在其常见问题解答(FAQ)中将这种解决方案称为机器人腿问题"(想象将大腿,膝盖和小腿相同但左腿和右腿绑在一起,但左腿和右腿绑在一起,但问题不存在)作为问题.
在Java中,它看起来像:
public class DbModule extends PrivateModule {
private final String configPath;
public DbModule(String configPath) { this.configPath = configPath; }
// (no @Named annotation; bind it like it's the only one!)
@Provides DbConfig provideDbConfig(ConfigLoader loader) {
return new DbConfig(loader, configPath);
}
// (no @Named annotation; bind it like it's the only one!)
@Provides DataSource provideDataSource(DbConfig dbConfig) {
return dbConfig.dataSource;
}
@Override public void configure() {
// now bind the unqualified one to the qualified one
bind(DbConfig.class).annotatedWith(Names.named(configPath)).to(DbConfig.class);
bind(DataSource.class).annotatedWith(Names.named(configPath)).to(DataSource.class);
// and now you can expose only the qualified ones
expose(DbConfig.class).annotatedWith(Names.named(configPath));
expose(DataSource.class).annotatedWith(Names.named(configPath));
}
}
这样,您的@Provides
方法无需尝试使用仅在运行时可用的批注,并且不会因不合格的DbConfig和DataSource绑定而使全局Injector混乱.此外,这是该解决方案的真正好处,您可以在DbModule中直接注入DbConfig和DataSource,而无需使用@Named
批注.这使生产和使用可重复使用的机器变得更加容易,因为您的可重复使用的部件将不会有任何@Named
注释.您甚至可以将配置路径绑定为字符串(@Named("configPath") String
或@ConfigPath String
),并将其直接注入DbConfig中,从而允许您使用@Inject
标记DbConfig并摆脱其@Provides
方法.
(关于它的价值,如果您使用了不使用PrivateModules的替代解决方案,而是使用了更长且更复杂的bind
语句和Names.named
,那么DbModule("secondaryDb")
和DbModule("tertiaryDb")
将共存只要公共绑定不相互冲突就可以了.)
I'd like to create a configurable Module that will bind a few different @Named things. Applications/injections that use the module will know the @Name ahead of time, but the module itself won't know until it's instantiated at runtime.
I'm using kotlin in my example code, but happy for java answers.
This fails to compile because all of the @Named annotations need to refer to constant strings, not runtime variables (An annotation argument must be a compile-time constant
):
class DbModule(val configPath: String) : KotlinModule() {
@Provides
@Named(configPath) // <-- can't do this
fun provideDbConfig(loader: ConfigLoader): DbConfig {
// note ConfigLoader is separately bound,
// but a needed depenency of DbConfig
return DbConfig(loader, configPath)
}
@Provides
@Named(configPath) // <-- can't do this
fun provideDataSource(
@Named(configPath) // <-- can't do this
dbConfig: DbConfig): DataSource
{
return dbConfig.dataSource
}
}
I can get the DbConfig binding to work by adding a Provider:
private class ConfigProvider
@Inject constructor(
val loader: ConfigLoader,
@Named("configPath") val configPath: String
) : Provider<DbConfig> {
override fun get(): DbConfig {
return DbConfig(loader, configPath)
}
}
class DbModule(val configPath: String) : KotlinModule() {
override configure() {
bindConstant().annotatedWith(Names.named("configPath"))
.to(configPath)
bind<DbConfig>().annotatedWith(Names.named(configPath))
.toProvider(ConfigProvider::class.java)
}
}
But I'm not sure how to get a Provider<DataSource>
that would have the correct configPath
annotated DbConfig()
available to it so that it can get the DataSource
out of the config? I might be able to have a DataSourceProvider
that constructs its own DbConfig(configPath)
the same way the ConfigProvider
does, but it seems preferable to have guice create the dbconfig via the ConfigProvider
and be able to leverage that in the DataSourceProvider
?
At the end of this, I'd like to be able to have the following injected:
class BusinessObject1
@Inject constructor(
@Named("secondaryDb") val dbConfig: DbConfig
)
class BusinessObject2
@Inject constructor(
@Named("secondaryDb") val dataSource: DataSource
)
Assuming that those objects are created by an injector:
Guice.createInjector(DbModule("secondaryDb"))
(also note the code above won't allow creation of both DbModule("secondaryDb")
and DbModule("tertiaryDb")
, but that can be solved with private modules, which I've left to avoid additional complexity)
You've left aside PrivateModule, but that's exactly what I'd use to solve your problem. If I've guessed your KotlinModule source correctly, it has a counterpart in KotlinPrivateModule.
Guice docs advocate for this solution as the "robot legs problem" (imagine binding left and right legs with identical thighs, knees, and shins, but different left and right legs) in its FAQ as the question "How do I build two similar but slightly different trees of objects?".
In Java this would look like:
public class DbModule extends PrivateModule {
private final String configPath;
public DbModule(String configPath) { this.configPath = configPath; }
// (no @Named annotation; bind it like it's the only one!)
@Provides DbConfig provideDbConfig(ConfigLoader loader) {
return new DbConfig(loader, configPath);
}
// (no @Named annotation; bind it like it's the only one!)
@Provides DataSource provideDataSource(DbConfig dbConfig) {
return dbConfig.dataSource;
}
@Override public void configure() {
// now bind the unqualified one to the qualified one
bind(DbConfig.class).annotatedWith(Names.named(configPath)).to(DbConfig.class);
bind(DataSource.class).annotatedWith(Names.named(configPath)).to(DataSource.class);
// and now you can expose only the qualified ones
expose(DbConfig.class).annotatedWith(Names.named(configPath));
expose(DataSource.class).annotatedWith(Names.named(configPath));
}
}
This way your @Provides
methods don't need to try to use an annotation that is only available at runtime, and you don't clutter the global Injector with unqualified DbConfig and DataSource binding. Futhermore—and this is the real benefit to the solution—within DbModule you can inject DbConfig and DataSource directly without @Named
annotations. This makes it much easier to produce and consume reusable machinery, since your reusable pieces won't have any @Named
annotations to worry about. You could even bind your config path as a String (@Named("configPath") String
or @ConfigPath String
) and have that directly injectable into DbConfig, allowing you to mark DbConfig with @Inject
and get rid of its @Provides
method.
(For what it's worth, if you had gone with an alternate solution that doesn't use PrivateModules and instead used longer and more-complex bind
statements with Names.named
, then DbModule("secondaryDb")
and DbModule("tertiaryDb")
would coexist just fine as long as the public bindings don't conflict with one another.)
这篇关于如何绑定动态@Named绑定的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!