ドキュメント

§Play 2.7 の新機能

このページでは、Play 2.7 の新機能について説明します。Play 2.7 へのマイグレーションに必要な変更点については、Play 2.7 マイグレーションガイドを参照してください。

§Scala 2.13 のサポート

Play 2.7 は、Scala 2.13、2.12、および 2.11 に対してクロスビルドされる最初の Play リリースです。これを達成するために、多くの依存関係が更新されました。

build.sbtscalaVersion設定することで、使用する Scala のバージョンを選択できます。

Scala 2.12 の場合

scalaVersion := "2.12.19"

Scala 2.11 の場合

scalaVersion := "2.11.12"

Scala 2.13 の場合

scalaVersion := "2.13.13"

§Akka の Coordinated Shutdown によるライフサイクル管理

Play 2.6 では、Akka のCoordinated Shutdownの使用が導入されましたが、コアフレームワーク全体で使用されていなかったり、エンドユーザーに公開されていませんでした。Coordinated Shutdown は、アクタシステムのシャットダウン中に順序付けられた方法で実行できるタスクのレジストリを持つ Akka 拡張機能です。

Coordinated Shutdown は内部的に Play 2.7 のライフサイクルを処理し、CoordinatedShutdownのインスタンスをインジェクションで利用できます。Coordinated Shutdown は、Play のアプリケーションライフサイクルのような単一フェーズを持つのではなく、有向非巡回グラフ (DAG)として整理された、より細かい粒度のフェーズを提供します。たとえば、サーバーバインディングの前後、または現在リクエストがすべて完了した後に実行するタスクを追加できます。Akka Clusterとの統合も向上します。

Play マニュアルのCoordinated Shutdown に関する新しいセクションで詳細を確認するか、Akka のCoordinated Shutdown に関するリファレンスドキュメントを参照してください。

§Guice が 4.2.2 にアップグレードされました

Play で使用されるデフォルトの依存性注入フレームワークである Guice が 4.1.0 から 4.2.2 にアップグレードされました。4.2.24.2.1、および4.2.0のリリースノートを参照してください。この新しい Guice バージョンには破壊的変更が導入されているため、Play 2.7 マイグレーションガイドを確認してください。

§Java フォームによる multipart/form-data ファイルアップロードのバインド

Play 2.6 までは、multipart/form-data エンコードされたフォームを介してアップロードされたファイルを取得する唯一の方法は、アクションメソッド内で呼び出すrequest.body().asMultipartFormData().getFile(...)ことでした。

Play 2.7 以降、このようなアップロードされたファイルは Java フォームにもバインドされるようになりました。カスタムの multipart ファイルパートボディパーサーを使用していない場合は、フォームにTemporaryFile型のFilePartを追加するだけで済みます。

import play.libs.Files.TemporaryFile;
import play.mvc.Http.MultipartFormData.FilePart;

public class MyForm {

  private FilePart<TemporaryFile> myFile;

  public void setMyFile(final FilePart<TemporaryFile> myFile) {
    this.myFile = myFile;
  }

  public FilePart<TemporaryFile> getMyFile() {
    return this.myFile;
  }
}

以前と同様に、コントローラーにインジェクションされたFormFactoryを使用してフォームを作成します。

Form<MyForm> form = formFactory.form(MyForm.class).bindFromRequest(req);

バインディングが成功した場合(フォームの検証がパスした場合)、ファイルにアクセスできます。

MyForm myform = form.get();
myform.getMyFile();

アップロードされたファイルの処理に役立つメソッドも追加されました。

// Get all files of the form
form.files();

// Access the file of a Field instance
Field myFile = form.field("myFile");
field.file();

// To access a file of a DynamicForm instance
dynamicForm.file("myFile");

注記:カスタムの multipart ファイルパートボディパーサーを使用している場合は、TemporaryFileをボディパーサーで使用している型に置き換えるだけです。

§Play Java で提供される制約アノテーションは @Repeatable になりました

play.data.validation.Constraintsによって定義されているすべての制約アノテーションは、現在@Repeatableです。この変更により、たとえば、同じ要素で同じアノテーションを複数回使用できますが、毎回異なるgroupsを使用できます。ただし、一部の制約については、@ValidateWithのように、それ自体を繰り返す方が理にかなっています。

@Validate(groups={GroupA.class})
@Validate(groups={GroupB.class})
public class MyForm {

    @ValidateWith(MyValidator.class)
    @ValidateWith(MyOtherValidator.class)
    @Pattern(value="[a-k]", message="Should be a - k")
    @Pattern(value="[c-v]", message="Should be c - v")
    @MinLength(value=4, groups={GroupA.class})
    @MinLength(value=7, groups={GroupB.class})
    private String name;

