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.current
和 Play.application
已被弃用。
我有两个问题:
- 上面的示例代码是否是注入 class 的正确方法?
请求参数?或者还有其他更好的方法吗?
- 对于Play 2.5/2.6,应用程序注入器的获取方式是什么?
您已经正确地指出 Play.current
和 Play.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% 的情况下,您只对特定部分感兴趣 - 例如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()
关于 "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"
我正在使用 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.current
和 Play.application
已被弃用。
我有两个问题:
- 上面的示例代码是否是注入 class 的正确方法? 请求参数?或者还有其他更好的方法吗?
- 对于Play 2.5/2.6,应用程序注入器的获取方式是什么?
您已经正确地指出 Play.current
和 Play.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% 的情况下,您只对特定部分感兴趣 - 例如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()
关于 "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"