如何绑定动态@Named绑定 [英] How to bind a dynamic @Named binding

查看:86
本文介绍了如何绑定动态@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)

    }
}

但是我不确定如何获取具有正确的configPath带注释的DbConfig()Provider<DataSource>以便它可以使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"),但是可以使用私有模块来解决,为避免额外的复杂性,我已经离开了该模块)

解决方案

您已经将.

在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")将共存只要公共绑定不相互冲突就可以了.)

I'd like to create a configurable Module that will bind a few different @Named things. Applications/injections that use the module will know the @Name ahead of time, but the module itself won't know until it's instantiated at runtime.

I'm using kotlin in my example code, but happy for java answers.

This fails to compile because all of the @Named annotations need to refer to constant strings, not runtime variables (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
    }
}

I can get the DbConfig binding to work by adding a Provider:

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)

    }
}

But I'm not sure how to get a Provider<DataSource> that would have the correct configPath annotated DbConfig() available to it so that it can get the DataSource out of the config? I might be able to have a DataSourceProvider that constructs its own DbConfig(configPath) the same way the ConfigProvider does, but it seems preferable to have guice create the dbconfig via the ConfigProvider and be able to leverage that in the DataSourceProvider?

At the end of this, I'd like to be able to have the following injected:

class BusinessObject1
    @Inject constructor(
        @Named("secondaryDb") val dbConfig: DbConfig
    )
class BusinessObject2
    @Inject constructor(
        @Named("secondaryDb") val dataSource: DataSource
    )

Assuming that those objects are created by an injector:

Guice.createInjector(DbModule("secondaryDb"))

(also note the code above won't allow creation of both DbModule("secondaryDb") and DbModule("tertiaryDb"), but that can be solved with private modules, which I've left to avoid additional complexity)

解决方案

You've left aside 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 docs advocate for this solution as the "robot legs problem" (imagine binding left and right legs with identical thighs, knees, and shins, but different left and right legs) in its FAQ as the question "How do I build two similar but slightly different trees of objects?".

In Java this would look like:

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));
  }
}

This way your @Provides methods don't need to try to use an annotation that is only available at runtime, and you don't clutter the global Injector with unqualified DbConfig and DataSource binding. Futhermore—and this is the real benefit to the solution—within DbModule you can inject DbConfig and DataSource directly without @Named annotations. This makes it much easier to produce and consume reusable machinery, since your reusable pieces won't have any @Named annotations to worry about. You could even bind your config path as a String (@Named("configPath") String or @ConfigPath String) and have that directly injectable into DbConfig, allowing you to mark DbConfig with @Inject and get rid of its @Provides method.

(For what it's worth, if you had gone with an alternate solution that doesn't use PrivateModules and instead used longer and more-complex bind statements with Names.named, then DbModule("secondaryDb") and DbModule("tertiaryDb") would coexist just fine as long as the public bindings don't conflict with one another.)

这篇关于如何绑定动态@Named绑定的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