【问题标题】:Scala Play run time injection based on request parameter基于请求参数的Scala Play运行时注入
【发布时间】:2018-06-30 20:15:34
【问题描述】:

我正在使用 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 或信用卡付款的服务类

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,应用注入器的获取方式是什么?

【问题讨论】:

    标签: scala dependency-injection playframework playframework-2.6


    【解决方案1】:

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

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

    class PaymentController @Inject()(configuration: Configuration)
                                     (implicit ec: ExecutionContext) extends Controller  {
    
    
      // your code goes here
    }
    

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

    【讨论】:

    • 很好的答案!注入注射器似乎是正确的方法。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-09-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多