§Play 2.1 の変更点
§Scala 2.10 への移行
Play の全てのランタイム API がScala 2.10 に移行しました。これにより、Scala 最新版に含まれる素晴らしい機能を使ってアプリケーション開発が行えるようになりました。
同時に ビルドシステム (sbt) の Scala バージョンと、アプリケーション実行時に利用される Scala のバージョンを独立させました。これにより、 実験的または試験的なブランチの Scala を使ってより簡単に Play のビルドやテストを行えるようになりました。
§scala.concurrent.Future
への移行
Scala 2.10 で導入された目玉機能の一つに、Scala で非同期コードを扱うための scala.concurrent.Future
というライブラリがあります。Play 自身、この API をベースに動作するようになりました。また、Play の非同期 HTTP 通信やストリーミングの機能も同 API で直接扱えるようになりました。
さらに Play と Akka 、またこれからリリースされるであろう非同期なデータストアドライバとを今まで以上に容易に組み合わせられるようになりました。
同時に、実行コンテキストのモデルがよりシンプル化されるように見直しを行った結果、アプリケーションの部分部分で非同期コードを実行する ExecutionContext
を容易に選択できるようになりました。
§モジュール化
Play プロジェクトがいくつかのサブプロジェクトに分割されて、その中からユーザ自身が必要最小限のものを選択できるようになりました。
今後、アプリケーションに必要だと思われる依存プロジェクトを次のリストの中から自由に選択することができます。
jdbc
: JDBC コネクションプールとplay.api.db
APIanorm
: AnormjavaCore
: Java 向けのコア APIjavaJdbc
: Java 向けのデータベース APIjavaEbean
: Java 向けの Ebean プラグインjavaJpa
: Java 向けの JPA プラグインfilters
: Play にビルトインされたフィルタ (CSRF フィルターなど)
play
コアプロジェクトには非常に限られた依存ライブラリしか含まれていません。そして、コアはそれ単体で最小構成の非同期的、かつハイパフォーマンスな HTTP サーバとして利用できます。
§アプリケーションのより柔軟なモジュール化
Play 2.1 に追加されたサブ・ルータ合成という機能のおかげで、アプリケーションの部分部分をこれまでより柔軟にモジュール化、そして再利用できるようになりました。
具体的には、サブプロジェクトとして利用されるアプリケーションには、それ独自の名前空間で、それ専用のルータを定義できます。
例えば、 conf/my.subproject.routes
に次のようなルータを定義しておくとします。
GET / my.subproject.controllers.Application.index
このルータは、メインとなるアプリケーションへ次のように組み込むことができます。
# トップページ
GET / controllers.Application.index
# サブプロジェクトの組み込み
-> /my-subproject my.subproject.Routes
# 静的コンテンツ
GET /public/*file controllers.Assets.at(file)
この設定によって、アプリケーション実行時に /my-subproject
以下の URL へ行われたアクセスは、全て my.subproject.controllers.Application.index
というアクションの呼び出しに変換されます。
Note: メインとなるアプリケーション内の名前と衝突を起こさないように、サブプロジェクトとなるアプリケーションのコントローラは専用のサブパッケージ (上記の例では
my.subproject
がそうです) 以下に定義しましょう。同様に、サブプロジェクトとなるアプリケーションの Assets コントローラも同じ名前空間に定義したほうがよいでしょう。
この機能に関する詳細については サブプロジェクト関連のドキュメント を参照してください。
§Java API 向けの Http.Context
プロパゲーション機能
Play 2.0 では、HTTP コンテキストが非同期コールバックの過程で失われてしまっていました。これは、非同期コールバックの過程で、コードを実行スレッドが、最初に HTTP リクエストを処理し始めたスレッドとは別のスレッドへの変わってしまうからです。
次の例を考えてみましょう。
public static Result index() {
return async(
aServiceSomewhere.getData().map(new Function<String,Result>(data) {
// Ouch! You try to access the request data in an asynchronous callback
String user = session().get("user");
return ok("Here is the result " + user + ": " + data);
})
);
}
このコードは意図したとおりには動いていませんでした。その理由は、Play の非同期処理のアーキテクチャをよく知っていればわかるといえばわかるのですが、 Java 開発者にとってはあまり馴染みのない挙動でした。
この問題を解決する方法、つまり Http.Context
を複数スレッドにまたがるスタックへ伝搬していく方法が発見されたため、このコードは Play 2.1 からは意図したとおりに動くようになりました。
§Java API 向けスレッドモデルの改善
排他制御を行わずに非同期コードをミュータブルなデータ構造に対して実行すると、何らかのレースコンディションに陥いる可能性が高いと思われます。Play が高度に非同期化されたノンブロッキングなコードを宣伝していることに加えて、 Java のデータ構造が大抵はミュータブルで非スレッドセーフであることを考えると、これまではアプリケーション側で排他制御の問題を解決する必要がありました。
次の例を考えてみましょう。
public static Result index() {
final HashMap<String,Integer> result = new HashMap<String,Integer>();
aService.doSomethingAsync().map(new Function<String,String>(key) {
Integer i = result.get(key);
if(i != null) {
result.set(key, i++);
}
return key;
});
aService.doSomethingElse().map(new Function<String,String>(key) {
result.remove(key);
return null;
});
...
}
上記のコードでは、二つのコールバックが result
という HashMap を共有しています。したがって、これらコールバックが別々のスレッドで同時に実行された場合、レースコーンディションに陥る可能性が高いと思われます。そして、これが Java の HashMap 実装と相まると、結果的にアプリケーションが擬似的なデッドロック状態に陥ってしまうことが考えられます。
このような問題を回避するため、コールバックの実行に必要な排他制御がフレームワークレベルで行われるようになりました。 Play 2.1 では、同じ Http.Context
に対してふたつ以上のコールバックが同時に実行されることはもうありません。同じコンテキスト内では、全てのコールバックは順番に(ただし、同じスレッドで実行されるという保証はありません)実行されます。
§コントローラクラスのインスタンス生成処理がカスタマイズ可能になった
初期状態では、 Play は URL をコントローラのメソッドに静的にバインドします。したがって、フレームワークによってコントローラのインスタンスが生成されることはなく、単に URL に応じた適切な static メソッドが呼び出されるだけです。しかし、場合によってはコントローラの生成処理をカスタマイズしたいこともあります。ルーティングの新しいシンタックスを使うとそれが可能です。
@ から始まるルート定義は Global::getControllerInstance
メソッドによって処理されます。例えば、次のようなルートを定義します。
GET / @controllers.Application.index()
この場合、 Play によって呼び出された getControllerInstance
メソッドが controllers.Application
のインスタンス(初期状態ではデフォルトコンストラクタによって生成されます)を返します。したがって、 DI フレームワークや独自のロジックによってコントローラクラスのインスタンス生成を行いたい場合、アプリケーションの Global クラスにおいて getControllerInstance メソッドをオーバーライドするとよいでしょう。
この デモアプリケーション のように、 Spring を始めとするあらゆる DI フレームワークを Play アプリケーションに組み込むことができます。
§Scala 向けの新しい JSON API
Scala 向けの新しい JSON API によって、JSON ツリーの変換やバリデーションなどの便利な機能が実現できました。詳細については Scala JSON コンビネータのドキュメント を参照してください。
§新たなフィルタ API と CSRF プロテクション機能
Play 2.1 では強力なフィルタ API が追加され、完全にノンブロッキングな方法で HTTP リクエストや HTTP レスポンスの部分をインターセプトできるようになりました。
この API を実現するため、既存の Action[A]
という型の代替えとして、 EssentialAction
という次のようなシンプルな型が追加されました。
RequestHeader => Iteratee[Array[Byte], Result]
これを利用すると、フィルタは次のように定義できます。
EssentialAction => EssentialAction
Note: 既存の
Action[A]
という型は互換性のために残されています。
Play のディストリビューションに含まれる filters
プロジェクトには、標準的なフィルタがいくつか用意されています。例えば、 CSRF
フィルタには CSRF 対策のために自動的にトークン管理を行う機能があります。
§RequireJS
Play 2.0 では、JavaScript は Google Closure の CommonJS モジュールサポートによって処理されていました。Play 2.1 からは、代わりに RequireJS が使われるようになりました。
実利用においてこれが意味するのは、特に何か設定しない限り、 Play は stage 、 dist 、 start 時のみ JavaScript を minify するということです。開発モード時は、クライアント側で JavaScript モジュール間の依存性が解決されます。
この機能を利用する場合は、ビルドファイル内の設定に RequireJS で管理したいモジュールを追加する必要があります。
requireJs := "main.js"
この機能に関する詳細については、 RequireJS 関連のドキュメント を参照してください。
§コンテンツネゴシエーション
コンテンツネゴシエーションのサポートが改善され、コントローラにおいてレスポンスに最適な言語が Accent-Language
リクエストヘッダ値の quality 値に基いて自動的に選択されるようになりました。
加えて、同じリソースについて複数のレスポンス形式をサポートするような Web サービスを開発することが容易になりました。例えば、次のように記述すると、Accept
リクエストヘッダ値に基いて適切なレスポンスの形式を選択することができます。
val list = Action { implicit request =>
val items = Item.findAll
render {
case Accepts.Html() => Ok(views.html.list(items))
case Accepts.Json() => Ok(Json.toJson(items))
}
}
この機能についての詳細はコンテンツネゴシエーションに関するドキュメントの Scala 版 および Java 版 を参照してください。
このドキュメントの翻訳は Play チームによってメンテナンスされているものではありません。 間違いを見つけた場合、このページのソースコードを ここ で確認することができます。 ドキュメントガイドライン を読んで、お気軽にプルリクエストを送ってください。