播放Scala依赖注入:如何使用它

kon*_*tor 8 dependency-injection scala playframework-2.0

我正在尝试使用Play 2.5依赖注入.我有以下类调用REST api并解析响应

class Client @Inject()(ws:WSClient, baseUrl: string) {

  def this(ws:WSClient) = this(ws, "<url string>")
  def getResponse() = {....}
....

}
Run Code Online (Sandbox Code Playgroud)

代码的调用者如下所示

var client = new Client(WS.client)
client.getResponse()
Run Code Online (Sandbox Code Playgroud)

我收到了警告.

不推荐使用包ws中的对象WS:将WSClient注入到组件中

我知道我需要注入WS.Client而不是将它显式传递给Client构造函数.但是我该怎么做?

===更新===

我不想从Controller注入Client或WSClient.我的控制器在运行时创建对象和类,我希望这些对象创建客户端对象.当我明确地将WS.client对象传递给Client对象时,我得到上述警告.

===更新2 ===

我的应用程序中有一个插件架构.当控制器启动一个动作时.它不知道它将执行哪些插件集.有些插件不需要WSClient,其中一些插件也是如此.所以我不想将WSClient的注入耦合到我的控制器中.每个插件都独立决定是否要调用远程服务.当插件决定调用远程服务时,它应该能够在它想要调用的客户端中注入WSClient.

控制器操作 - >确定要执行的插件 - >执行插件--->插件1(需要调用远程api,创建一个客户端对象,根据新客户端(WS.Client)).这是注射应该发生的地方,而不是控制器.

mar*_*ira 10

好.我假设你有两节课.首先,我们将为您的Client课程:

@Singleton // this is not necessary, I put it here so you know this is possible
class Client @Inject() (ws:WSClient, baseUrl: String) {

    // Since this controller is not annotated with @Inject
    // it WILL NOT be used when binding components
    def this(ws:WSClient) = this(ws, "<url string>")

    def getResponse() = {
        // do something using ws object
    }
}
Run Code Online (Sandbox Code Playgroud)

然后你有另一个类Client,每个实例使用一个控制器:

class MyController @Inject() (client: Client) extends Controller {

    def someAction = Action {
        // do something with client object
    }

}
Run Code Online (Sandbox Code Playgroud)

这里的要点是控制器不需要创建Client实例.它由Guice自动注入.

此外,您的客户端类需要一个,baseUrl并且没有地方告诉Play需要哪个值.如果这是一个配置,那么你可以这样做:

import play.api.Configuration

class Client @Inject() (ws:WSClient, configuration: Configuration) {

    def getResponse() = {
        val baseUrl = configuration.getString("key.to.baseUrl")
        // do something using ws object and baseUrl
    }
}
Run Code Online (Sandbox Code Playgroud)

但是,如果你真的希望你的Client对象收到一个String,那么我们需要告诉Play需要注入哪个String:

package com.acme.modules

import com.google.inject.AbstractModule
import com.google.inject.name.Names

class MyModule extends AbstractModule {
  def configure() = {
    bind(classOf[String])
      .annotatedWith(Names.named("baseUrl")) // attention to the name here. It will be used below
      .toInstance("http://api.example.com/")
  }
}
Run Code Online (Sandbox Code Playgroud)

然后通过添加以下行来启用此模块application.conf:

play.modules.enabled += "com.acme.modules.MyModule"
Run Code Online (Sandbox Code Playgroud)

在那之后,我们将更改为Client具体的String预期:

import play.api.Configuration

// @Named needs to receive the same value defined at the module class.
class Client @Inject() (ws:WSClient, @Named("baseUrl") baseUrl: String) {

    def getResponse() = {
        val baseUrl = configuration.getString("key.to.baseUrl")
        // do something using ws object and baseUrl
    }
}
Run Code Online (Sandbox Code Playgroud)

问题编辑后更新:

给出你想要/需要的结构:

Controller Action --> Determine Plugins to Execute --> Execute Plugins ---> Plugin1
Run Code Online (Sandbox Code Playgroud)

您的代码也可以使用以下类的路径:

MyController -> PluginResolver -> Plugin
             -> PluginRunner ->
Run Code Online (Sandbox Code Playgroud)

然后,你可以:

控制器:

class MyController @Inject() (
    pluginResolver: PluginResolver,
    pluginRunner: PluginRunner
) extends Controller {

    def action = Action {
        val plugins = pluginsResolver.resolve(/* give a criteria to select plugins */)
        val someResultFromPluginsExecution = pluginsRunner.run(plugins)

        // map result from plugins execution to a play play.api.mvc.Result
        // return the play.api.mvc.Result
    }
}
Run Code Online (Sandbox Code Playgroud)

插件类:

import play.api.inject.Injector

class PluginResolver @Inject()(injector: Injector) {

    def resolve(/* some criteria to resolve plugins */): Seq[Plugin] = {
        val pluginsClasses = ... // find the necessary plugins based on the criteria
        pluginsClasses.map { pluginClass => injector.instanceOf(pluginClass) }
    }

}

// ExecutionContext is not really necessary, but maybe you want/need
// another thread pool to execute plugins
class PluginRunner @Inject()(implicit executionContext: ExecutionContext) {

    def run(plugins: Seq[Plugin]): Seq[PluginExecutionResult] = {
        // run the plugins
        // return the result
    }
}

trait Plugin {
    def execute(): PluginExecutionResult
}
Run Code Online (Sandbox Code Playgroud)

这里真正的魔力发生在PluginResolver.它使用a play.api.inject.Injector来创建插件实例,然后你的插件可以使用依赖注入.每个实例:

class PluginThatNeedsWSClient @Inject(wsClient: WSClient) extends Plugin {
    def execute(): PluginExecutionResult = {
        // Use wsClient to call a remote service
        // return the execution result
    }
}
Run Code Online (Sandbox Code Playgroud)

参考:

  1. Scala:依赖注入
  2. Scala:玩WS API
  3. play.api.inject.Injector