ジョブ
Play はウェブアプリケーションフレームワークなので、アプリケーションロジックの大部分は HTTP リクエストに応じるコントローラによって行われます。
しかし、どのような HTTP リクエストとも関係のないところで何らかのアプリケーションロジックを実行しなければならないことも時々あります。初期化タスク、保守タスク、あるいは HTTP リクエストの実行プールをブロックせずに実行する長いタスクなどには、役に立つ場合があります。
ジョブはフレームワークによって完全に管理されます。これは、Play が JPA エンティティマネージャの同期と、トランザクション管理といったすべてのデータベース接続関連のものを管理することを意味します。ジョブを作成するのに必要なのは、 play.jobs.Job スーパークラスを継承することだけです。
package jobs;
import play.jobs.*;
public class MyJob extends Job {
public void doJob() {
// execute some application logic here ...
}
}
結果を返すジョブを作成しなければならない場合があります。そのときは doJobWithResult() メソッドをオーバーライドします。
package jobs;
import play.jobs.*;
public class MyJob extends Job<String> {
public String doJobWithResult() {
// execute some application logic here ...
return result;
}
}
この例では String を使用しましたが、もちろん、ジョブはどんなオブジェクト型でも返すことができます。
起動時に実行するジョブ
ブートストラップジョブはアプリケーション始動時に Play によって実行されます。ブートストラップジョブとしてマークするために必要なのは、 @OnApplicationStart アノテーションを追加することだけです。
import play.jobs.*;
@OnApplicationStart
public class Bootstrap extends Job {
public void doJob() {
if(Page.count() == 0) {
new Page("root").save();
Logger.info("The page tree was empty. A root page has been created.");
}
}
}
結果を返す必要はありません。結果を返しても、それは失われます。
警告
DEV モードでアプリケーションを実行する場合、アプリケーションは最初の HTTP リクエストがあるまで始動を控えます。さらには、DEV モードの場合、アプリケーションは必要に応じて自動的に再起動することがあります。
PROD モードで実行するとき、アプリケーションはサーバの起動と同時に開始します。
スケジューリングされたジョブ
スケジューリングされたジョブは、フレームワークによって定期的に実行されます。@Every アノテーションを使用することで、特定の間隔でジョブを実行するよう Play に指示することができます。
import play.jobs.*;
@Every("1h")
public class Bootstrap extends Job {
public void doJob() {
List<User> newUsers = User.find("newAccount = true").fetch();
for(User user : newUsers) {
Notifier.sayWelcome(user);
}
}
}
@Every アノテーションで事足りない場合は、CRON 式を使用してジョブを実行する @On アノテーションを使用することができます。
import play.jobs.*;
/** Fire at 12pm (noon) every day **/
@On("0 0 12 * * ?")
public class Bootstrap extends Job {
public void doJob() {
Logger.info("Maintenance job ...");
...
}
}
Tip
Play は Quartz ライブラリ の CRON 式パーサを使用します。
結果を返す必要はありません。結果を返しても、それは失われます。
中断可能なリクエスト
Play は非常に短いリクエストについて動作するよう意図されています。Play は、HTTP コネクタによってキューイングされたリクエストを処理する固定のスレッドプールを使用します。最適な結果を得るために、スレッドプールはできるだけ小さくあるべきです。デフォルトプールサイズには、通常、 プロセッサ数 + 1 を設定値として使用します。
これは、あるリクエストが非常に長い間 (例えば、長い計算を待っている間) 、スレッドプールをブロックし、アプリケーションの応答性に影響を与えることを意味します。もちろん、より多くのスレッドをプールに追加することもできますが、リソースの無駄遣いになりますし、とにかくプールは決して無限にはなりません。
例えば、表示する新しいメッセージを待つ、ブロッキング HTTP リクエストを送るチャットアプリケーションを考えてください。これらのリクエストは、とてもとても長い間 (通常は数秒間) スレッドプールをブロックします。100 人のユーザが同時にチャットアプリケーションに接続することを許容すると計画する場合、少なくとも 100 スレッドを支給する必要があるでしょう。これは実現可能です。しかし、1,000 ユーザの場合はどうですか? 10,000 ユーザだったら?
このようなユースケースを解決するために、Play ではリクエストを一時的に中断することができます。HTTP リクエストは接続された状態のまま残りますが、このリクエストの実行は、スレッドプールからポップされ、あとで再実行されます。指定した遅延の後にリクエストの実行を試みるか、ジョブの完了を待つように Play に指示することができます。
Tip
$PLAY_HOME/samples-and-tests/chat/ で実際の例を見ることができます。
例えば、このアクションは非常に長いジョブを起動し、HTTP レスポンスに結果を返さずにこのジョブの完了を待ちます:
public static void generatePDF(Long reportId) {
if(request.isNew) {
Report report = Report.findById(reportId);
Future<InputStream> task = new ReportAsPDFJob(report).now();
request.args.put("task", task);
waitFor(task);
}
renderBinary((Future<InputStream>) request.args.get("task").get());
}
このアクションは 2 回コールされます。最初の 1 回は、 request.isNew タグは true に設定されます。そのため、アクションは Job を作成して、直ちにこれを開始します。その後、アクションは、アクションの再実行を試みる前にタスクの完了を待つよう Play に指示します。タスクが完了した後、Play はこのアクションを再度実行します。このとき、 request.isNew タグは false に設定されます。アクションはジョブの結果を検索し、HTTP レスポンスとしてこれを送信します。
考察を続けます
次: e メール送信