Scala根据请求参数播放运行时注入

时间:2018-01-22 08:38:24

标签: scala dependency-injection playframework playframework-2.6

我正在使用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,获取应用程序注入器的方法是什么?

1 个答案:

答案 0 :(得分:1)

您已正确说明Unhandled rejection Error: Error invoking Compose. Check the inner error for details. ------------------------------------------------ Inner Error: Message: Constructor Parameter with index 0 cannot be null or undefined. Are you trying to inject/register something that doesn't exist with DI? Inner Error Stack: Error: Constructor Parameter with index 0 cannot be null or undefined. Are you trying to inject/register something that doesn't exist with DI? at Object.invokeWithDynamicDependencies (http://localhost:9000/common.595063c809425d78a6ec.bundle.js:35219:13) at InvocationHandler.invoke (http://localhost:9000/common.595063c809425d78a6ec.bundle.js:35204:166) at Container.invoke (http://localhost:9000/common.595063c809425d78a6ec.bundle.js:35475:23) at ProviderResolver.get (http://localhost:9000/common.595063c809425d78a6ec.bundle.js:31046:72) at Container.get (http://localhost:9000/common.595063c809425d78a6ec.bundle.js:35414:21) at Container.elementContainerGet [as get] (http://localhost:9000/common.595063c809425d78a6ec.bundle.js:31101:15) at HtmlBehaviorResource.create (http://localhost:9000/common.595063c809425d78a6ec.bundle.js:33088:56) at applyInstructions (http://localhost:9000/common.595063c809425d78a6ec.bundle.js:31203:31) at ViewFactory.create (http://localhost:9000/common.595063c809425d78a6ec.bundle.js:31424:7) at BoundViewFactory.create (http://localhost:9000/common.595063c809425d78a6ec.bundle.js:31304:33) at If._show (http://localhost:9000/common.595063c809425d78a6ec.bundle.js:36666:36) at If.bind (http://localhost:9000/common.595063c809425d78a6ec.bundle.js:73912:12) at Controller.bind (http://localhost:9000/common.595063c809425d78a6ec.bundle.js:32473:22) at View.bind (http://localhost:9000/common.595063c809425d78a6ec.bundle.js:30462:22) at Controller.bind (http://localhost:9000/common.595063c809425d78a6ec.bundle.js:32465:17) at Controller.automate (http://localhost:9000/common.595063c809425d78a6ec.bundle.js:32410:10) End Inner Error Stack ------------------------------------------------ at new AggregateError (http://localhost:9000/common.595063c809425d78a6ec.bundle.js:8478:11) at Container.invoke (http://localhost:9000/common.595063c809425d78a6ec.bundle.js:35477:13) at ProviderResolver.get (http://localhost:9000/common.595063c809425d78a6ec.bundle.js:31046:72) at Container.get (http://localhost:9000/common.595063c809425d78a6ec.bundle.js:35414:21) at Container.elementContainerGet [as get] (http://localhost:9000/common.595063c809425d78a6ec.bundle.js:31101:15) at HtmlBehaviorResource.create (http://localhost:9000/common.595063c809425d78a6ec.bundle.js:33088:56) at applyInstructions (http://localhost:9000/common.595063c809425d78a6ec.bundle.js:31203:31) at ViewFactory.create (http://localhost:9000/common.595063c809425d78a6ec.bundle.js:31424:7) at BoundViewFactory.create (http://localhost:9000/common.595063c809425d78a6ec.bundle.js:31304:33) at If._show (http://localhost:9000/common.595063c809425d78a6ec.bundle.js:36666:36) at If.bind (http://localhost:9000/common.595063c809425d78a6ec.bundle.js:73912:12) at Controller.bind (http://localhost:9000/common.595063c809425d78a6ec.bundle.js:32473:22) at View.bind (http://localhost:9000/common.595063c809425d78a6ec.bundle.js:30462:22) at Controller.bind (http://localhost:9000/common.595063c809425d78a6ec.bundle.js:32465:17) at Controller.automate (http://localhost:9000/common.595063c809425d78a6ec.bundle.js:32410:10) at http://localhost:9000/common.595063c809425d78a6ec.bundle.js:33554:18 Play.current已被弃用,从2.5开始,使用它们的方式确实是通过注入它们。

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

Play.application

现在是棘手的部分。您可能只是注入class PaymentController @Inject()(configuration: Configuration) (implicit ec: ExecutionContext) extends Controller { // your code goes here } ,但这并不完全正确,因为您将遇到循环依赖。这是正常的,因为您希望在实际存在的同时注入整个应用程序。这有一个 hack ,它是通过注入application: play.Application。我称之为 hack ,因为通常你不需要/想要注入整个应用程序。在99%的情况下,您只对特定部分感兴趣 - 例如Provider[Application]Configuration

这就是解决方案。您可以注入Environment

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

具有共同特征(就像你这样做)有助于这种情况,你可以有多个实现,甚至是模拟的测试。如果模块位于根包中,则会自动注册该模块。否则你应该告诉Play它的位置:

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