Scala Play 运行 基于请求参数的时间注入

Scala Play run time injection based on request parameter

我正在使用 Scala Play 2.6 并尝试使用依赖注入根据请求参数实例化服务 class。如下面的示例代码,控制器 class 从查询字符串

中获取付款方式
package controllers

import com.google.inject.Inject
import play.api.mvc._
import scala.concurrent.ExecutionContext

class PaymentController @Inject()()
                                 (implicit ec: ExecutionContext)
  extends InjectedController  {

  def doPayment() = Action.async { implicit request =>
    request.getQueryString("payment-method").getOrElse("") match {
      case "paypal" => // Inject a PaypalPaymentService
        val paymentService = Play.current.injector.instanceOf[PaypalPaymentService]
        paymentService.processPayment()

      case "creditcard" => // Inject a CreditCardPaymentService
        val paymentService = Play.current.injector.instanceOf[CreditCardPaymentService]
        paymentService.processPayment()

      case _ => // Return error
    }
  }
}

和服务 class 处理 Paypal 或信用卡付款

package services

import scala.concurrent.Future

trait PaymentService {    
  def processPayment(): Future[Boolean]    
}

package services

import com.google.inject.Inject
import scala.concurrent.{ExecutionContext, Future}
import play.api.libs.ws.WSClient

class PaypalPaymentService @Inject()(ws: WSClient)
                                     (implicit ec: ExecutionContext)
  extends PaymentService {

  def processPayment(): Future[Boolean] = {
    //Process paypal payment
  }
}

class CreditCardPaymentService @Inject()(ws: WSClient)
                                    (implicit ec: ExecutionContext)
  extends PaymentService {

  def processPayment(): Future[Boolean] = {
    //Process credit card payment

  }
}

从 Play 2.5 开始,Play.currentPlay.application 已被弃用。

我有两个问题:

  1. 上面的示例代码是否是注入 class 的正确方法? 请求参数?或者还有其他更好的方法吗?
  2. 对于Play 2.5/2.6,应用程序注入器的获取方式是什么?

您已经正确地指出 Play.currentPlay.application 已被弃用,从 2.5 开始,使用它们的方法确实是注入它们。

我会更改您的控制器定义,以便您使用 DI 来包含所需的组件。类似于:

class PaymentController @Inject()(configuration: Configuration)
                                 (implicit ec: ExecutionContext) extends Controller  {


  // your code goes here
}

棘手的部分来了。您可能会认为只注入 application: play.Application 是可能的,但这并不完全正确,因为您将要 运行 进入循环依赖。这是正常的,因为您想在 whole 应用程序实际处于其中时注入它。为此有一个 hack,它是通过注入 Provider[Application]。我称之为 hack 因为通常你不会 need/want 注入整个应用程序。在 99% 的情况下,您只对特定部分感兴趣 - 例如ConfigurationEnvironment

解决方案来了。你可以只注入你的 Injector

class PaymentController @Inject()(injector: Injector)
                                 (implicit ec: ExecutionContext) extends Controller  {

  // your code goes here
}

从这里开始,游戏就变得简单了。只需使用 Injector 即可获得所需的服务。像这样:

case "paypal" => // Inject a PaypalPaymentService
      val paymentService = injector.instanceOf(classOf[PaypalPaymentService])
      paymentService.processPayment()

关于 "correct way" 使用它的最后一句话。我实际上发现您的方法还可以,不一定会改变它。在这个方向上只有一个想法是你创建一个 Module 这样的:

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

class PaymentModule extends AbstractModule {
  def configure() = {

    bind(classOf[PaymentService])
      .annotatedWith(Names.named("paypal"))
      .to(classOf[PaypalPaymentService])

    bind(classOf[PaymentService])
      .annotatedWith(Names.named("creditcard"))
      .to(classOf[CreditCardPaymentService])
  }
}

在这种情况下,拥有一个共同特征(正如您所做的那样)会有所帮助,并且您可以拥有多个实现,甚至可以为您的测试模拟一个。如果模块位于根包中,它将被自动注册。否则你应该告诉 Play 它的位置:

play.modules.enabled += "modules.PaymentModule"