これは Mayaa Advent Calendar 2015 の18日目です。昨日は「Mayaaに慣れた僕がMixer2を試してみる」でした。
毎日ブログを書き続けて18日目です。
始めの頃は辛かったですが、今では一つの記事を1,2時間で書けるようになりました。書き溜めすれば良いのではないかと思われるかもしれませんが、案外、書き溜めはできないもので、毎日ネタを考えて書いています。
Mayaaアドベントカレンダーはまだ参加者を募集しています!
さて、テンプレートエンジン特集第2回はThymeleafです
前回のMixer2は、Mayaaと似たようなエンジンだろうと思って触ってみると、大分正確が異なる印象でした。
Mayaaよりも原始的な「テンプレートエンジン」として特化していて、Mixer2と比較するとMayaaは「ビューフレームワーク」と読んだほうが良いかもしれないと思いました。
さて、今日は海外でも人気があるThymeleafを試してみます。
Thymeleafとは
Thymeleaf is a Java library. It is a template engine capable of processing and generating HTML, XML, JavaScript, CSS and text, and can work both in web and non-web environments. It is better suited for serving the view layer of web applications, but it can process files in many formats, even in offline environments.
(引用:公式サイト)
Thymeleafは、
* Javaライブラリ
* テンプレートエンジン
* HTML, XML, JavaScript, CSS, Textを生成・処理できる
* Webだけでなく、Web以外でも動く
* Webアプリケーションのビューレイヤーとして提供するのに適している
* 様々なフォーマットのファイルを扱うことができ、オフライン環境のファイルすらも扱うことができる
という特徴をもったライブラリです。
日本語訳されたチュートリアルもあります。
テンプレートをXHTMLで書くという点がMayaaと共通しています。Mayaaと異なる点は、mayaaファイルというような独自の定義ファイルは存在せず、また、Mixer2のようにJava側でXMLツリーを操作するわけでもありません。
代わりに
<p th:text="#{home.welcome}">Welcome to our grocery store!</p>
のように書くようです。mayaaファイルの内容をそのままテンプレートに書いてしまったような感じです。
さっそく、HelloWorldしてみましょう。
HelloWorldしてみる
Thymeleafはリリース版の2.x系と、Beta版の3.x系があります。ここでは2を使いましょう。
ダウンロードはここからできます。Mavenと単体配布の両方があるようです。
また、SpringBootを使うならSTSのウィザードから最初に選べたりします!
今回は単体でダウンロードしました。
まず、チュートリアルに従って、アプリケーションクラスを作ってみます。Mixer2と同じでエンジンのシングルトン化をします。
public class HelloApplication {
private static TemplateEngine templateEngine;
static {
initializeTemplateEngine();
}
public static TemplateEngine getTemplateEngine() {
return templateEngine;
}
private static void initializeTemplateEngine() {
ServletContextTemplateResolver templateResolver =
new ServletContextTemplateResolver();
// XHTML is the default mode, but we set it anyway for better understanding of code
templateResolver.setTemplateMode("XHTML");
// This will convert "home" to "/WEB-INF/templates/home.html"
templateResolver.setPrefix("/WEB-INF/templates/");
templateResolver.setSuffix(".html");
// Template cache TTL=1h. If not set, entries would be cached until expelled by LRU
templateResolver.setCacheTTLMs(3600000L);
templateEngine = new TemplateEngine();
templateEngine.setTemplateResolver(templateResolver);
}
}
テンプレートを /WEB-INF/templates/hello.html として作成します
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title th:text="${title}">たいとる</title>
</head>
<body>
<span th:text="${message}">ハローワールド</span>
</body>
</html>
Servletを書いてみます
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
TemplateEngine templateEngine = HelloApplication.getTemplateEngine();
WebContext ctx = new WebContext(request, response, getServletContext(), request.getLocale());
ctx.setVariable("title", "タイトルThymeleaf");
ctx.setVariable("message", "こんにちはタイムリーフ!");
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html");
templateEngine.process("hello", ctx, response.getWriter());
}
}
結果
こんにちはタイムリーフ!
結果ソース
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>タイトルThymeleaf</title>
</head>
<body>
<span>こんにちはタイムリーフ!</span>
</body>
</html>
引き続き、いしがみメソッドの確認を行いましょう。
m:id含め、プログラム都合の識別子は常に大文字を使う
m:idのような識別子ではなく、直接テンプレートに変数を埋め込んでいきます。
例えば、上記例でのmessageなどの変数を大文字にすることはできるかもしれません。
ただ、そうすると、オブジェクトのプロパティを全部大文字にしなければならなくなるので適切な方法ではないでしょう。
もっとも、使う属性が"th:"から始まる独自のものなので、デザイナーと名前空間がぶつかることはありません。
m:idのする仕事を4種類に限定する
Thymeleafが提供している制御方法は次のものがあります。
- th:text
- th:each
- th:if, th:unless
- th:attr
- etc, ...
概ね必要なものはそろっています。デザイナーが属性名でどんな動きをするのか理解する必要がありますが、うまく伝達できれば問題なさそうです。
ただ、これら、自由な式が書けてしまうので、m:idのように一覧化できず、デザイナーは何を書いたら良いのか迷うと思われます。
事前に何を出すにはどうしたらよいかの手引書を共有するか、デザイナーに、ある程度の制御書式とデータ構造を教育する必要があります。
LOOP系のm:idはindexには長い名前を使い、maxを十分に大きな値を設定する
あまり問題にならないでしょう。
writeプロセッサーでエスケープを解除するときは全部する
エスケープしないテキストは th:utext で出力します。
tableタグだけは特別に扱う
m:idのような識別子を必要としないため、都度適切な属性を書けば良さそうなので、ここはThymeleafでは問題にならなそうです。
m:idをパラメータ対応にする
式を直接書けてしまうので、もはや、パラメータという概念がなさそうです。ただ、メソッドをコールするといった複雑なことはできなそうなので、案外むずかしいかもしれません。
PathAdjusterを効果的に使う
Thymeleafではth:href属性によって、パスを制御します。相対パスはIWebContextに格納したオブジェクトから取得してくるようですが、MayaaやMixer2ほど柔軟で自動的な制御はしてくれないようです。
ヘッダー・フッター・共通部品はiframeタグをうまく使う
「テンプレートフラグメント」という仕組みを使います。
th:fragment, th:includeなどを使います。
テンプレート側は、Mayaaのときと同じようにiframeで制御しても良いかもしれませんが、これも、自動的にうまいことやってもらうというより、自分でローカル用と、サーバー用の出力を両方書くようなスタイルになるのでしょう。
テンプレート上のコメントは、ソース表示時に見えないようにする
Thymeleafでは
<!--/*
*/-->
という書式を使うようです。
単語はなるべくテンプレート側に書く
これはできそうです。
また、多言語化やメッセージの書き換えの仕組みとして、th:text="#{キー}"
という構文でメッセージを多言語・テーブル化することができます。
Thymeleafではある程度テンプレートにロジックが入ってしまいますので、メッセージのテーブルを活用してテンプレートはあまりバリエーションを作らないのが良いスタイルかもしれません。
nekoHTMLパーサーをいじる
HTML5対応しているので、そんな必要はありません。
まとめ
Thymeleafは、テンプレートとmayaaファイルのように、表示と制御を完全に分離することなく、テンプレートにある程度の制御情報を残すことを許すことで、非常にシンプルで実用的な設計になっている印象を受けました。
デザイナーが直接テンプレートを触ることはないが、かといって、テンプレートにロジックをゴリゴリ書きたくないといったとき、妥当な選択肢だと思います。
デザイナーがある程度システムのわかる人間の場合は、ブラックボックス部分が少ないことがかえって円滑に連携できるかもしれません。
とはいえ、テンプレートの編集にかなりのシステムの知識が要求されてくるので、「デザイナーが自由にテンプレートをいじる」という世界には向かないようです。