Documentation

You are viewing the documentation for Play 1. The documentation for Play 2 is here.

ジョブ

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 メール送信