我想创建一个可配置模块,该模块将绑定一些不同的@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)
}
}
但是我不确定如何获得一个Provider<DataSource>
并带有正确的configPath
注释的DbConfig()
,以便它可以使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")
,但是可以使用私有模块来解决,为避免额外的复杂性,我将其保留了)
答案 0 :(得分:1)
您已经抛开PrivateModule,但这正是我用来解决您的问题的方法。如果我猜对了您的KotlinModule源,那么它在KotlinPrivateModule中有一个对应的源。
Guice Docs在其常见问题解答中将问题称为"How do I build two similar but slightly different trees of objects?",将其称为“机器人腿问题”(想象用相同的大腿,膝盖和小腿绑扎左右腿,但用不同的左右腿绑住腿)
在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并摆脱其{{1 }}方法。
(关于它的价值,如果您使用了不使用PrivateModules而是使用更长和更复杂的@Provides
语句和bind
的替代解决方案,则Names.named
并且只要公共绑定不互相冲突,DbModule("secondaryDb")
就可以共存。