2

我正在使用 Scala Play 2.6 并尝试使用依赖注入来根据请求参数实例化服务类。如下示例代码,控制器类从查询字符串中获取付款方式

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

和服务类来处理 Paypal 或 CreditCard 付款

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. 上面的示例代码是基于请求参数注入类的正确方法吗?还是有其他更好的方法可以做到这一点?
  2. 对于 Play 2.5/2.6,应用注入器的获取方式是什么?
4

1 回答 1

1

您已经正确地说明了这一点Play.current并且Play.application已被弃用,从 2.5 开始,使用它们的方式确实是通过注入它们。

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

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


  // your code goes here
}

现在是棘手的部分。您可能会认为仅注入是可能的,application: play.Application但这并不完全正确,因为您将遇到循环依赖。这是正常的,因为您想在实际处于其中的同时注入整个应用程序。为此有一个技巧,它是通过注入Provider[Application]. 我称之为hack,因为通常你不需要/不想注入整个应用程序。在 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()

关于使用它的“正确方法”的遗言。我实际上发现你的方法没问题,不一定会改变它。在这个方向上只有一个想法是你创建一个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"

于 2018-01-22T11:32:48.933 回答