Как связать динамическое связывание @Named - PullRequest
0 голосов
/ 31 января 2019

Я хотел бы создать настраиваемый модуль, который будет связывать несколько разных вещей @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 Ответ

0 голосов
/ 01 февраля 2019

Вы оставили в стороне PrivateModule , но это именно то, что я бы использовал для решения вашей проблемы.Если я правильно угадал ваш источник KotlinModule, у него есть аналог в KotlinPrivateModule .

Документы Guice поддерживают это решение как «проблему ног робота» (представьте, что левую и правую ноги связывают с помощьюидентичные бедра, колени и голени, но разные левая и правая ноги) в ответе на часто задаваемые вопросы в виде вопроса «Как построить два одинаковых, но немного разных дерева объектов?» .

ВВ 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 аннотациях.Вы даже можете связать свой путь конфигурации как String (@Named("configPath") String или @ConfigPath String) и использовать его непосредственно в DbConfig, позволяя пометить DbConfig с помощью @Inject и избавиться от его метода @Provides.

(Для чего бы это ни стоило, если бы вы выбрали альтернативное решение, которое не использует PrivateModules и вместо этого использовали более длинные и более сложные операторы bind с Names.named, тогда DbModule("secondaryDb") и DbModule("tertiaryDb") сосуществуют простохорошо, если публичные привязки не конфликтуют друг с другом.)

...