Velocity
Apache Velocity
汎用テンプレートエンジンの1つで、Javaで記述されているため様々な環境で実行させることができます。JSPの代わりなどとして、Webアプリで使用されることが多いですが、ローカルのアプリケーションに組み込んで使用することも可能です(例えば、バッチ処理によるメール送信アプリケーションのメール本文のテンプレート等に適用できます)。
テンプレートの記述方法に関してはいろいろなところで解説があるので、ここでは自分がハマったり、Velocityのソースから理解した内容を中心にメモを記載しています(なお、Velocity1.4&VelocityTools1.2を用いています)。
jarファイルに関して
velocity-1.4.jar
- Velocityテンプレートエンジンの本体。
- 別のバージョンのcommons-collections等を使用している場合、こちらを使用することになるのでは?
- ただし、バージョンの組み合わせには注意が必要。
velocity-dep-1.4.jar
- Velocityテンプレートエンジン+それを動かすために最低限必要となる外部ライブラリ。
- commons-collections, jakarta-oro等に含まれているうちの必要最低限のクラスを含んでいるようです。
- 単にVelocityを使用するだけであれば、このjarファイルを使用すればOK。
velocity-tools-generic-1.2.jar
- 汎用的(generic)なツール類を含んだライブラリ。
- ローカルアプリケーションで使用するのであれば、基本的にこのライブラリのみで充分のはずです。
velocity-tools-view-1.2.jar
- velocity-tools-generic-1.2.jarに含まれている内容+VelocityViewServletなどのServlet類を含んだライブラリ。
velocity-tools-1.2.jar
- velocity-tools-view-1.2.jarに含まれている内容+Strutsとの連携用のクラス類(ActionMessagesToolやErrorsToolなど)を含んだライブラリ。
- ファイルサイズを気にしないのであれば、常にこのライブラリを使用するようにしていてもOKかも。
エンジンの初期化
Velocity
org.apache.velocity.app.Velocity
内部的にシングルトンの作りとなっており、生成されるインスタンスは1つのみとなっています。従って、複数の設定を任意に切り替えながら使用したりすることはできませんが、テンプレートエンジン自体のインスタンスを保持することなく処理できるので、気軽に使えます。とりあえずVelocityを使ってみるという場合は、こちらから使用するといいかと思います。
単純に初期化するだけなら
Velocity.init();で完了。で、VelocityContextと何らかのWriterを準備し、
Template template = Velocity.getTemplate("templateFile.vm", "MS932");と書くことでテンプレートに値を埋め込んだ結果を取得することができます。
template.merge(context, writer);
なお、デフォルトではテンプレートの文字コードはiso-8859-1となっているので、Windows環境等においては、明示的にMS932を指定する機会が多くなるのではないかと思います(半角英数のみのテンプレートを用いる場合ならデフォルトのままで問題ありませんが)。
VelocityEngine
org.apache.velocity.app.VelocityEngine
Velocity1.2から追加されたようです。複数のインスタンスを作成することができるため、複数の設定を(アプリケーション内で)使い分けることができます。ただし、何らかの方法でVelocityEngineのインスタンスを保持する必要があります。
単純に初期化するだけなら
VelocityEngine engine = new VelocityEngine();でOKです。テンプレートとのマージを行う際も、このインスタンスを使用して
engine.init();
Template template = engine.getTemplate("templateFile.vm", "MS932");って感じの記述になります。
template.merge(context, writer);
初期化時の設定
VelocityもVelocityEngineも初期化する際にinit()以外に、init(java.util.Properties)あるいはinit(String)を利用することが可能です。
なお、いずれの方法で初期化を行った場合でも、まずはVelocityのjarファイルに含まれているorg/apache/velocity/runtime/defaults/velocity.propertiesファイルを読み込みます(このファイルに記述されている内容を「デフォルト値」と表現することとします)。その後、初期化時に指定された設定がある場合、その内容で上書きを行います(従って、プロパティを設定していない項目に関しては、デフォルト値が使用されることとなります)。
java.util.Propertiesを用いた初期化
Key&ValueをセットしたPropertiesのインスタンスを作成して、それを用いて初期化します。例えば、
Properties p = new Properties();とかやると、getTemplateを行う際の文字コードとして"MS932"を指定する手間を省いたり、Velocityのログファイルの出力先を変更したりすることが可能となります。
p.setProperty("input.encoding", "MS932");
p.setProperty("runtime.log", "./logs/velocity.log");
engine.init(p);
Stringを用いた初期化
引数のファイルを読み込んで、その内容を元に初期化します。org/apache/velocity/runtime/defaults/velocity.propertiesのコピーを作成し、必要な部分を書き換えるといった使い方がメインになるのではないかと思います。
普通にファイル名を相対パスあるいは絶対パスで指定することになります(プロパティファイルが見つからなかった場合、FileNotFoundExceptionがスローされます)。例えばカレントフォルダを基準にして、velocity/custom.propertiesファイルで初期化する場合、
VelocityEngine engine = new VelocityEngine();となります。
engine.init("velocity/custom.properties");
ログファイルに関して
複数のVelocityEngineを併用する場合、それぞれのEngineで別々のファイルにログを出力した方が無難です(どうも、デフォルトの動きを見た感じ、ファイルを作成する際に「新規ファイル」として出力しているようです)。
リソースローダー
FileResourceLoader
org.apache.velocity.runtime.resource.loader.FileResourceLoader
デフォルトのリソースローダー。何も指定しない場合、カレントフォルダを基準にファイルの検索を行います。なお、テンプレート名(ファイル名)の先頭が"/"で始まっている場合、無条件でそれを除去するようです。ですので、
template/template1.vmと
/template/template1.vmとは、結果的に同じものとなります。
また、file.resource.loader.pathを使用することで、基準となるパスを任意に設定することができます。カンマ区切りで複数のパスを登録することもでき、その場合は記述順にファイルの検索を行い、最初に見つかったファイルを使用するようです。
Properties p = new Properties();と設定した状態で、engine.getTemplate("template.vm")を指定した場合、C:/template.vm→C:/Tools/template.vmの順に検索を行います。なお、デフォルト値を上書きするため、カレントも基準フォルダとして登録する場合は以下のように明示的に追加する必要があります。
p.setProperty("file.resource.loader.path", "C:/, C:/Tools");
engine.init(p);
p.setProperty("file.resource.loader.path", "., C:/, C:/Tools");この場合、engine.getTemplate("template.vm")を実行すると、カレントフォルダ内のtemplate.vm→C:/template.vm→C:/Tools/template.vmの順に検索されます。
JarResourceLoader
org.apache.velocity.runtime.resource.loader.JarResourceLoader
Jarファイル内のテンプレートファイルを使用する際のリソースローダー。ざっくりと書くと、以下のような感じで初期化して使用します。
VelocityEngine engine = new VelocityEngine();
Properties p = new Properties();
p.setProperty(RuntimeConstants.RESOURCE_LOADER, "jar");
p.setProperty("jar.resource.loader.class",
"org.apache.velocity.runtime.resource.loader.JarResourceLoader");
p.setProperty("jar.resource.loader.path", "jar:file:jarTemplate/Templates.jar");
p.setProperty("runtime.log", "./logs/velocity_jar.log");
engine.init(p);
jarファイルは、カレントからの相対パスあるいは絶対パスで記述します。また、FileResourceLoaderの時と同様に複数指定することも可能です。
p.setProperty("jar.resource.loader.path",
"jar:file:jarTemplate/Templates1.jar, jar:file:jarTemplate/Templates2.jar");
内部的にはjava.net.JarURLConnectionが使用されているため、Web上のjarファイルをダウンロードしてきてテンプレートを読み取ることも可能です(まぁ、こういう使い方をするケースがどの程度あるかわかりませんが・・・)。
p.setProperty("jar.resource.loader.path", "jar:http://localhost:8080/WebTemplates.jar");
テンプレートを読み込む際は、jarファイル内の階層構造のまま記述すればOK。
Template template = engine.getTemplate("velocity/JarTemplate.vm");
内部的に、jar:file:jarTemplate/Templates.jar!/velocity/JarTemplate.vmに置き換えられ(パスの末尾が!/で終わっていない場合、!/を付加)、JarURLConnectionでロードされます。
ClasspathResourceLoader
org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader
クラスパス内からテンプレートを取得する際のリソースローダ。テンプレートファイルは、jarファイル(あるいはzipファイル)にまとめて、Java起動時の-cpオプションでクラスパスを通しておきます(サーバ上で実行するのであれば、WEB-INF/libフォルダ内に配置)。もちろん、jarファイル内にはアプリケーションのクラスファイルetc.を含めても問題ありません。
使い方そのものは他のリソースローダーと同様で、
VelocityEngine engine = new VelocityEngine();
Properties p = new Properties();
p.setProperty(RuntimeConstants.RESOURCE_LOADER, "class");
p.setProperty("class.resource.loader.class",
"org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
engine.init(p);
って感じで初期化します。クラスパスの特性上、FileResourceLoaderやJarResourceLoaderの時とは異なり、pathの指定はありません。
テンプレートを取得する際は、普通にクラスパスでの表記となります。
Template template = engine.getTemplate("velocity/TestTemplate.vm");
ちなみに、内部的にはざっくりとまとめると、
getClass().getClassLoader().getResourceAsStream("velocity/TestTemplate.vm")
という処理が実行されることとなります。
アプリケーションと一緒にjarファイルに入れて配布できるため、「テンプレートファイルだけ削除してしまった」というようなケースを防ぐ(手動でjarファイルの中身を書き換えられたら別ですが)ことはできそうですが・・・個人的に、どのようなケースで使用することになるのか、よくわかっていません。
複数のリソースローダーの併用
p.setProperty(RuntimeConstants.RESOURCE_LOADER, "file, jar");
って感じで指定することで、複数のリソースローダーを併用することができます。この場合、記述した順番でテンプレートファイルの検索が行われます。なお、fileという文字列を使用した場合、デフォルトのプロパティファイルで指定されている設定がそのままロードされることとなります(明示的に指定しなかった場合)。それ以外の文字列(例えばf)を指定した場合、
p.setProperty("f.resource.loader.class",
"org.apache.velocity.runtime.resource.loader.FileResourceLoader");
p.setProperty("f.resource.loader.path", ".");
のような感じで、明示的にローダーを設定する必要があります。
基本となるテンプレートはjarファイルに含めて配布する一方、その中から任意のテンプレートを取り出して編集し、編集後のファイルを(jarファイルに戻すのではなく)FileResourceLoader経由で読み込ませるようにする(従って、元の状態に戻す場合は編集後のファイルを削除すればOK)という使い方などができそうです。
まぁ、このような使い方なら、FileResourceLoaderの検索パスをcustom, defaultといった感じで複数指定し、編集後のファイルをcustomに入れるようにしておけば同じことができますが(ただ、テンプレートファイルの数が多い場合、jarファイルにまとめておくとすっきりしますけどね)。
リソースローダーを使わないテンプレートマージ
利用者がTextAreaで入力したテンプレートをそのまま用いたりする場合、物理ファイルとして存在するわけではないのでFileResourceLoader等を使うことができません(もちろん、テンポラリファイルに保存するという手段もありますが)。
そんなときは、
StringReader reader = new StringReader(templateText);とかいう感じでサクッとテンプレートとして使用することができちゃいます(templateTextは例えばJTextAreaの入力値)。
engine.evaluate(context, writer, "UserInputTemplate", reader);
Readerなら何でも渡すことができるので、極端な話、evaluate(VelocityContext, Writer, String, Reader)メソッドだけで何でもできてしまいます(^^; もちろん、テンプレートのキャッシュ制御やら何やらを考慮すると、ファイルとして存在するものまであえてevaluateを使用するメリットはないと思いますが。
ちなみに、文字列の場合は、evaluate(VelocityContext, Writer, String, String)メソッドを使えば、わざわざStringReaderを使用する必要もなくなります。なお、いずれのevaluateの場合も、第3引数のStringは何か問題が生じた場合にログ出力する際の「テンプレート名」とのことですので、わかりやすい名前にしておくといいでしょう。
リソースローダーのその他の設定
nameの部分は、resource.loaderで指定した名称をセットします。
キャッシュ
name.resource.loader.cache = false
テンプレートファイルのキャッシュを行うか否かを指定します。キャッシュを行うとパフォーマンスの向上が期待できますので、開発中以外は基本的にtrueで問題ないかと思います。
リロード間隔
name.resource.loader.modificationCheckInterval = 2
テンプレートファイルの更新確認を、どの程度の感覚(秒)で行うかを指定します。キャッシュがオン(true)の時のみ意味をなします。0を指定すると、更新確認を行わなくなるので、アプリケーション実行中にテンプレートファイルを差し替えた場合、アプリケーションを再起動するまで更新が反映されないこととなります。
VelocityTools
Webアプリケーションで使用するのであれば、VelocityViewServletとtoolbox.xml(ファイル名は一例)を用いるようにすればOKで、これに関してはいろいろとまとめられた資料があるのでここでは割愛。
GenericTools等のユーティリティクラス
org.apache.velocity.tools.generic.DateTool等を使用する場合、何も考えずにVelocityContextにputすればOKです。toolbox.xml等で使用するのと同じ名称でContextにセットするようにしておけば、Webアプリケーションで使用するテンプレートファイルをローカルで簡単にテストすることも可能です。
VelocityEngine engine = new VelocityEngine();
engine.init();
VelocityContext context = new VelocityContext();
context.put("date", new DateTool());
context.put("today", new Date());
StringWriter writer = new StringWriter();
String template = "日付のテスト $date.format('yyyy-MM-dd HH:mm:ss.SSS',$today) テンプレート";
StringReader reader = new StringReader(template);
engine.evaluate(context, writer, "テストテンプレート", reader);
reader.close();
writer.close();
System.out.println(writer.toString());
ってな感じの処理を実行すると、
日付のテスト 2007-02-17 11:35:48.515 テンプレート
のような結果を得ることができます。
まぁ、普通の値(オブジェクト)を格納するのと同じ要領で、ユーティリティークラスのインスタンスを格納して、テンプレートからメソッド実行を行っているわけですね。
VelocityViewServletと同様、定義ファイルを読み込んで自動的にVelocityContextに内容をセットするような抽象クラスを作成し、ローカルのアプリケーションではそれを継承して処理を行うようにすればいいのではないかと(スレッドを利用したアプリケーションで使用する場合、ユーティリティークラスがスレッドセーフとなっているかどうかに注意しなければいけませんが)。
当然ながら、VelocityContextに格納できるものであれば、自作のユーティリティークラスでも何でもOKです。
自作ユーティリティークラスをVelocityViewServletで使用する
GenericTools内にあるユーティリティークラスは基本的にPOJOですので、任意の自作クラスを利用することも可能です。toolbox.xmlに、適切なkeyを割り当ててクラスおよびスコープ(生存期間)をセットすれば使えるようになります。
<toolbox>
<tool>
<key>customdate</key>
<scope>application</scope>
<class>test.CustomDateTool</class>
</tool>
</toolbox>
のような感じです。スコープとしては、request/session/applicationがあるようですが、スレッドセーフなクラスなら、applicationにすることでパフォーマンスの向上が期待できるのではないかと思います。
2007年02月17日(土) 21:44:50 Modified by syo1976