You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Play框架Action中保存状态并重构重复代码块?

提取自定义Action简化Play控制器代码

Great question! Extracting repetitive boilerplate like shared setup code and exception handling into a custom Play Action is a perfect way to keep your controllers clean, focused on business logic, and maintainable. Let's walk through how to do this step by step.

第一步:创建自定义ActionBuilder

We'll build a custom ActionBuilder that wraps your commonCode execution and centralized exception handling. This way, every action using this builder automatically gets that behavior without repeating code.

基础版本(无状态传递)

If your commonCode doesn't need to pass state to the specific action logic, this simple version works:

import play.api.mvc._
import scala.concurrent.{ExecutionContext, Future}

// Make this injectable so we can use it in controllers
class MyCustomAction @Inject()(val parser: BodyParsers.Default)(implicit ec: ExecutionContext) 
  extends ActionBuilder[Request, AnyContent] {

  override def invokeBlock[A](request: Request[A], block: Request[A] => Future[Result]): Future[Result] = {
    try {
      // Run your shared commonCode first
      commonCode(request)
      // Execute the action-specific logic
      block(request)
    } catch {
      // Centralized exception handling
      case mx: MyException => Future.successful(mx.toResult())
      case t: Throwable => Future.successful(BadRequest(s"Unexpected error: ${t.getMessage}"))
    }
  }

  // Move your shared commonCode here
  private def commonCode[A](request: Request[A]): Unit = {
    // Your original setup logic (create state, initialize resources, etc.)
    // Example: val sessionState = SessionState.fromRequest(request)
  }
}

进阶版本(传递状态给业务逻辑)

If your commonCode creates a state that the action-specific logic needs to use, we'll extend ActionTransformer to wrap the request with your state:

First, define a wrapped request type to hold your state:

// Custom request type that includes your state
case class RequestWithState[A](state: MyState, request: Request[A]) extends WrappedRequest[A](request)

// Your state class (adjust based on your actual needs)
case class MyState(userId: Option[String], sessionData: Map[String, String])

Then update the custom action to create and pass the state:

class MyCustomAction @Inject()(val parser: BodyParsers.Default)(implicit ec: ExecutionContext) 
  extends ActionBuilder[RequestWithState, AnyContent] 
  with ActionTransformer[Request, RequestWithState] {

  override def transform[A](request: Request[A]): Future[RequestWithState[A]] = {
    try {
      // Create your state using commonCode logic
      val state = createStateFromRequest(request)
      Future.successful(RequestWithState(state, request))
    } catch {
      case mx: MyException => Future.failed(mx)
      case t: Throwable => Future.failed(new RuntimeException("Failed to initialize request state", t))
    }
  }

  override def invokeBlock[A](request: Request[A], block: RequestWithState[A] => Future[Result]): Future[Result] = {
    // Transform the request, run the business logic, and handle exceptions centrally
    transform(request).flatMap(block).recover {
      case mx: MyException => mx.toResult()
      case t: Throwable => BadRequest(s"Unexpected error: ${t.getMessage}")
    }
  }

  private def createStateFromRequest[A](request: Request[A]): MyState = {
    // Your original commonCode logic to build the state
    MyState(
      userId = request.session.get("userId"),
      sessionData = request.session.data
    )
  }
}

第二步:在控制器中使用自定义Action

Now your controller becomes much cleaner—you only need to write the action-specific logic, and the custom action handles the rest:

class MyController @Inject()(myCustomAction: MyCustomAction, cc: ControllerComponent) 
  extends AbstractController(cc) {

  // Use your custom action instead of the default Play Action
  def get(name: String) = myCustomAction { request =>
    // If using the stateful version, access the state via request.state
    val response = actionSpecificCode(request)
    Ok(response)
  }

  // Your original action-specific logic, now focused solely on business needs
  private def actionSpecificCode[A](request: Request[A]): String = {
    // Example business logic
    s"Hello, $name! Your session ID is ${request.session.id}"
  }
}

为什么这很有用?

  • 减少重复代码: No more copying try/catch blocks and common setup across every action.
  • 统一异常处理: All exceptions follow the same handling logic, making it easier to update later.
  • 关注点分离: Controllers focus on business logic, while the custom action handles cross-cutting concerns (setup, error handling).

内容的提问来源于stack exchange,提问作者Igor Urisman

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 09:34:16