§Play 2.6 の新機能
このページでは、Play 2.6 の新機能について説明します。Play 2.6 への移行時に必要な変更について学ぶには、Play 2.6 移行ガイドを参照してください。
§Scala 2.12 のサポート
Play 2.6 は、Scala 2.12 と 2.11 の両方に対してクロスビルドされた最初の Play リリースです。両方のバージョンをサポートできるように、多くの依存関係を更新しました。
build.sbt
でscalaVersion
設定を行うことで、使用する Scala のバージョンを選択できます。
Scala 2.12 の場合
scalaVersion := "2.12.19"
Scala 2.11 の場合
scalaVersion := "2.11.12"
§PlayService sbt プラグイン (実験的)
Play 2.6.8 以降、Play は PlayService
プラグインも提供しています。これは、マイクロサービスを対象とした、はるかに最小限の Play 構成です。従来の Play レイアウトではなく標準の Maven レイアウトを使用し、Twirl テンプレートや sbt-web 機能は含まれていません。例:
lazy val root = (project in file("."))
.enablePlugins(PlayService)
.enablePlugins(RoutesCompiler) // place routes in src/main/resources, or remove if using SIRD/RoutingDsl
.settings(
scalaVersion := "2.12.19",
libraryDependencies ++= Seq(
guice, // remove if not using Play's Guice loader
akkaHttpServer, // or use nettyServer for Netty
logback // add Play logging support
)
)
注記: このプラグインは実験的であると見なされています。つまり、API が変更される可能性があります。Play 2.7.0 では安定すると予想しています。
§「グローバル状態フリー」アプリケーション
最も大きな内部変更点は、Play がグローバル状態に依存しなくなったことです。Play 2.6 では、play.api.Play.current
/ play.Play.application()
を使用してグローバルアプリケーションにアクセスできますが、非推奨となっています。これは、グローバル状態がまったくない Play 3.0 の準備段階です。
次の構成値を設定することで、グローバルアプリケーションへのアクセスを完全に無効にすることができます。
play.allowGlobalApplication=false
上記のセッティングは、`Play.current` の呼び出しごとに例外が発生する原因となります。
§Akka HTTP サーバーバックエンド
Play は現在、Akka-HTTP サーバーエンジンをデフォルトのバックエンドとして使用しています。Akka-HTTP との Play の統合に関する詳細は、Akka HTTP サーバーページにあります。Akka HTTP の設定に関する追加ページもあります。
Netty バックエンドはまだ使用でき、Netty 4.1 を使用するようにアップグレードされています。NettyServer ページで、プロジェクトが Netty を使用するように明示的に設定できます。
§HTTP/2 サポート (実験的)
Play は、PlayAkkaHttp2Support
モジュールを使用して Akka HTTP サーバーで HTTP/2 サポートを提供するようになりました。
lazy val root = (project in file("."))
.enablePlugins(PlayJava, PlayAkkaHttp2Support)
これは、HTTP/2 の設定プロセスのほとんどを自動化します。ただし、デフォルトでは run
コマンドでは動作しません。詳細はAkka HTTP サーバーページを参照してください。
§リクエスト属性
Play 2.6 のリクエストには、属性が含まれるようになりました。属性を使用すると、リクエストオブジェクト内に追加情報を格納できます。たとえば、リクエストに属性を設定するフィルターを作成し、後でアクション内から属性値にアクセスできます。
属性は、各リクエストにアタッチされた TypedMap
に格納されます。TypedMap
は、型安全なキーと値を格納する不変マップです。属性はキーによってインデックス付けされ、キーの型は属性の型を示します。
Java
// Create a TypedKey to store a User object
class Attrs {
public static final TypedKey<User> USER = TypedKey.<User>create("user");
}
// Get the User object from the request
User user = req.attrs().get(Attrs.USER);
// Put a User object into the request
Request newReq = req.addAttr(Attrs.USER, newUser);
Scala
// Create a TypedKey to store a User object
object Attrs {
val User: TypedKey[User] = TypedKey.apply[User]("user")
}
// Get the User object from the request
val user: User = req.attrs(Attrs.User)
// Put a User object into the request
val newReq = req.addAttr(Attrs.User, newUser)
属性は TypedMap
に格納されます。属性の詳細については、TypedMap
のドキュメントを参照してください。 Javadoc、Scaladoc.
リクエストタグは非推奨となり、代わりに属性を使用する必要があります。詳細については、移行ドキュメントのタグセクションを参照してください。
§ルート修飾子タグ
routes ファイルの構文では、カスタム動作を提供する「修飾子」を各ルートに追加できるようになりました。CSRF フィルターでそのようなタグの 1 つである「nocsrf」タグを実装しました。デフォルトでは、次のルートには CSRF フィルターが適用されません。
+ nocsrf # Don't CSRF protect this route
POST /api/foo/bar ApiController.foobar
独自の修飾子を作成することもできます。+
記号の後に、任意の数の空白で区切られたタグを続けることができます。
これらは、HandlerDef
リクエスト属性で使用できます(routes ファイルのハンドラー定義に関するその他のメタデータも含まれています)。
Java
import java.util.List;
import play.routing.HandlerDef;
import play.routing.Router;
HandlerDef handler = req.attrs().get(Router.Attrs.HANDLER_DEF);
List<String> modifiers = handler.getModifiers();
Scala
import play.api.routing.{ HandlerDef, Router }
import play.api.mvc.RequestHeader
val handler = request.attrs.get(Router.Attrs.HandlerDef)
val modifiers = handler.map(_.modifiers).getOrElse(List.empty)
HandlerDef
リクエスト属性は、routes ファイルから Play によって生成されたルーターを使用する場合のみ存在することに注意してください。
Scala SIRD や Java RoutingDsl
など、コードでルートが定義されている場合は、この属性は追加されません。この場合、Scala では request.attrs.get(HandlerDef)
は None
を、Java では null
を返します。フィルターを作成する場合は、この点を考慮してください。
§インジェクタブル Twirl テンプレート
Twirl テンプレートは、@this
を使用してコンストラクター注釈で作成できるようになりました。コンストラクター注釈は、Twirl テンプレートをテンプレートに直接注入し、独自の依存関係を管理できることを意味します。コントローラーは、自分自身だけでなく、レンダリングする必要があるテンプレートの依存関係も管理する必要はありません。
例として、コントローラーで使用されていないコンポーネント TemplateRenderingComponent
に依存するテンプレートがあるとします。
まず、コンストラクターに @this
構文を使用してファイル IndexTemplate.scala.html
を作成します。コンストラクターは、apply
メソッドのテンプレートのパラメーターに使用される @()
構文の前に配置する必要があります。
@this(trc: TemplateRenderingComponent)
@(item: Item)
@{trc.render(item)}
デフォルトでは、Play 内で @this
構文を使用して Twirl が作成するすべての生成された Scala テンプレートクラスには、@javax.inject.Inject()
が自動的に注釈付けされます。必要に応じて、build.sbt
でこの動作を変更できます。
// Add one or more annotation(s):
TwirlKeys.constructorAnnotations += "@java.lang.Deprecated()"
// Or completely replace the default one with your own annotation(s):
TwirlKeys.constructorAnnotations := Seq("@com.google.inject.Inject()")
次に、コンストラクターでテンプレートを注入することによってコントローラーを定義します。
Java
public class MyController extends Controller {
private final views.html.indexTemplate template;
@Inject
public MyController(views.html.indexTemplate template) {
this.template = template;
}
public Result index() {
return ok(template.render());
}
}
Scala
class MyController @Inject()(indexTemplate: views.html.IndexTemplate,
cc: ControllerComponents)
extends AbstractController(cc) {
def index = Action { implicit request =>
Ok(indexTemplate())
}
}
テンプレートがその依存関係とともに定義されると、コントローラーにテンプレートを注入できますが、コントローラーは TemplateRenderingComponent
を認識しません。
§フィルターの強化
Play には、構成を通じて定義された、有効化されたフィルターのデフォルトセットが付属するようになりました。これにより、新しい Play アプリケーションに「デフォルトで安全」なエクスペリエンスが提供され、既存の Play アプリケーションのセキュリティが強化されます。
次のフィルターはデフォルトで有効になっています。
play.filters.csrf.CSRFFilter
は CSRF 攻撃を防ぎます。ScalaCsrf / JavaCsrf を参照してください。play.filters.headers.SecurityHeadersFilter
は XSS とフレームオリジン攻撃を防ぎます。SecurityHeaders を参照してください。play.filters.hosts.AllowedHostsFilter
は DNS リバインディング攻撃を防ぎます。AllowedHostsFilter を参照してください。
さらに、フィルターは application.conf
を通じて構成できるようになりました。デフォルトリストに追加するには、+=
を使用します。
play.filters.enabled+=MyFilter
テストのためにフィルターを具体的に無効にする場合は、構成からも実行できます。
play.filters.disabled+=MyFilter
詳細は、フィルターページを参照してください。
注記:
CSRF.formField
などの CSRF フォームヘルパーを使用していない既存のプロジェクトから移行する場合は、CSRF フィルターから PUT および POST リクエストで「403 Forbidden」が表示される場合があります。この動作を確認するには、<logger name="play.filters.csrf" value="TRACE"/>
をlogback.xml
に追加してください。同様に、localhost 以外で Play アプリケーションを実行する場合は、接続元のホスト名/IP を明示的に許可するように AllowedHostsFilter を構成する必要があります。
§gzip フィルター
gzip フィルターを有効にしている場合、application.conf
を介して (独自の Filters
クラスを作成する代わりに) コンテンツタイプに基づいて、どのレスポンスを gzip 圧縮するのか、しないのかを制御できるようになりました。
play.filters.gzip {
contentType {
# If non empty, then a response will only be compressed if its content type is in this list.
whiteList = [ "text/*", "application/javascript", "application/json" ]
# The black list is only used if the white list is empty.
# Compress all responses except the ones whose content type is in this list.
blackList = []
}
}
詳細は、gzip フィルターページを参照してください。
§JWT クッキー
Play は現在、セッションクッキーとフラッシュクッキーに JSON Web Token (JWT) 形式を使用しています。これにより、標準化された署名付きクッキーデータ形式、クッキーの有効期限(リプレイ攻撃を困難にする)、およびクッキーの署名における柔軟性の向上が可能になります。
詳細は、Scala または Java のページを参照してください。
§ロギングマーカーAPI
SLF4J マーカーのサポートが play.Logger
と play.api.Logger
に追加されました。
Java API では、SLF4J Logger API のストレートポートです。これは便利ですが、より豊富なロギングエクスペリエンスのために、Godaddy Logger などの SLF4J ラッパーが見つかる場合があります。
Scala API では、マーカーは MarkerContext トレイトを介して追加されます。これは、ロガーメソッドに暗黙のパラメーターとして追加されます。
import play.api._
logger.info("some info message")(MarkerContext(someMarker))
これにより、複数のステートメントのログ記録に暗黙のマーカーを渡すことが可能になり、MDCに頼ることなくログへのコンテキストの追加がはるかに容易になります。たとえば、Logstash Logback Encoderと暗黙の変換チェーンを使用すると、リクエスト情報をログステートメントに自動的にエンコードできます。
trait RequestMarkerContext {
// Adding 'implicit request' enables implicit conversion chaining
// See http://docs.scala-lang.org/tutorials/FAQ/chaining-implicits.html
implicit def requestHeaderToMarkerContext(implicit request: RequestHeader): MarkerContext = {
import net.logstash.logback.marker.LogstashMarker
import net.logstash.logback.marker.Markers._
val requestMarkers: LogstashMarker = append("host", request.host)
.and(append("path", request.path))
MarkerContext(requestMarkers)
}
}
そして、コントローラーで使用され、異なる実行コンテキストを使用する可能性のあるFuture
を通して運ばれます。
def asyncIndex = Action.async { implicit request =>
Future {
methodInOtherExecutionContext() // implicit conversion here
}(otherExecutionContext)
}
def methodInOtherExecutionContext()(implicit mc: MarkerContext): Result = {
logger.debug("index: ") // same as above
Ok("testing")
}
マーカーコンテキストは、「トレーサー弾」スタイルのログ記録にも非常に役立ちます。これは、ログレベルを明示的に変更せずに、特定のリクエストでログ記録を行う場合です。たとえば、特定の条件が満たされた場合にのみマーカーを追加できます。
trait TracerMarker {
import TracerMarker._
implicit def requestHeaderToMarkerContext(implicit request: RequestHeader): MarkerContext = {
val marker = org.slf4j.MarkerFactory.getDetachedMarker("dynamic") // base do-nothing marker...
if (request.getQueryString("trace").nonEmpty) {
marker.add(tracerMarker)
}
marker
}
}
object TracerMarker {
private val tracerMarker = org.slf4j.MarkerFactory.getMarker("TRACER")
}
class TracerBulletController @Inject() (cc: ControllerComponents) extends AbstractController(cc) with TracerMarker {
private val logger = play.api.Logger("application")
def index = Action { implicit request: Request[AnyContent] =>
logger.trace("Only logged if queryString contains trace=true")
Ok("hello world")
}
}
そして、logback.xml
で次のTurboFilterを使用してログ記録をトリガーします。
<turboFilter class="ch.qos.logback.classic.turbo.MarkerFilter">
<Name>TRACER_FILTER</Name>
<Marker>TRACER</Marker>
<OnMatch>ACCEPT</OnMatch>
</turboFilter>
詳細については、ScalaLoggingまたはJavaLoggingを参照してください。
ログ記録でのマーカーの使用の詳細については、LogbackマニュアルのTurboFilterセクションとマーカーベースのトリガーセクションを参照してください。
§設定の改善
Java APIでは、play.Configuration
の代わりにLightbendのConfigライブラリの標準的なConfig
オブジェクトに移行しました。これにより、メソッドがすべてのキーが存在することを期待するようになったため、動作が標準的な設定動作と一致するようになりました。移行の詳細については、Java設定移行ガイドを参照してください。
Scala APIでは、APIを簡素化し、カスタムタイプの読み込みを可能にするために、play.api.Configuration
クラスに新しいメソッドを追加しました。これで、暗黙的なConfigLoader
を使用して、任意のカスタムタイプを読み込むことができます。Config
APIと同様に、新しいConfiguration#get[T]
はデフォルトでキーが存在することを期待し、タイプT
の値を返しますが、null
の設定値を許可するConfigLoader[Option[T]]
もあります。詳細については、Scala設定ドキュメントを参照してください。
§セキュリティログ
Playのセキュリティ関連操作のためにセキュリティマーカーが追加され、セキュリティチェックに失敗した場合は、セキュリティマーカーが設定されたWARNレベルでログに記録されるようになりました。これにより、開発者は特定のリクエストが失敗した理由を常に把握できるようになり、Playでセキュリティフィルターがデフォルトで有効になっているため、これは重要です。
セキュリティマーカーを使用すると、セキュリティの失敗を通常のログ記録とは別にトリガーまたはフィルタリングすることもできます。たとえば、SECURITYマーカーが設定されているすべてのログ記録を無効にするには、logback.xml
ファイルに次の行を追加します。
<turboFilter class="ch.qos.logback.classic.turbo.MarkerFilter">
<Marker>SECURITY</Marker>
<OnMatch>DENY</OnMatch>
</turboFilter>
さらに、セキュリティマーカーを使用したログイベントは、さらなる処理のためにセキュリティ情報およびイベント管理(SIEM)エンジンへのメッセージをトリガーすることもできます。
§Javaでのカスタムログフレームワークの設定
以前は、カスタムログフレームワークを使用する場合は、Javaプロジェクトであっても、Scalaを使用して設定する必要がありました。現在は、JavaとScalaの両方でカスタムLoggerConfigurator
を作成できます。JavaでLoggerConfigurator
を作成するには、与えられたインターフェースを実装する必要があります。たとえば、Log4Jを設定するには
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import org.slf4j.ILoggerFactory;
import play.Environment;
import play.LoggerConfigurator;
import play.Mode;
import play.api.PlayException;
import java.io.File;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.*;
import org.apache.logging.log4j.core.config.Configurator;
public class JavaLog4JLoggerConfigurator implements LoggerConfigurator {
private ILoggerFactory factory;
@Override
public void init(File rootPath, Mode mode) {
Map<String, String> properties = new HashMap<>();
properties.put("application.home", rootPath.getAbsolutePath());
String resourceName = "log4j2.xml";
URL resourceUrl = this.getClass().getClassLoader().getResource(resourceName);
configure(properties, Optional.ofNullable(resourceUrl));
}
@Override
public void configure(Environment env) {
Map<String, String> properties = LoggerConfigurator.generateProperties(env, ConfigFactory.empty(), Collections.emptyMap());
URL resourceUrl = env.resource("log4j2.xml");
configure(properties, Optional.ofNullable(resourceUrl));
}
@Override
public void configure(Environment env, Config configuration, Map<String, String> optionalProperties) {
// LoggerConfigurator.generateProperties enables play.logger.includeConfigProperties=true
Map<String, String> properties = LoggerConfigurator.generateProperties(env, configuration, optionalProperties);
URL resourceUrl = env.resource("log4j2.xml");
configure(properties, Optional.ofNullable(resourceUrl));
}
@Override
public void configure(Map<String, String> properties, Optional<URL> config) {
try {
LoggerContext loggerContext = (LoggerContext) LogManager.getContext(false);
loggerContext.setConfigLocation(config.get().toURI());
factory = org.slf4j.impl.StaticLoggerBinder.getSingleton().getLoggerFactory();
} catch (URISyntaxException ex) {
throw new PlayException(
"log4j2.xml resource was not found",
"Could not parse the location for log4j2.xml resource",
ex
);
}
}
@Override
public ILoggerFactory loggerFactory() {
return factory;
}
@Override
public void shutdown() {
LoggerContext loggerContext = (LoggerContext) LogManager.getContext();
Configurator.shutdown(loggerContext);
}
}
注記:この実装はScalaバージョンの
LoggerConfigurator
と完全に互換性があり、必要に応じてScalaプロジェクトでも使用できます。つまり、モジュール作成者はJavaまたはScalaの実装を提供でき、JavaとScalaの両方のプロジェクトで使用できます。
§独立したJava FormsモジュールとPlayMinimalJavaプラグイン
Javaフォームの機能は、独立したモジュールに分割されました。フォーム機能はいくつかのSpringモジュールとHibernateバリデーターに依存しているため、フォームを使用していない場合は、不要な依存関係を回避するためにJavaフォームモジュールを削除することをお勧めします。
このモジュールはPlayJava
プラグインによって自動的に含まれますが、代わりにPlayMinimalJava
プラグインを使用することで無効にすることができます。
lazy val root = (project in file("."))
.enablePlugins(PlayMinimalJava)
§Javaコンパイル時コンポーネント
Scalaと同様に、PlayはJavaコンパイル時依存関係注入を有効にするコンポーネントを備えています。コンポーネントは、実装する必要があるインターフェースとして作成され、デフォルトの実装を提供します。 ランタイム依存関係注入を使用する場合に注入できるすべてのタイプのコンポーネントがあります。コンパイル時依存関係注入を使用してアプリケーションを作成するには、カスタム実装のplay.BuiltInComponents
を使用するplay.ApplicationLoader
の実装を提供するだけです。たとえば
import play.routing.Router;
import play.ApplicationLoader;
import play.BuiltInComponentsFromContext;
import play.filters.components.HttpFiltersComponents;
public class MyComponents extends BuiltInComponentsFromContext
implements HttpFiltersComponents {
public MyComponents(ApplicationLoader.Context context) {
super(context);
}
@Override
public Router router() {
return Router.empty();
}
}
play.ApplicationLoader
import play.ApplicationLoader;
public class MyApplicationLoader implements ApplicationLoader {
@Override
public Application load(Context context) {
return new MyComponents(context).application();
}
}
Javaコンパイル時依存関係注入ドキュメントで説明されているように、MyApplicationLoader
を設定します。
§フォーム処理I18Nサポートの改善
MessagesApi
クラスとLang
クラスは、Playでの国際化に使用され、フォームにエラーメッセージを表示するために必要です。
過去には、Playでフォームを組み立てるには複数のステップが必要であり、リクエストからMessages
インスタンスを作成することは、フォーム処理のコンテキストでは説明されていませんでした。
さらに、フォーム処理が必要な場合にすべてのテンプレートフラグメントを通してMessages
インスタンスを渡すことは不便であり、Messages
の暗黙的なサポートはコントローラートレイトを通じて直接提供されていました。I18N APIは、MessagesProvider
トレイト、リクエストに直接結び付けられた暗黙的なもの、およびフォームドキュメントの改善が追加されて洗練されました。
MessagesActionBuilder
が追加されました。このアクションビルダーはMessagesRequest
を提供します。これはWrappedRequest
であり、MessagesProvider
を拡張します。テンプレートで使用できる暗黙のパラメーターは1つだけで済み、Controller
をI18nSupport
で拡張する必要はありません。これは、フォームでCSRFを使用する場合、テンプレートで使用できるRequest
(技術的にはRequestHeader
)とMessages
オブジェクトの両方が必要なためにも役立ちます。
class FormController @Inject()(messagesAction: MessagesActionBuilder, components: ControllerComponents)
extends AbstractController(components) {
import play.api.data.Form
import play.api.data.Forms._
val userForm = Form(
mapping(
"name" -> text,
"age" -> number
)(UserData.apply)(UserData.unapply)
)
def index = messagesAction { implicit request: MessagesRequest[AnyContent] =>
Ok(views.html.displayForm(userForm))
}
def post = ...
}
ここで、displayForm.scala.html
は次のように定義されています。
@(userForm: Form[UserData])(implicit request: MessagesRequestHeader)
@import helper._
@helper.form(action = routes.FormController.post()) {
@CSRF.formField @* <- takes a RequestHeader *@
@helper.inputText(userForm("name")) @* <- takes a MessagesProvider *@
@helper.inputText(userForm("age")) @* <- takes a MessagesProvider *@
}
詳細については、ScalaI18Nを参照してください。
§テストサポート
MessagesApi
インスタンスを作成するためのサポートが改善されました。 MessagesApi
インスタンスを作成する場合は、デフォルトの引数を使用してDefaultMessagesApi()
またはDefaultLangs()
を作成できます。設定または別のソースからテストメッセージを指定する場合は、これらの値を渡すことができます。
val messagesApi: MessagesApi = {
val env = new Environment(new File("."), this.getClass.getClassLoader, Mode.Dev)
val config = Configuration.reference ++ Configuration.from(Map("play.i18n.langs" -> Seq("en", "fr", "fr-CH")))
val langs = new DefaultLangsProvider(config).get
new DefaultMessagesApi(testMessages, langs)
}
§Futureタイムアウトと遅延サポート
非同期操作におけるPlayのFutureのサポートが、Futures
トレイトを使用して改善されました。
play.libs.concurrent.Futures
インターフェースを使用して、非ブロッキングタイムアウトでCompletionStage
をラップできます。
class MyClass {
@Inject
public MyClass(Futures futures) {
this.futures = futures;
}
CompletionStage<Double> callWithOneSecondTimeout() {
return futures.timeout(computePIAsynchronously(), Duration.ofSeconds(1));
}
}
または、Scala APIでplay.api.libs.concurrent.Futures
トレイトを使用できます。
import play.api.libs.concurrent.Futures._
class MyController @Inject()(cc: ControllerComponents)(implicit futures: Futures) extends AbstractController(cc) {
def index = Action.async {
// withTimeout is an implicit type enrichment provided by importing Futures._
intensiveComputation().withTimeout(1.seconds).map { i =>
Ok("Got result: " + i)
}.recover {
case e: TimeoutException =>
InternalServerError("timeout")
}
}
}
指定された遅延後にのみFuture
を実行するdelayed
メソッドもあります。これはタイムアウトと同様に機能します。
詳細については、ScalaAsyncまたはJavaAsyncを参照してください。
§CustomExecutionContextとスレッドプールサイジング
このクラスは、akka.actor.ActorSystem
に委任するカスタム実行コンテキストを定義します。これは、デフォルトの実行コンテキストを使用すべきでない状況(たとえば、データベースまたはブロッキングI/Oを使用している場合)に非常に役立ちます。ThreadPoolsページに詳細情報がありますが、Play 2.6.xには、基盤となるAkkaディスパッチャのルックアップを処理するCustomExecutionContext
クラスが追加されています。
§事前設定されたCustomExecutionContextを使用したテンプレートの更新
PlayのダウンロードページにあるブロッキングAPI(つまり、Anorm、JPA)を使用するすべてのPlayサンプルテンプレートは、適切な場所にカスタム実行コンテキストを使用するように更新されています。たとえば、https://github.com/playframework/play-java-jpa-example/にアクセスすると、JPAPersonRepositoryクラスがすべてのデータベース操作をラップするDatabaseExecutionContext
を受け取ることがわかります。
JDBC接続プールを含むスレッドプールサイジングでは、スレッドプールエグゼキュータを使用して、接続プールと一致する固定スレッドプールサイズが必要です。HikariCPのプールサイジングページのアドバイスに従って、物理コアの数の2倍、プラスディスクスピンドルの数をJDBC接続プールに設定する必要があります。
ここで使用されているディスパッチャ設定は、Akkaディスパッチャからのものです。
# db connections = ((physical_core_count * 2) + effective_spindle_count)
fixedConnectionPool = 9
database.dispatcher {
executor = "thread-pool-executor"
throughput = 1
thread-pool-executor {
fixed-pool-size = ${fixedConnectionPool}
}
}
§ScalaでのCustomExecutionContextの定義
カスタム実行コンテキストを定義するには、ディスパッチャ名を使用してCustomExecutionContext
をサブクラス化します。
@Singleton
class DatabaseExecutionContext @Inject()(system: ActorSystem)
extends CustomExecutionContext(system, "database.dispatcher")
次に、暗黙のパラメーターとして実行コンテキストを渡します。
class DatabaseService @Inject()(implicit executionContext: DatabaseExecutionContext) {
...
}
§JavaでのCustomExecutionContextの定義
カスタム実行コンテキストを定義するには、ディスパッチャ名を使用してCustomExecutionContext
をサブクラス化します。
import akka.actor.ActorSystem;
import play.libs.concurrent.CustomExecutionContext;
public class DatabaseExecutionContext
extends CustomExecutionContext {
@javax.inject.Inject
public DatabaseExecutionContext(ActorSystem actorSystem) {
// uses a custom thread pool defined in application.conf
super(actorSystem, "database.dispatcher");
}
}
次に、JPAコンテキストを明示的に渡します。
public class JPAPersonRepository implements PersonRepository {
private final JPAApi jpaApi;
private final DatabaseExecutionContext executionContext;
@Inject
public JPAPersonRepository(JPAApi jpaApi, DatabaseExecutionContext executionContext) {
this.jpaApi = jpaApi;
this.executionContext = executionContext;
}
...
}
§Play WSClient
の改善
Play WSClient
には大幅な改善が加えられています。Play WSClient
は、スタンドアロンのplay-ws実装をラップするようになりました。これはPlayの外でも使用できます。さらに、play-wsに関与する基盤となるライブラリはシェーディングされているため、そこで使用されているNettyの実装は、Spark、Play、または異なるバージョンのNettyを使用するその他のライブラリと競合しません。
最後に、キャッシュ実装が存在する場合、HTTPキャッシングがサポートされるようになりました。HTTPキャッシュを使用すると、バックエンドRESTサービスへの繰り返しリクエストを削減でき、stale-on-error
およびstale-while-revalidate
などの復元力機能と組み合わせることで特に効果を発揮します。
詳細については、WsCacheとWS移行ガイドを参照してください。
§Play JSONの改良点
このリリースのJSONライブラリには、多くの改良点が含まれています。
§タプルのシリアライズ機能
Play-jsonでタプルのシリアライズが可能になり、暗黙的なスコープにReads
とWrites
の実装が追加されました。タプルは配列としてシリアライズされるため、("foo", 2, "bar")
はJSONで["foo", 2, "bar"]
としてレンダリングされます。
§Scala.jsのサポート
Play JSON 2.6.0は、Scala.jsをサポートするようになりました。依存関係は以下のように追加できます。
libraryDependencies += "com.typesafe.play" %%% "play-json" % version
ここで、version
は使用するバージョンです。ライブラリはJVM上とほぼ同様に動作しますが、JVMタイプのサポートはありません。
§自動JSONマッピングのカスタムネーミング戦略
Json
マクロ(reads
、writes
、format
)によって生成されるハンドラーをカスタマイズできます。そのため、必要なJSONフィールドにマッピングするネーミング戦略を定義できます。
カスタムネーミング戦略を使用するには、JsonConfiguration
とJsonNaming
の暗黙的なインスタンスを定義する必要があります。
2つのネーミング戦略が提供されています。1つはクラスプロパティの名前をそのまま使用するデフォルトのもの、もう1つはJsonNaming.SnakeCase
です。
デフォルト以外の戦略は、次のように使用できます。
import play.api.libs.json._
implicit val config = JsonConfiguration(SnakeCase)
implicit val userFormat: OFormat[PlayUser] = Json.format[PlayUser]
さらに、JsonNaming
の実装を提供することで、カスタムネーミング戦略を実装できます。
§テストの改良点
依存関係注入されたコンポーネントを使用した機能テストを容易にするために、2.6.xのplay.api.test
パッケージにいくつかのユーティリティクラスが追加されました。
§注入
インジェクターを暗黙的なapp
を通して直接使用する機能テストが多くあります。
"test" in new WithApplication() {
val executionContext = app.injector.instanceOf[ExecutionContext]
...
}
Injecting
トレイトを使用することで、これを省略できます。
"test" in new WithApplication() with Injecting {
val executionContext = inject[ExecutionContext]
...
}
§StubControllerComponents
StubControllerComponentsFactory
は、コントローラーの単体テストに使用できるスタブControllerComponents
を作成します。
val controller = new MyController(stubControllerComponents())
§StubBodyParser
StubBodyParserFactory
は、コンテンツの単体テストに使用できるスタブBodyParser
を作成します。
val stubParser = stubBodyParser(AnyContent("hello"))
§ファイルアップロードの改良点
ファイルのアップロードは、ScalaFileUpload / JavaFileUploadで指定されているように、一時ファイルシステムにファイルを保存するTemporaryFile
APIを使用します。これはref
属性からアクセスできます。
ファイルのアップロードは、無制限のファイルアップロードによってファイルシステムがいっぱいになる可能性があるため、本質的に危険な操作です。そのため、TemporaryFile
の考え方は、完了時にのみスコープ内にあり、できるだけ早く一時ファイルシステムから移動する必要があるというものです。移動されない一時ファイルは削除されます。
2.5.xでは、finalize
を使用して、ファイル参照がガベージコレクションされると、TemporaryFileが削除されました。しかしながら、特定の条件下では、ガベージコレクションがタイムリーに行われませんでした。バックグラウンドクリーンアップは、finalize
を使用するのではなく、FinalizableReferenceQueueとPhantomReferencesを使用するように変更されました。
TemporaryFile
のJavaとScalaのAPIは、すべてのTemporaryFile
参照がTemporaryFileCreator
トレイトから取得されるように、そして実装が必要に応じて交換できるようになり、利用可能な場合はStandardCopyOption.ATOMIC_MOVE
を使用するatomicMoveWithFallback
メソッドが追加されました。
§TemporaryFileReaper
Akkaスケジューラを使用して、ガベージコレクションの方法とは別に、定期的に一時ファイルを削除できるplay.api.libs.Files.TemporaryFileReaper
も追加されました。
リーパーはデフォルトで無効になっており、application.conf
で有効になります。
play.temporaryFile {
reaper {
enabled = true
initialDelay = "5 minutes"
interval = "30 seconds"
olderThan = "30 minutes"
}
}
上記の構成では、「olderThan」プロパティを使用して、30分以上経過したファイルが削除されます。アプリケーションの起動後5分後にリーパーが開始され、その後30秒ごとにファイルシステムがチェックされます。リーパーは既存のファイルアップロードを認識しないため、システムが適切に構成されていない場合、長時間のファイルアップロードはリーパーに遭遇する可能性があります。
次へ: 移行ガイド
このドキュメントに誤りを見つけましたか?このページのソースコードはこちらにあります。ドキュメントガイドラインをお読みになった後、プルリクエストを自由に投稿してください。ご質問やアドバイスがありましたら、コミュニティフォーラムでコミュニティとの会話を開始してください。