Ba.とWEBと梅干し太郎

WEB制作・プログラミング・音楽の学習したこと・つくったもののアウトプットブログです。あとは日記。

(JAVA) Play FrameworkでAkkaのスケジューラーを使わずに処理を定期実行する

どうも!もっこりmokkoです。

これはHamee Advent Calendar 2016の24日目の記事です!

前回に引き続きPlay FrameWorkネタを一つ。
Play FrameworkではAkkaを使用したスケジューラーが提供&推奨されておりますが、今回はjarファイルを作成しcrontabで定期実行する方法です。
フレームワークの思想からは外れるかもしれないけど、WEBアプリケーションのデプロイ時にバッチの動作を担保したい場合は有用かも。あくまで一つの方法として記事にしておきます。


#この記事から分かること
- PlayFrameWorkでjarファイルを作成して処理を定期実行をする方法
- jarファイルの生成に使用するプラグインの導入方法


#作ったもの
- 10分間に一回、引数に渡した文字列でtwitterでエゴサしてコンソールに出す

#jarファイルを作成するプラグインを導入
sbt assemblyというプラグインを使用します。 Play FarmeworkはScalaで実装されたフレームワークなのでパッケージ管理はsbt(Scala build tool)になります。
というわけで、project/plugins.sbtに下記を追記。

addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.3")

せっかくなので最新バージョン(2016年12月末時点)を使用。
過去のバージョンに比べて設定に必要な記述が減っています。

続いてsbt assemblyの設定をbuild.sbtに記述します。
今回はシンプルに3つの設定だけ書きました。

//jarファイルの名前を決める。  
assemblyJarName in assembly := "twitter-kun.jar"  
//これを書いておくと、テストをスキップしてjarファイルを作成できる。コンパイル時間が長いので今回はスキップしちゃいます。    
test in assembly := {}  
//jarファイルのマニュフェストを設定できます。今回は実行したいmain関数を持つクラスを指定しました。  
mainClass in assembly := Some("batch.GetToTweet")

これでjarファイルを作成する準備は整いました。
プラグインをインストールする時間がかかるので、すぐにassemblyコマンドを叩くとエラーになるかも。

実際に叩くコマンドはこちら。プロジェクト直下で叩くとtarget/scala-x.xx.xx/下にjarファイルができます。

$activator assembly  

後はcrontabに書いてjarを実行してあげればいいわけですが、ここでハマりポイントがあります。
Play Frameworkはサーバー上でアプリケーションが起動している状態じゃないとクラスやORMを使えない場合があります。
Ebeanを使った時はうんともすんとも言いませんでした。

というわけで、jarファイルからでもアプリのソースを使いまわせるよう実行するmain関数内でアプリケーションの起動と停止をやってあげましょう。
Play Framework入門の記事で作ったアプリに定期実行で使えるクラスを追加してみました。

package batch;

import controllers.ApplicationController;
import play.api.Play;
import play.inject.guice.GuiceApplicationLoader;
import play.libs.Json;
import twitter4j.TwitterException;
import utils.TwitterSearch;
import java.io.File;
import java.util.Arrays;

public class GetToTweet {
    public static void main(String[] args) {

        //アプリケーションを起動するソース
        play.Environment env = new play.Environment(
                new File("."),
                ApplicationController.class.getClassLoader(),
                play.Mode.PROD
        );
        play.ApplicationLoader.Context context = play.ApplicationLoader.Context.create(env);
        GuiceApplicationLoader appLoader = new GuiceApplicationLoader();
        play.Application application = appLoader.load(context);

        //javaのライブラリにはstartのメソッドがないため、ここだけScalaのライブラリのPlay.api.play
        Play.start(application.getWrappedApplication());

        //コンソールに取得したツイートをはく。
        Arrays.stream(args).forEach(arg -> {
            try {
                System.out.println(Json.toJson(TwitterSearch.searchTweet(arg).getTweets()));
            } catch (TwitterException e) {
                e.printStackTrace();
            }
        });

        //処理が終わったらアプリケーションを停止してあげることも忘れずに
        Play.stop(application.getWrappedApplication());
    }
}

起動時に使っているクラス達はJavaScalaで同名のクラスがあり、importを間違えると型を解決できなくなるので要注意!
今回書いたソースでは、Play.start()までは全部Javaのクラスを使います。

        //scalaにはplay.api.Environmentがある。play.apiと続くものはscala。
        play.Environment env = new play.Environment(
                new File("."),
                ApplicationController.class.getClassLoader(),
                play.Mode.PROD
        );
        play.ApplicationLoader.Context context = play.ApplicationLoader.Context.create(env);
        GuiceApplicationLoader appLoader = new GuiceApplicationLoader();
        play.Application application = appLoader.load(context);

        //javaのライブラリにはstartのメソッドがないため、ここだけScalaのライブラリのPlay.api.play。Applicationクラスの.getWrappedApplication()でscalaのアプリとしてstart()に渡さなければいけない。
        Play.start(application.getWrappedApplication());

これで問題なくjarファイルが実行できるようになりました!

java -jar twitter-kun.jar 'メリークリスマス'  

f:id:masayannuu:20161225142002p:plain

Jsonにして出力してみました。
play.libs.Jsonを使えば簡単にListをJsonにして出力できるので便利です。

あとはcrontabをこんな感じで書けば、定期実行できますね。

*/10 * * * * root java -jar hogehoge/twitter-kun.jar 'メリークリスマス'   

といったところで、javaでplay Framework使っている人のお役に立てれば幸いです!