ドキュメント

§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.sbtscalaVersion設定を行うことで、使用する 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 のドキュメントを参照してください。 JavadocScaladoc.

リクエストタグは非推奨となり、代わりに属性を使用する必要があります。詳細については、移行ドキュメントのタグセクションを参照してください。

§ルート修飾子タグ

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 アプリケーションのセキュリティが強化されます。

次のフィルターはデフォルトで有効になっています。

さらに、フィルターは 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.Loggerplay.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つだけで済み、ControllerI18nSupportで拡張する必要はありません。これは、フォームで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などの復元力機能と組み合わせることで特に効果を発揮します。

詳細については、WsCacheWS移行ガイドを参照してください。

§Play JSONの改良点

このリリースのJSONライブラリには、多くの改良点が含まれています。

§タプルのシリアライズ機能

Play-jsonでタプルのシリアライズが可能になり、暗黙的なスコープにReadsWritesの実装が追加されました。タプルは配列としてシリアライズされるため、("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マクロ(readswritesformat)によって生成されるハンドラーをカスタマイズできます。そのため、必要なJSONフィールドにマッピングするネーミング戦略を定義できます。

カスタムネーミング戦略を使用するには、JsonConfigurationJsonNamingの暗黙的なインスタンスを定義する必要があります。

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秒ごとにファイルシステムがチェックされます。リーパーは既存のファイルアップロードを認識しないため、システムが適切に構成されていない場合、長時間のファイルアップロードはリーパーに遭遇する可能性があります。

次へ: 移行ガイド


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