玩Scala依赖注入:如何使用 [英] Play Scala Dependency injection: How to use it
问题描述
我正在尝试使用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
}
}
参考:
- Scala: Dependency Injection
- Scala: Play WS API
play.api.inject.Injector
这篇关于玩Scala依赖注入:如何使用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!