文件

§動作組成

本章介紹了定義一般動作功能的幾種方法。

§關於動作的提醒

先前,我們說過動作是一個 Java 方法,它會傳回 play.mvc.Result 值。實際上,Play 內部將動作管理為函式。Java API 所提供的動作是 play.mvc.Action 的實例。Play 會為您建立一個根動作,它只會呼叫適當的動作方法。這允許進行更複雜的動作組合。

§組合動作

以下是 VerboseAction 的定義

public class VerboseAction extends play.mvc.Action.Simple {
  public CompletionStage<Result> call(Http.Request req) {
    log.info("Calling action for {}", req);
    return delegate.call(req);
  }
}

您可以使用 @With 注解,將動作方法提供的程式碼與另一個 play.mvc.Action 組合。

@With(VerboseAction.class)
public Result verboseIndex() {
  return ok("It works!");
}

在某個時間點,您需要使用 delegate.call(...) 委派給包裝的動作。

您也可以使用自訂動作註解來混合多個動作

@Security.Authenticated
@Cached(key = "index.result")
public Result authenticatedCachedIndex() {
  return ok("It works!");
}

注意:每個要求必須由您 play.mvc.Action 的不同實例提供服務。如果使用單例模式,則在多個要求場景中,要求會被不正確地路由。例如,如果您將 Spring 用作 Play 的 DI 容器,則需要確保動作 bean 是原型範圍。

注意: play.mvc.Security.Authenticatedplay.cache.Cached 注解和對應的預定義動作會隨 Play 一起提供。有關更多資訊,請參閱相關的 API 文件。

§動作註解和 WebSocket 動作方法

預設情況下,處理 WebSocket 時不會套用動作組成。可以在 WebSocket 文件 中找到如何啟用動作組成的指南,其中包含範例。

§定義自訂動作註解

您也可以使用自己的註解標記動作組成,而該註解本身必須使用 @With 註解

@With(VerboseAnnotationAction.class)
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface VerboseAnnotation {
  boolean value() default true;
}

您的 Action 定義會將註解作為組態擷取

public class VerboseAnnotationAction extends Action<VerboseAnnotation> {
  public CompletionStage<Result> call(Http.Request req) {
    if (configuration.value()) {
      log.info("Calling action for {}", req);
    }
    return delegate.call(req);
  }
}

然後您可以在動作方法中使用新的註解

@VerboseAnnotation(false)
public Result verboseAnnotationIndex() {
  return ok("It works!");
}

§註解控制器

您也可以將任何動作組成註解直接放在 Controller 類別上。在此情況下,它會套用至該控制器定義的所有動作方法。

@Security.Authenticated
public class Admin extends Controller {
...

}

注意:如果您希望放在 Controller 類別上的動作組成註解在放在動作方法上的註解之前執行,請在 application.conf 中設定 play.http.actionComposition.controllerAnnotationsFirst = true。不過,請注意,如果您在專案中使用第三方模組,它可能會依賴其註解的特定執行順序。

§從動作傳遞物件至控制器

您可以透過利用要求屬性從動作傳遞物件至控制器。

public class Attrs {
  public static final TypedKey<User> USER = TypedKey.<User>create("user");
}

public class PassArgAction extends play.mvc.Action.Simple {
  public CompletionStage<Result> call(Http.Request req) {
    return delegate.call(req.addAttr(Attrs.USER, User.findById(1234)));
  }
}

然後您可以在動作中這樣取得要求屬性

@With(PassArgAction.class)
public static Result passArgIndex(Http.Request request) {
  User user = request.attrs().get(Attrs.USER);
  return ok(Json.toJson(user));
}

§偵錯動作組成順序

若要查看動作組成鏈中的動作將以何順序執行,請將下列內容新增至 logback.xml

<logger name="play.mvc.Action" level="DEBUG" />

您現在會在記錄中看到完整的動作組成鏈,其中包含相關的註解(及其關聯的方法/控制器)

[debug] play.mvc.Action - ### Start of action order
[debug] play.mvc.Action - 1. ...
[debug] play.mvc.Action - 2. ...
[debug] play.mvc.Action - ### End of action order

§動作組合與主體解析互動

預設情況下,主體解析會在動作組合發生之前進行,表示您可以在每個動作的 call(...) 方法中透過 request.body() 存取已解析的請求主體。不過,在某些使用案例中,將主體解析延後到動作組合發生之後進行是有意義的。例如

當然,當延後主體解析時,請求主體尚未在 call(...) 方法中解析,因此 request.body() 會傳回 null

您可以在 conf/application.conf 中啟用延後主體解析

play.server.deferBodyParsing = true

請注意,與所有 play.server.* 組態金鑰一樣,當在 DEV 模式下執行時,Play 偵測不到此組態,僅在 PROD 模式下偵測得到。要在 DEV 模式中設定此組態,您必須在 build.sbt 中設定。

PlayKeys.devSettings += "play.server.deferBodyParsing" -> "true"

除了啟用延後主體解析外,您也可以使用路由修改器 deferBodyParsing 僅針對特定路由啟用。

+ deferBodyParsing
POST    /      controllers.HomeController.uploadFileToS3(request: Request)

相反的情況也是如此。如果您啟用延後主體解析,您可以使用路由修改器 dontDeferBodyParsing 停用特定路由的延後主體解析。

+ dontDeferBodyParsing
POST    /      controllers.HomeController.processUpload(request: Request)

§使用相依性注入

您可以將執行時期相依性注入編譯時期相依性注入與動作組合搭配使用。

§執行時期相依性注入

例如,如果您想要定義您自己的結果快取解決方案,請先定義註解

@With(MyOwnCachedAction.class)
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface WithCache {
  String key();
}

然後您可以定義您的動作,並注入相依性

public class MyOwnCachedAction extends Action<WithCache> {

  private final AsyncCacheApi cacheApi;

  @Inject
  public MyOwnCachedAction(AsyncCacheApi cacheApi) {
    this.cacheApi = cacheApi;
  }

  @Override
  public CompletionStage<Result> call(Http.Request req) {
    return cacheApi.getOrElseUpdate(configuration.key(), () -> delegate.call(req));
  }
}

注意:如上所述,每個要求必須由您 play.mvc.Action 的不同執行個體提供服務,而且您不可將您的動作註解為 @Singleton

§編譯時間相依性注入

在使用 編譯時間相依性注入 時,您需要手動將您的 Action 供應器加入 JavaHandlerComponents。您必須覆寫 BuiltInComponents 中的 javaHandlerComponents 方法才能執行此動作

public class MyComponents extends BuiltInComponentsFromContext
    implements NoHttpFiltersComponents, CaffeineCacheComponents {

  public MyComponents(ApplicationLoader.Context context) {
    super(context);
  }

  @Override
  public Router router() {
    return Router.empty();
  }

  @Override
  public MappedJavaHandlerComponents javaHandlerComponents() {
    return super.javaHandlerComponents()
        // Add action that does not depends on any other component
        .addAction(VerboseAction.class, VerboseAction::new)
        // Add action that depends on the cache api
        .addAction(MyOwnCachedAction.class, () -> new MyOwnCachedAction(defaultCacheApi()));
  }
}

注意:如上所述,每個要求必須由您 play.mvc.Action 的不同執行個體提供服務,這就是為什麼您會加入 java.util.function.Supplier<Action>,而不是執行個體本身。當然,您可以讓 Supplier 每一次都傳回相同的執行個體,但這並不建議這麼做。

下一步:內容協商


在此文件當中發現錯誤?此頁面的原始程式碼可以在 這裡 找到。在閱讀 文件指南 之後,請隨時提交拉取要求。有問題或建議要分享?請前往 我們的社群論壇,與社群展開對話。