spullara/mustache.java · GitHub
Mustache.javaでhello worldレベルのことをやる。
環境
やったこと
サンプルコード動かす
pom.xmlに依存性追加する
<dependency> <groupId>com.github.spullara.mustache.java</groupId> <artifactId>compiler</artifactId> <version>0.9.0</version> </dependency>
spullara/mustache.java · GitHubのサンプルをコピペして動かしてみる。
src/main/resources
とかパスの通ってる場所に、テンプレートとなるtemplate.mustache
ファイルを作る。
{{#items}} Name: {{name}} Price: {{price}} {{#features}} Feature: {{description}} {{/features}} {{/items}}
これにデータ流し込むJavaのコードを書く。テンプレートに対応するクラスを作って、そのインスタンスをエンジンに流し込む感じ。
package com.example.kagamihoge.mustachepractice; import java.io.IOException; import java.io.PrintWriter; import java.util.Arrays; import java.util.List; import com.github.mustachejava.DefaultMustacheFactory; import com.github.mustachejava.Mustache; import com.github.mustachejava.MustacheFactory; public class Example1 { List<Item> items() { return Arrays.asList( new Item("Item 1", "$19.99", Arrays.asList(new Feature("New!"), new Feature("Awesome!"))), new Item("Item 2","$29.99", Arrays.asList(new Feature("Old."), new Feature("Ugly.")))); } static class Item { Item(String name, String price, List<Feature> features) { this.name = name; this.price = price; this.features = features; } String name, price; List<Feature> features; } static class Feature { Feature(String description) { this.description = description; } String description; } public static void main(String[] args) throws IOException { MustacheFactory mf = new DefaultMustacheFactory(); Mustache mustache = mf.compile("template.mustache"); mustache.execute(new PrintWriter(System.out), new Example1()).flush(); } }
実行結果はこんな感じ。
Name: Item 1 Price: $19.99 Feature: New! Feature: Awesome! Name: Item 2 Price: $29.99 Feature: Old. Feature: Ugly.
マルチスレッドで動かす
READMEを読む限りだと、どうやらパフォーマンスに力を入れているらしい。mustache.javaはそのまま使えば逐次処理で進むのだけど、比較的簡単にマルチスレッドで動かすように出来る。なので、サンプルコードをコピペしてそれを試してみる。
まず、Feature#Callable<String> description()
を作る。REAMEによると、mustache.javaはCallable
を見つけるとそこを勝手にマルチスレッドで実行してくれる、とのこと。また、動作を見るためにSystem.out
を入れている。
static class Feature { Feature(String description) { this.description = description; } String description; Callable<String> description() throws InterruptedException { return new Callable<String>() { public String call() throws Exception { System.out.println("description" + description); return description; } }; } }
次にエンジンを呼び出すmainを修正する。スレッドの生成元はこちら側で指定するので、DefaultMustacheFactory#setExecutorService
で適当なExecutorService
を設定する。
public static void main(String[] args) throws IOException { ExecutorService es = Executors.newFixedThreadPool(10); DefaultMustacheFactory mf = new DefaultMustacheFactory(); mf.setExecutorService(es); Mustache mustache = mf.compile("template.mustache"); Writer e = mustache.execute(new PrintWriter(System.out), new Example2()); e.close(); es.shutdown(); }
実行するとこんな感じ。出力内容については後述。
Name: Item 1 Price: $19.99 Feature: descriptionNew! descriptionUgly. descriptionAwesome! descriptionOld. New! Feature: Awesome! Name: Item 2 Price: $29.99 Feature: Old. Feature: Ugly.
Callable
にしたメソッドの中にブレークポイントを置くと、下みたいな感じになる。Feature#description
は都合4回呼ばれるので、4つのスレッドで処理されているのが見て取れる。もちろん、プールサイズを変更すればそのサイズまでしかスレッドは作られない。
ハマッた点
先の実行でゴチャゴチャに出力されていた件に絡むのだけど、mainも一つのスレッドで動いているということを忘れるとハマる羽目になる。
たとえば、マルチスレッド版のmainを以下のように、Writer#close
ではなくWriter#flush
にするとどうなるか。
public static void main(String[] args) throws IOException { ExecutorService es = Executors.newFixedThreadPool(4); DefaultMustacheFactory mf = new DefaultMustacheFactory(); mf.setExecutorService(es); Mustache mustache = mf.compile("template.mustache"); Writer e = mustache.execute(new PrintWriter(System.out), new Example2()); e.flush(); //e.close(); es.shutdown(); }
実行すると、以下のように中途半端にしか表示されない(かもしれない)
Name: Item 1 Price: $19.99 Feature: descriptionNew! descriptionOld. descriptionUgly. descriptionAwesome!
この原因は、4つのCallable
を呼び出したあとmainスレッドの実行が終了する場合があるため。
mainで一度flush
されるとその時点の内容が標準出力に書き出され、それから終了する。もちろん、スレッドの実行時の順序に依存するので、毎回同じ結果になるとは限らない。なので、今回はとりあえずclose
させることにした。
感想とか
スレッドを作る方法と、スレッド上で動かすコードとを、分離してるこのやり方は中々良いんじゃないかなーと。java.util.concurrent
の流儀をうまいこと活かしてるし。