kagamihogeの日記

kagamihogeの日記です。

Mustache.javaさわる

spullara/mustache.java · GitHub

Mustache.javahello 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.javaCallableを見つけるとそこを勝手にマルチスレッドで実行してくれる、とのこと。また、動作を見るために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つのスレッドで処理されているのが見て取れる。もちろん、プールサイズを変更すればそのサイズまでしかスレッドは作られない。

f:id:kagamihoge:20150124150026j:plain

ハマッた点

先の実行でゴチャゴチャに出力されていた件に絡むのだけど、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の流儀をうまいこと活かしてるし。