はじめに (対象読者・この記事でわかること)
この記事は、Playframework 2.5(Java)でAkkaのsystem.scheduler()を使おうとして
ClassCastException: scala.concurrent.impl.ExecutionContextImpl cannot be cast to play.api.libs.streams.ExecutionContextProvider
とエラーに悩まされている開発者向けです。
Play 2.4 まで動いていた定期実行コードが、2.5 にアップデートした途端に動かなくなり、ググっても日本語記事が皆無だった…という経験をされた方に読んでいただきたい内容です。
記事を読み終えると、
・Play 2.5 での Dependency Injection の仕組み変化
・Akka Scheduler を安全に使うための正しい設定方法
・エラーの根本的な回避策
がしっかり身につきます。
前提知識
この記事を読み進める上で、以下の知識があるとスムーズです。 - Java8 以降の言語機能(ラムダ式など) - Playframework のルーティング・コントローラの基本 - Akka Actor の基礎用語(ActorSystem, ActorRef など)
Play 2.5 で変わった DI 仕様と Akka の関係
Play 2.5 から Dependency Injection の実装が Guice 一本に統一され、従来の Play.current や Play.application() などの静的アクセスが非推奨になりました。
それに伴い、Akka の ActorSystem も Guice 管理下に移行。
結果、次のような「古い」コードが動かなくなります。
Java// Play 2.4 以前で動いていたコード ActorSystem system = Play.application().actorSystem(); system.scheduler().scheduleOnce( Duration.create(5, TimeUnit.SECONDS), () -> Logger.info("5秒後に実行"), system.dispatcher() );
2.5 以降、上記コードを実行すると
scala.concurrent.impl.ExecutionContextImpl を ExecutionContextProvider へキャストしようとして失敗、
つまり ClassCastException が発生します。
これは、Guice が注入する ActorSystem が「Play 内部でラップされた特殊な実装」であり、
従来のように system.dispatcher() をそのまま scheduler に渡すと型互換が崩れるためです。
Play 2.5 以降で Akka Scheduler を使う正しい手順
以下、実プロジェクトで動作検証済みの設定・コードを共有します。
ステップ1:build.sbt の記述確認
Play 2.5 以降、Java 用 Akka scheduler を使うのに追加モジュールは不要です。
念のため、バージョンを明示しておくとトラブルが減ります。
Scala// project/plugins.sbt addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.5.19") // build.sbt scalaVersion := "2.11.11" // or 2.12.x libraryDependencies ++= Seq( javaWs, javaJpa, // Akka 関連は Play が既に依存しているため追加不要 )
ステップ2:独自実行用の ExecutionContext を用意する
Guice 注入された ActorSystem の dispatcher をそのまま使わず、
明示的に ExecutionContext.fromExecutor を新規作成してやるのがポイントです。
Javapackage tasks; import akka.actor.ActorSystem; import play.inject.ApplicationLifecycle; import scala.concurrent.ExecutionContext; import scala.concurrent.duration.Duration; import javax.inject.Inject; import javax.inject.Singleton; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; @Singleton public class ScheduledTask { private final ActorSystem system; private final ExecutionContext customExecutionContext; @Inject public ScheduledTask(ActorSystem system, ApplicationLifecycle lifecycle) { this.system = system; // 独自 ExecutionContext を生成 this.customExecutionContext = system.dispatchers().lookup("akka.actor.default-dispatcher"); // アプリ終了時にキャンセルできるように返却 lifecycle.addStopHook(() -> { // 必要に応じて Cancellable を保持して cancel() 呼ぶ return CompletableFuture.completedFuture(null); }); start(); } private void start() { system.scheduler().schedule( Duration.create(0, TimeUnit.SECONDS), // 初回即実行 Duration.create(30, TimeUnit.SECONDS), // 30秒周期 this::run, customExecutionContext ); } private void run() { play.Logger.info("⏰ 30秒周期で実行中・・・"); } }
ステップ3:Module に登録してアプリケーション起動時に初期化
Javaimport com.google.inject.AbstractModule; public class TaskModule extends AbstractModule { @Override protected void configure() { bind(ScheduledTask.class).asEagerSingleton(); } }
application.conf に追記
play.modules.enabled += "modules.TaskModule"
これで Play 起動と同時に scheduler が動き始めます。
ハマった点・エラー解決
-
ClassCastExceptionが出続ける
→system.dispatcher()を直接 scheduler に渡しているのが原因。
独自ExecutionContextを作って差し替えることで解消。 -
開発モード(run)では動くが、本番モード(start)で動かない
→ApplicationLifecycleの stopHook で scheduler をキャンセルしていないと、
クラスローダーが解放されずにリークする。
必ずCancellableを保持してcancel()呼ぶか、上記例のように
ライフサイクルフックで安全に終了させる。 -
テストコードで
ActorSystemが見つからない
→WithApplicationなどでアプリケーションを起動してから
app.injector().instanceOf(ActorSystem.class)で取得する。
解決策まとめ
Play 2.5 以降、Akka Scheduler を使う際は
「Guice 注入された ActorSystem は触らぬが華」ではなく、
「dispatcher をそのまま使わず、明示的に ExecutionContext を生成してやる」
という意識を持つだけで、ClassCastException は確実に回避できます。
まとめ
本記事では、Playframework 2.5 以降で Akka Scheduler が動かなくなる理由と、
それを回避するための正しい DI の設定方法を解説しました。
- Play 2.5 で DI が Guice 一本化され、ActorSystem も管理対象に
- 従来コードのまま
system.dispatcher()を scheduler に渡すとClassCastException - 独自 ExecutionContext を生成・指定することで安全に定期実行可能
この知見を活かせば、Play 2.5 でも 2.6、2.7、2.8 と移行する際にも
Akka まわりのトラブルを最小限に抑えながら、安定した定期処理を実装できます。
次回は「Play 2.8 + Akka Typed での scheduler 実装」について掘り下げていきます。
参考資料
- Play 2.5 Migration Guide - Akka Migration
- Akka Official Docs - Scheduler
- Playframework Google Group - ClassCastException discussion
