Как связать динамическое связывание @Named
Я хотел бы создать настраиваемый модуль, который будет связывать несколько разных вещей @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 создавал dbconfig через ConfigProvider
и быть в состоянии использовать это в 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")
, но это можно решить с помощью частных модулей, которые я оставил, чтобы избежать дополнительной сложности)
1 ответ
Вы оставили в стороне PrivateModule, но это именно то, что я бы использовал, чтобы решить вашу проблему. Если я правильно угадал ваш источник KotlinModule, у него есть аналог в 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. Более того - и это реальная выгода для решения - в DbModule вы можете вводить DbConfig и DataSource напрямую, без @Named
аннотаций. Это значительно упрощает производство и потребление машин многократного использования, так как у ваших многоразовых деталей не будет @Named
аннотации для беспокойства. Вы даже можете связать свой путь конфигурации в виде строки (@Named("configPath") String
или же @ConfigPath String
) и сделать это непосредственно в DbConfig, что позволит вам пометить DbConfig с помощью @Inject
и избавиться от его @Provides
метод.
(Для чего это стоит, если вы пошли с альтернативным решением, которое не использует PrivateModules, а вместо этого использовали более длинные и более сложные bind
заявления с Names.named
, затем DbModule("secondaryDb")
а также DbModule("tertiaryDb")
будет прекрасно сосуществовать, пока публичные привязки не конфликтуют друг с другом.)