关于Scala:使用Guice在Play框架中注入配置值

我使用conf/application.conf播放了Web应用程序(没什么异常)。 Guice用于依赖项注入。如何在类构造函数中注入属性值?代码如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
class MyController @Inject() (private val foo: Foo) extends Controller {
    ...
}

@ImplementedBy(classOf[FooImpl])
trait Foo {
    def bar: String
}

class FooImpl extends Foo {
    override val bar = current.configuration.getString("my.bar").get
    ...
}

在当前配置中,如果不运行应用程序,则无法测试FooImpl。我希望能够在单元测试中实例化FooImpl。完美的解决方案(从我的angular来看)应如下所示:

1
2
3
class FooImpl @Inject() (@Named("my.bar") override val bar: String) extends Foo {
    ...
}

不幸的是,此代码不起作用,因为Guice没有'my.bar'绑定:

No implementation for java.lang.String annotated with @com.google.inject.name.Named(value=my.bar) was bound.

我想到的唯一解决方案是编写自己的模块,该模块循环访问配置属性并将其绑定为命名依赖项(此文档中的示例的变体)。但是我相信存在更好的方法。


我使用Java实现了这一点。我希望您可以将其用作Scala实现的参考。

首先,我创建了一个模块:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MainModule extends AbstractModule {
    public final static String TIMEOUT_IN_MILLISECONDS_ANNOTATION ="timeout-promise";
    private final Configuration configuration;

    public MainModule(@SuppressWarnings("unused") Environment environment, Configuration configuration) {
        this.configuration = configuration;
    }

    @Override
    protected void configure() {
        long timeoutInMilliseconds = configuration.getLong("application.promise.timeout.in.milliseconds", 0L);
        bindConstant().annotatedWith(Names.named(TIMEOUT_IN_MILLISECONDS_ANNOTATION)).to(timeoutInMilliseconds);
    }
}

在那之后,我只是在不同的地方使用了注释:

1
2
3
4
5
6
7
class Service {

    @Inject
    @Named(MainModule.TIMEOUT_IN_MILLISECONDS_ANNOTATION)
    protected long timeoutInMilliseconds;

}

希望这会有所帮助。


大约一年后,我遇到了相同的问题,这次提出了以下解决方案(非常类似于@ stranger-in-the-q和@droidman提出的解决方案):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class InjectionModule extends AbstractModule {

  override def configure(): Unit = {

    val config: Config = TypesafeConfigReader.config
    config.entrySet().asScala.foreach { entry =>
      val path = entry.getKey
      entry.getValue.valueType() match {
        case ConfigValueType.NUMBER =>
          bind(classOf[Int])
            .annotatedWith(Names.named(path))
            .toInstance(config.getInt(path))
        case ConfigValueType.BOOLEAN =>
           bind(classOf[Boolean])
             .annotatedWith(Names.named(path))
             .toInstance(config.getBoolean(path))
        case ConfigValueType.STRING =>
           bind(classOf[String])
             .annotatedWith(Names.named(path))
             .toInstance(config.getString(path))
        case _ =>
      }
    }
  }
}

此外,可以通过将前缀附加到系统属性(键/值对是已加载配置的一部分)来扩展此方法:

1
2
3
4
5
6
7
8
private def getPrefix(configValue: ConfigValue): String = {
  val description = configValue.origin().description()
  if (description.contains("system properties")) {
   "sys."
  } else {
   ""
  }
}

在这种情况下,应该写Names.named(getPrefix(entry.getValue) + path)而不是写Names.named(path)


前段时间,我针对映射到Enum

上的简单注入配置变量开发了小型guice扩展

guice-config


要从播放配置中注入多个属性,您可以这样做。创建一个不在Play配置中的地图,并将其作为"属性"传递给Guice活页夹。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Module extends AbstractModule {

    private Environment environment;
    private Configuration configuration;

    public Module(Environment environment,Configuration configuration){
        this.environment = environment;
        this.configuration = configuration;
    }

    @Override
    public void configure() {
        Configuration helloConf = configuration.getConfig("myconfig");
        Map<String, Object> map = helloConf.asMap();
        Properties properties = new Properties();
        properties.putAll(map);        
        Names.bindProperties(binder(), properties);
    }
}