Javaのテンプレートエンジン

Javaのテンプレートエンジンについて調べてみた.ざっと検索してよく引っかかってきたのは二つ.

VelocityはApacheプロジェクトの一環で,Velocity Engineを中核として周辺にいくつかサブプロジェクトがある模様.現在version 1.5.FreeMarkerは独立したプロジェクトで,version 2.4 Preview 1が最新.どちらもアクティブに活動している模様だし,ぱっと見るとほとんど機能に差がないので,どちらを使ったらいいのか分からない.どちらもXMLを読み込んで別のXMLを出力するXSLT的な使い方もできるようだ.

FreeMarkerのページには,Migrating from Velocityというページがあり,Velocity engineのテンプレートファイルを自動的にFreeMarkerのテンプレートに変換するツールが公開されている.ただし,対応しているVelocityは1.2で,現行の1.5にどの程度対応しているのかは分からない.

さらにFreeMarker vs. Velocityという機能比較のページまである.FreeMarkerによれば,Velocityのほうがライトウェイトでシンプルなので一見良さそうにみえるかもしれないけど,シンプルな結果,さまざまなワークアラウンドを使うことになってしまい,生産性が低下している,とのこと.本当だろうか?

FreeMarkerを使ってみる.

とりあえず,後発でがんばっていそうな感じのするFreeMarkerを使ってみた.

インストール

上記ページからダウンロード,展開,antで簡単にビルドできた.ダウンロードしてきたパッケージは小さいのだが,ビルド時に必要なJarファイルを取りにいっているようだ.ビルド後のlibディレクトリはこんな感じ.

> ls lib
README.txt	freemarker.jar	jsp-api-1.2.jar	log4j.jar	struts.jar
ant.jar		javacc.jar	jsp-api-2.0.jar	logkit.jar	xalan.jar
dom4j.jar	jaxen.jar	jsp-api-2.1.jar	rt122.jar
emma.jar	jdom.jar	junit.jar	saxpath.jar
emma_ant.jar	js.jar		jython.jar	servlet.jar

すごいことになっているが,実行に必要なのはfreemarker.jarだけ.

ざっくりとした使い方

どんなテンプレートエンジンでも同じだけど,使い方はこんな感じ.

  • オブジェクトツリーを作る.
  • テンプレートを読み込む.
  • テンプレートにオブジェクトツリーを与えてレンダリング

オブジェクトツリーとしては,Map, List,Javaの配列,JavaBeansが使える.

テンプレート例

例としてタイトルと日付と中身を持つメモ帳をレンダリングすることを考えてみた.テンプレートはこんな感じ.

<html><head> <title> ${pageTitle} </title> </head>
<body><H1> ${pageTitle} </H1>

<#list entries as entry >
<div>
<span> ${entry.title} </span> <span>${entry.date?datetime}</span>
<div>
${entry.content}
</div>
</div>
</#list>

</body></html>

複数のメモをループでレンダリングする部分が

<#list entries as entry >

となっているのが,ちょっとわかりにくい.普通にforeachで

<#foreach entry in entries >

とかにしてくれればいいのに.

${entry.date?datetime}

と「?datetime」がついているのは,Dateオブジェクトに対して,日付と時間の両方を出力するようにというおまじない.

オブジェクトツリーの構成

まず,メモのクラスを定義する.

public class Entry {
  String title;
  Calendar date;
  String content;

  public String getTitle() {
    return title;
  }
  public Date getDate() {
    return date.getTime();
  }
  public String getContent() {
    return content;
  }
}

JavaBeansとしてアクセスするために,get メソッドが定義してある.putメソッド群は不要.

で,このメモエントリのリストを作る.

List<Entry> entries = new ArrayList<Entry>();
entries.add(new Entry("title 1", new GregorianCalendar(), "content 1"));
entries.add(new Entry("title 2", new GregorianCalendar(), "content 2"));
entries.add(new Entry("title 3", new GregorianCalendar(), "content 3"));

ルートとなるオブジェクトをマップとして作り,そこにこのリストを登録.ついでに,ページタイトルも.

Map<String, Object> root = new HashMap<String, Object>();
root.put("entries", entries);
root.put("pageTitle", "samplePage");
テンプレートを読みこんでレンダリング

テンプレートを読み込むにはちょっとおまじないが要る.まずコンフィギュレーションオブジェクトを作って,適当な設定をし,そのコンフィギュレーションからテンプレートを作る形になる.テンプレートができてしまえばあとは,オブジェクトツリーを食わせるだけ.

Configuration cfg = new Configuration();
cfg.setDirectoryForTemplateLoading(new File("."));
cfg.setObjectWrapper(new DefaultObjectWrapper());
Template temp = cfg.getTemplate("template.ftl");

temp.process(root, new OutputStreamWriter(System.out));

その他.

オブジェクトツリーは,マップやリストとBeansで構成する以外に,他のオブジェクトをラップする形でも作れるようだ.XMLのDOMをラップするものが提供されている.また,JSPサーブレットstrutsなどとの連携もできるようになっているようだ.

テンプレート内に変数を持つこともできるし,マクロの定義もできる.個人的にはちょっとやり過ぎのような気もする.