如何绑定动态@Named绑定

时间:2019-01-31 20:23:35

标签: guice

我想创建一个可配置模块,该模块将绑定一些不同的@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"),但是可以使用私有模块来解决,为避免额外的复杂性,我将其保留了)

1 个答案:

答案 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")就可以共存。