/ / Play Scala Dependency Injection: Como usá-lo - scala, injeção de dependência, playframework-2.0

Jogar Scala Dependency injection: Como usar - scala, dependency-injection, playframework-2.0

Estou tentando usar a injeção de dependência do Play 2.5. Eu tenho a seguinte classe que faz uma chamada para API REST e analisa a resposta

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

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

}

O chamador do código é semelhante a abaixo

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

Estou recebendo o seguinte aviso.

O objeto WS no pacote ws foi descontinuado: injete o WSClient no seu componente

Entendo que preciso injetar o WS.Client em vez de passá-lo explicitamente ao construtor Client. Mas como eu faço isso?

=== Atualização ===

Não quero injetar Client ou WSClient deo controlador. Meu controlador cria objetos e classes em tempo de execução e eu quero que esses objetos criem Objeto Cliente. Quando passo explicitamente o objeto WS.client para o objeto Client, recebo o aviso acima.

=== Atualização 2 ===

Eu tenho uma arquitetura de plugins no meu aplicativo. Quando um controlador inicia uma ação. Ele não sabe qual conjunto de plugins será executado. Alguns plug-ins não precisariam de um WSClient e outros. Então, eu não quero acoplar a injeção do WSClient no meu controlador. Cada plug-in decide independentemente se deseja chamar um serviço remoto. Quando um plug-in decide chamar o serviço remoto, ele deve ser capaz de injetar o WSClient em qualquer cliente que ele queira chamar.

Ação do controlador -> Determinar plugins paraExecute -> Execute Plugins ---> Plugin1 (precisa chamar uma API remota, criar um objeto de cliente, por exemplo, novo cliente (WS.Client)). É aqui que a injeção deve ocorrer, não no controlador.

Respostas:

8 para resposta № 1

Está bem. Eu vou assumir que você tem duas classes. Primeiro teremos o seu Client classe:

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

Então você tem outra classe que usa Client, por exemplo, um controlador:

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

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

}

O ponto principal aqui é que o controlador não precisou criar um Client instância. Foi injetado automaticamente pelo Guice.

Além disso, sua classe de cliente precisa de um baseUrl e não há lugar para dizer ao Play qual valor é necessário lá. Se essa é uma configuração, você pode fazer algo assim:

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

Mas se você realmente quer seu Client objeto para recebe um String, então precisamos diga ao Play qual String precisa ser injetada:

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

E ative este módulo adicionando a seguinte linha ao seu application.conf:

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

Depois disso, vamos mudar Client ser específico sobre quais String está esperando:

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

Atualizar após a edição da pergunta:

Dê a estrutura que você deseja / precisa:

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

Seu código também pode seguir esse caminho com classes como esta:

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

E, então, você pode ter:

Controlador:

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

Classes de plug-in:

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
}

A verdadeira magia aqui acontece no PluginResolver. Ele usa um play.api.inject.Injector para criar instâncias de plug-ins e seus plug-ins podem usar a Injeção de Dependência. Por exemplo:

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

Referência:

  1. Scala: Injeção de Dependência
  2. Scala: API do Play WS
  3. play.api.inject.Injector

3 para resposta № 2

Eu vi esse post incrível na semana passada: http://www.schibsted.pl/2016/04/dependency-injection-play-framework-scala/