需要一种在 Play 动作中保存状态的方法

Need a way to save state in Play action

我所有的动作都有相似的结构:

class MyController @Inject() (
  val cc: ControllerComponent)
extends AbstractController(cc) {

  def get(name: String) = Action { request =>
     try {
        commonCode(request) // Creates state
        actionSpecificCode(request)  // Uses state
     }
     catch {
       case mx: MyException =>
          mx.toResult()
       case t: Throwable =>
          // convert to BadRequest
     }
   }
}

我想将 try-catch 块与 commonBlock() 一起分解到自定义操作中,以便改进后的代码看起来像

class MyController @Inject() (
  val myAction: MyAction,
  val cc: ControllerComponent)
extends AbstractController(cc) {

  def get(name: String) = myAction { request =>
        actionSpecificCode(request)
   }
}

这很容易做到,使用自定义动作组合,例如

override def invokeBlock[A](
   request: Request[A], 
   block: (Request[A]) => Future[Result]) = { request =>
      // try block 
   }

问题在于 try 块内的 commonCode() 需要创建可在 actionSpecificCode() 内访问的状态。

最简单的方法是将状态保存在 MyAction 的私有字段中,然后可以在控制器方法中像 myAction.field 一样访问它,但这不起作用,因为相同的操作实例被多个并发请求使用。

下一个想法(也是导致我迁移到实现请求属性的 Play 2.6 的想法)是做任何 Servlet 应用程序都会做的事情:在请求属性中存储请求本地状态。这也 运行 搁浅了,当我发现请求本身是不可变的并且向请求添加属性会使我的代码保留新请求,我无法将其传递到操作继承链中。

(这是 this original question 的跟进)

commonCode return 新请求(具有所有新状态),然后将其传递给 actionSpecificCode,这在自定义操作的上下文中将是block 参数到 invokeBlock:

override def invokeBlock[A](
  request: Request[A], 
  block: (Request[A]) => Future[Result]
) = { request =>
 try {
    val newRequest = commonCode(request) // Creates state
    block(newRequest)  // Uses state
 }
 catch {
   ...
 }
}

即使请求不是可变的,每个 action/filter 都有机会用修改后的版本替换它,然后传递给链上的下一个处理程序。

这似乎是 playframework ActionRefiner 的典型用法。 doc 描述了如何使用它们来更改请求类型(并向它们添加一些状态)。我认为无需更改请求类型,您可以简单地添加请求属性(如您所建议的那样),例如使用 ActionTransformer.

这种方法的优势在于组合精炼器时,这正是您要实现的目标。一个例子是 given on the same doc page,在你的情况下这可能变成:

class MyController @Inject() (
  val myAction: MyAction,
  val cc: ControllerComponent)
extends AbstractController(cc) {

  def commonCode = new ActionRefiner[Request, Request] {
    def refine[A](input: Request[A]) = Future.successful {
      // code taken from your other question
      val user: User = input.attrs(Attrs.User)
      input.addAttr(Attrs.User, newUser)
    }
  }

  def get(name: String) = (Action andThen commonCode) { request =>
        actionSpecificCode(request)
   }
}