    //...
}

もちろん、独自のカスタム制約を@Repeatableにすることもでき、Play は自動的にそれを認識します。

§Java の validate メソッドと isValid メソッドのペイロード

高度な検証機能を使用する場合、検証プロセスに必要な情報を時々含むValidationPayloadオブジェクトを、Java のvalidateメソッドまたはisValidメソッドに渡すことができます。
このようなペイロードをvalidateメソッドに渡すには、フォームに@ValidateWithPayload@Validateの代わりに)アノテーションを付け、ValidatableWithPayloadValidatableの代わりに)を実装します。

import java.util.Map;
import com.typesafe.config.Config;
import play.data.validation.Constraints.ValidatableWithPayload;
import play.data.validation.Constraints.ValidateWithPayload;
import play.data.validation.Constraints.ValidationPayload;
import play.i18n.Lang;
import play.i18n.Messages;
import play.libs.typedmap.TypedMap;

@ValidateWithPayload
public class SomeForm implements ValidatableWithPayload<String> {

    @Override
    public String validate(ValidationPayload payload) {
        Lang lang = payload.getLang();
        Messages messages = payload.getMessages();
        Map<String, Object> ctxArgs = payload.getArgs();
        TypedMap attrs = payload.getAttrs();
        Config config = payload.getConfig();
        // ...
    }

}

独自のカスタムクラスレベル制約を作成した場合は、PlayConstraintValidatorWithPayloadPlayConstraintValidatorの代わりに)を実装することで、isValidメソッドにペイロードを渡すこともできます。

import javax.validation.ConstraintValidatorContext;

import play.data.validation.Constraints.PlayConstraintValidatorWithPayload;
import play.data.validation.Constraints.ValidationPayload;
// ...

public class ValidateWithDBValidator implements PlayConstraintValidatorWithPayload<SomeValidatorAnnotation, SomeValidatableInterface<?>> {

    //...

    @Override
    public boolean isValid(final SomeValidatableInterface<?> value, final ValidationPayload payload, final ConstraintValidatorContext constraintValidatorContext) {
        // You can now pass the payload on to your custom validate(...) method:
        return reportValidationStatus(value.validate(...., payload), constraintValidatorContext);
    }

}

注記:ValidationPayloadConstraintValidatorContextを混同しないでください。前者は Play によって提供されるクラスであり、Play でフォームを扱う際の日常業務で使用します。後者はBean Validation 仕様によって定義されたクラスであり、Play の内部でのみ使用されます。ただし、上記の最後の例のように、独自のカスタムクラスレベル制約を作成する場合を除きます。この場合、reportValidationStatusメソッドに渡すだけで済みます。

§Caffeine のサポート

Play は、Caffeineに基づいた CacheApi 実装を提供するようになりました。Caffeine は Play ユーザー向けの推奨されるキャッシュ実装です。

EhCache から Caffeine にマイグレーションするには、依存関係からehcacheを削除してcaffeineに置き換える必要があります。デフォルトの設定をカスタマイズするには、ドキュメントの説明に従って、application.conf の設定も更新する必要があります。

Java キャッシュ APIScala キャッシュ APIのドキュメントを参照して、Play を使用したキャッシュの設定の詳細を確認してください。

§新しいコンテンツセキュリティポリシーフィルター

埋め込まれたコンテンツに対して CSP nonce とハッシュをサポートする新しいコンテンツセキュリティポリシーフィルターが利用可能になりました。

CSP をデフォルトで有効にし、default-src 'self'に設定する以前の設定は厳しすぎ、プラグインと干渉していました。CSP フィルターはデフォルトで有効になっておらず、SecurityHeaders フィルターcontentSecurityPolicyは非推奨になり、デフォルトでnullに設定されています。

CSP フィルターは、デフォルトで Google のStrict CSP ポリシー(nonce ベースのポリシー)を使用します。これは出発点として推奨され、含まれている CSPReport ボディパーサーとアクションを使用して、本番環境で CSP を適用する前に CSP 違反をログに記録します。

§HikariCP のアップグレード

HikariCPが最新のメジャーバージョンに更新されました。変更点については、マイグレーションガイドを参照してください。

§Java 用 Play WS curl フィルター

Play WS を使用すると、行われたリクエストを検査または強化するためのplay.libs.ws.WSRequestFilterを作成できます。Play は「curl としてログ出力する」フィルターを提供していますが、これは Java 開発者には不足していました。次のような記述が可能になりました。

ws.url("https://play.dokyumento.jp")
  .setRequestFilter(new AhcCurlRequestLogger())
  .addHeader("My-Header", "Header value")
  .get();

次に、次のログが出力されます。

