§アクションの合成
この章では、汎用的なアクション機能を定義するいくつかの方法を紹介します。
§アクションに関するリマインダー
以前は、アクションは`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.Authenticated
とplay.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()
を使用して既にパースされたリクエストボディにアクセスできます。ただし、ボディパースをアクション合成の後で実行する方が理にかなうユースケースがあります。例:
- リクエスト属性を介して、リクエスト固有の情報をボディパーサーに渡したい場合。たとえば、ユーザー依存の最大ファイルアップロードサイズ、またはボディパーサーがアップロードをリダイレクトする必要があるWebサービスやオブジェクトストレージのユーザー依存の資格情報などです。
- (粒度の細かい)承認にアクション合成を使用する場合、許可チェックが失敗した場合は、リクエストボディをパースせず、リクエストを早期にキャンセルすることもできます。
もちろん、ボディパースを遅延させる場合、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
に手動で追加する必要があります。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
を持つことができますが、これは推奨されません。
次へ: コンテンツネゴシエーション
このドキュメントに誤りを見つけましたか?このページのソースコードはこちらにあります。ドキュメントガイドラインをお読みになった後、プルリクエストを自由に送ってください。ご質問やアドバイスがありましたら、コミュニティフォーラムでコミュニティとの会話を開始してください。