我的所有行为都有类似的结构:
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应用程序将要做的事情:在请求属性中存储请求本地状态。当我发现请求本身不可变并且向请求添加属性时,我的代码仍然保留了我无法传递动作继承链的新请求,这也是搁浅的。
的后续行动答案 0 :(得分:2)
让commonCode
返回新请求(包含所有新状态),然后将其传递给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 {
...
}
}
:
{{1}}
即使请求不可变,每个操作/过滤器都有机会将其替换为传递给链上下一个处理程序的修改版本。
答案 1 :(得分:2)
这似乎是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)
}
}