玩Scala依赖注入:如何使用 [英] Play Scala Dependency injection: How to use it

查看:97
本文介绍了玩Scala依赖注入:如何使用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I am trying to use Play 2.5 dependency injection. I have following class which makes a call to REST api and parses the response

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

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

}

代码的调用者如下所示

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

我收到警告.

不建议使用ws软件包中的

对象WS:将WSClient注入您的 组件

object WS in package ws is deprecated: Inject WSClient into your component

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

I understand that i need to inject WS.Client instead of passing it explicitly to the Client constructor. But how do i do that?

===更新===

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

I don't want to inject Client or WSClient from the Controller. 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.

===更新2 ===

我的应用程序中有一个插件架构.控制器开始动作时.它不知道它将执行什么插件集.有些插件不需要WSClient,而有些插件则需要.所以我不想将WSClient注入到我的控制器中.每个插件独立决定是否要调用远程服务.当插件决定调用远程服务时,它应该能够将WSClient注入其要调用的任何客户端中.

I have a plugin architecture in my application. When a a 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. So i dont want to couple the injection of WSClient into my controller. 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.

控制器操作->确定要执行的插件->执行插件---> Plugin1(需要调用远程api,创建一个客户端对象,例如说新的Client(WS.Client)).应该在此处进行注入,而不是在控制器处进行.

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)). This is where the injection should happen, not at the controller.

推荐答案

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

Ok. I will assume you have two classes. First we will have your Client class:

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

然后您还有另一个使用Client的类,每个实例都使用一个控制器:

Then you have another class that uses Client, per instance, a controller:

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

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

}

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

The main point here is that the controller did not need to create a Client instance. It was automatically injected by Guice.

此外,您的客户端类需要一个baseUrl,并且没有地方告诉Play那里需要哪个值.如果这是配置,则可以执行以下操作:

Moreover, your client class needs a baseUrl and there is no place telling Play which value is needed there. 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
    }
}

但是,如果您真的希望您的Client对象收到String,则我们需要

But, if you really want your Client object to receives a String, then we need to tell Play which String needs to be injected:

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/")
  }
}

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

And then enable this module by adding the following line to your application.conf:

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

此后,我们将更改Client,以具体说明期望的String:

After that, we will change Client to be specific about which String it is expecting:

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

然后,您可以拥有:

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

插件类:

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
}

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

The real magic here happens at the PluginResolver. It uses a play.api.inject.Injector to create plugins instances and then your plugins can use Dependency Injection. Per instance:

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

参考:

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

这篇关于玩Scala依赖注入:如何使用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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