curl \
  --verbose \
  --request GET \
  --header 'My-Header: Header Value' \
  'https://play.dokyumento.jp'

これは、リクエストを独立して再現し、curlパラメーターを変更して動作を確認する場合に特に役立ちます。

§Gzip フィルターが圧縮レベルの設定をサポートするようになりました

gzip エンコーディングを使用する場合、使用する圧縮レベルを設定できるようになりました。たとえば、play.filters.gzip.compressionLevelを使用して設定できます。

play.filters.gzip.compressionLevel = 9

GzipEncodingで詳細を確認してください。

§API の追加

Play 2.7.0 で行った関連する API の追加の一部を次に示します。

§Result HttpEntity ストリームメソッド

以前のバージョンの Play には、HTTP チャンク転送エンコーディングを使用して結果をストリームするための便利なメソッドがありました。

Java
public Result chunked() {
  Source<ByteString, NotUsed> body = Source.from(Arrays.asList(ByteString.fromString("first"), ByteString.fromString("second")));
  return ok().chunked(body);
}
Scala
def chunked = Action {
  val body = Source(List("first", "second", "..."))
  Ok.chunked(body)
}

Play 2.6 では、HTTP チャンクエンコーディングを使用せずに同じようにストリームされた Result を返す便利なメソッドがありませんでした。代わりに次のように記述する必要がありました。

Java
public Result streamed() {
  Source<ByteString, NotUsed> body = Source.from(Arrays.asList(ByteString.fromString("first"), ByteString.fromString("second")));
  return ok().sendEntity(new HttpEntity.Streamed(body, Optional.empty(), Optional.empty()));
}
Scala
def streamed = Action {
  val body = Source(List("first", "second", "...")).map(s => ByteString.fromString(s))
  Ok.sendEntity(HttpEntity.Streamed(body, None, None))
}

Play 2.7 では、chunkedと同様に動作する新しいstreamedメソッドが結果に追加されることで、この問題が解決されました。

Java
public Result streamed() {
  Source<ByteString, NotUsed> body = Source.from(Arrays.asList(ByteString.fromString("first"), ByteString.fromString("second")));
  return ok().streamed(body, Optional.empty(), Optional.empty());
}
Scala
def streamed = Action {
  val body = Source(List("first", "second", "...")).map(s => ByteString.fromString(s))
  Ok.streamed(body, contentLength = None)
}

§新しい HTTP エラーハンドラー

Play 2.7 は、play.api.http.HttpErrorHandlerに 2 つの新しい実装をもたらします。1 つ目はJsonHttpErrorHandlerで、JSON 形式でフォーマットされたエラーを返し、JSON ペイロードを受け入れて返す REST API を開発する場合の優れた代替手段です。2 つ目はHtmlOrJsonHttpErrorHandlerで、クライアントのAcceptヘッダーで指定された設定に基づいて HTML または JSON エラーを返します。これは、現代の Web アプリで一般的であるように、アプリケーションが HTML と JSON の両方を使用する場合に最適なオプションです。

詳細は、JavaまたはScalaのドキュメントをご覧ください。

§Router.withPrefixのより簡潔な構文

Play 2.7では、play.api.routing.Router.withPrefixを使用するためのシンタックスシュガーを導入しました。従来の書き方

val router = apiRouter.withPrefix("/api")

は、以下のように書くことができます。

val router = "/api" /: apiRouter

さらに、複数のパスセグメントを組み合わせることも可能です。

val router = "/api" /: "v1" /: apiRouter

§ルーターの連結

Play 2.7では、Routerをプログラム的に合成するための新しいメソッドorElseを導入しました。
ルーターは以下のように合成できます。

Java
Router router = oneRouter.orElse(anotherRouter)
Scala
val router = oneRouter.orElse(anotherRouter)

§データベーストランザクションの分離レベル

play.api.db.Database.withTransaction API(Javaユーザーの場合はplay.db.Database)を使用する際に、分離レベルを選択できるようになりました。例えば

Java
public void someDatabaseOperation() {
  database.withTransaction(TransactionIsolationLevel.ReadUncommitted, connection -> {
    ResultSet resultSet = connection.prepareStatement("select * from users where id = 10").executeQuery();
    // consume the resultSet and return some value
  });
}
Scala
def someDatabaseOperation(): Unit = {
  database.withTransaction(TransactionIsolationLevel.ReadUncommitted) { connection =>
    val resultSet: ResultSet = connection.prepareStatement("select * from users where id = 10").executeQuery();
    // consume the resultSet and return some value
  }
}

利用可能なトランザクション分離レベルは、java.sql.Connectionで定義されているものと同様です。

次へ: 移行ガイド


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