简体   繁体   English

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

[英]Play Scala Dependency injection: How to use it

I am trying to use Play 2.5 dependency injection. 我正在尝试使用Play 2.5依赖注入。 I have following class which makes a call to REST api and parses the response 我有以下类调用REST api并解析响应

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

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

}

The caller of the code looks like below 代码的调用者如下所示

var client = new Client(WS.client)
client.getResponse()

I am getting following warning. 我收到了警告。

object WS in package ws is deprecated: Inject WSClient into your component 不推荐使用包ws中的对象WS:将WSClient注入到组件中

I understand that i need to inject WS.Client instead of passing it explicitly to the Client constructor. 我知道我需要注入WS.Client而不是将它显式传递给Client构造函数。 But how do i do that? 但是我该怎么做?

=== Update === ===更新===

I don't want to inject Client or WSClient from the Controller. 我不想从Controller注入Client或WSClient。 My controller creates objects and classes at run time and i want those objects to create Client Object. 我的控制器在运行时创建对象和类,我希望这些对象创建客户端对象。 When i explicitly pass WS.client object to the Client object i get the above stated warning. 当我明确地将WS.client对象传递给Client对象时,我得到上述警告。

=== Update 2 === ===更新2 ===

I have a plugin architecture in my application. 我的应用程序中有一个插件架构。 When aa controller starts an action. 当控制器启动一个动作时。 It does not know what set of plugins it is going to execute. 它不知道它将执行哪些插件集。 Some plugins would not need a WSClient and some of them would. 有些插件不需要WSClient,其中一些插件也是如此。 So i dont want to couple the injection of WSClient into my controller. 所以我不想将WSClient的注入耦合到我的控制器中。 Each plugin independently decides if it wants to call a remote service. 每个插件独立决定是否要调用远程服务。 When a plugin decides to call the remote service, it should be able to inject WSClient in what ever client it wants to invoke. 当插件决定调用远程服务时,它应该能够在它想要调用的客户端中注入WSClient。

Controller Action --> Determine Plugins to Execute --> Execute Plugins ---> Plugin1 (needs to call a remote api, create a client object, per say new Client(WS.Client)). 控制器操作 - >确定要执行的插件 - >执行插件--->插件1(需要调用远程api,创建一个客户端对象,根据新客户端(WS.Client))。 This is where the injection should happen, not at the controller. 这是注射应该发生的地方,而不是控制器。

Ok. 好。 I will assume you have two classes. 我假设你有两节课。 First we will have your Client class: 首先,我们将拥有您的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
    }
}

Then you have another class that uses Client , per instance, a controller: 然后你有另一个使用Client类,每个实例,一个控制器:

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

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

}

The main point here is that the controller did not need to create a Client instance. 这里的要点是控制器不需要创建Client实例。 It was automatically injected by Guice. 它由Guice自动注入。

Moreover, your client class needs a baseUrl and there is no place telling Play which value is needed there. 此外,您的客户端类需要一个baseUrl并且没有地方告诉Play那里需要哪个值。 If this is a configuration, than you can do something like this: 如果这是一个配置,那么你可以这样做:

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

But, if you really want your Client object to receives a String , then we need to tell Play which String needs to be injected : 但是,如果你真的希望你的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/")
  }
}

And then enable this module by adding the following line to your application.conf : 然后通过将以下行添加到application.conf来启用此模块:

play.modules.enabled += "com.acme.modules.MyModule"

After that, we will change Client to be specific about which String it is expecting: 之后,我们将更改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
    }
}

Update after question edit: 问题编辑后更新:

Give the structure you want/need: 给出你想要/需要的结构:

Controller Action --> Determine Plugins to Execute --> Execute Plugins ---> Plugin1

Your code can also follow that path with classes like this: 您的代码也可以使用以下类的路径:

MyController -> PluginResolver -> Plugin
             -> PluginRunner ->

And, then, you can have: 然后,你可以:

Controller: 控制器:

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

Plugin classes: 插件类:

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
}

The real magic here happens at the PluginResolver . 这里真正的魔力发生在PluginResolver It uses a play.api.inject.Injector to create plugins instances and then your plugins can use Dependency Injection. 它使用play.api.inject.Injector来创建插件实例,然后你的插件可以使用依赖注入。 Per instance: 每个实例:

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

Reference: 参考:

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

上周我看到了这篇很棒的帖子: http//www.schibsted.pl/2016/04/dependency-injection-play-framework-scala/

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM