ドキュメント

§アクションの合成

この章では、汎用的なアクション機能を定義するいくつかの方法を紹介します。

§アクションに関するリマインダー

以前は、アクションは`play.mvc.Result`値を返すJavaメソッドであると述べました。実際、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.confplay.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.*設定キーと同様に、この設定は開発モードではPlayによって取得されず、本番モードでのみ取得されることに注意してください。開発モードでこの設定を行うには、build.sbtで設定する必要があります。

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

グローバルに遅延ボディパースを有効にする代わりに、routes修飾子deferBodyParsingを使用して、特定のルートに対してのみ有効にすることができます。

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

逆も同様です。グローバルに遅延ボディパースを有効にしている場合、routes修飾子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に手動で追加する必要があります。BuiltInComponentsjavaHandlerComponentsメソッドをオーバーライドすることで実行します。

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を持つことができますが、これは推奨されません。

次へ: コンテンツネゴシエーション


このドキュメントに誤りを見つけましたか?このページのソースコードはこちらにあります。ドキュメントガイドラインをお読みになった後、プルリクエストを自由に送ってください。ご質問やアドバイスがありましたら、コミュニティフォーラムでコミュニティとの会話を開始してください。