如何绑定动态@Named 绑定

How to bind a dynamic @Named binding

我想创建一个可配置的模块,它将绑定一些不同的@Named 事物。 Applications/injections 使用模块的人会提前知道@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
    }
}

我可以通过添加 Provider 使 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 以与 ConfigProvider 相同的方式构造自己的 DbConfig(configPath),但让 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"),但这可以通过私有模块解决,我将其保留以避免额外的复杂性)

你离开了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 文档在其常见问题解答中将此解决方案提倡为 "robot legs problem"(想象一下用相同的大腿、膝盖和小腿绑住左右腿,但左右腿不同)"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 并摆脱其 @Provides方法。

(对于它的价值,如果您使用了不使用 PrivateModules 的替代解决方案,而是使用更长更复杂的 bind 语句和 Names.named,那么 DbModule("secondaryDb")DbModule("tertiaryDb") 可以共存,只要 public 绑定不相互冲突。)