M12i.

学術書・マンガ・アニメ・映画の消費活動とプログラミングについて

Thymeleafテンプレートエンジンをそれ単体で使う

f:id:m12i:20141206093357p:plain

このかんSpring Web MVCとの関連で取り上げてきたThymeleafテンプレートエンジンを、それ単体で──つまりサーブレット・コンテナの外、Spring Web MVCやSpring Bootを経由せず使う方法を知りたくなった(知る必要がでてきた)ので調べてみました。今回は依存関係のあれこれを考えるのが面倒だったのでMavenを使っています。

ディレクトリ構成は以下のようにしました:

├── pom.xml
└── src
     └── main
          ├── java
          │   └── sample
          │       └── Main.java
          └── resources
              └── templates
                  └── sample.html


まずはpom.xmlを用意します:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.m12i.thymeleaf.local</groupId>
  <artifactId>sample</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>sample project</name>
  <description>A sample project for using thymeleaf in local</description>

  <!--依存性にThymeleafの項目を追加-->
  <dependencies>
    <dependency>
      <groupId>org.thymeleaf</groupId>
      <artifactId>thymeleaf</artifactId>
      <version>2.1.3.RELEASE</version>
    </dependency>
  </dependencies>

  <!--以下は依存するライブラリも含めて単一の実行可能jarを生成するための設定-->
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-assembly-plugin</artifactId>
        <configuration>
          <descriptorRefs>
            <descriptorRef>jar-with-dependencies</descriptorRef>
          </descriptorRefs>
          <archive>
            <manifest>
              <mainClass>sample.Main</mainClass>
            </manifest>
          </archive>
        </configuration>
        <executions>
          <execution>
            <id>make-sample-jar</id>
            <phase>package</phase>
            <goals>
              <goal>single</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>

</project>

今回は依存するライブラリも含めて単一の実行可能jarを生成させたいので、Thymeleafのための<dependency>タグのほかに、<build>タグも記述しています。

Main.javaのコードは以下のようになります:

package sample;

import java.io.FileWriter;
import java.io.Writer;

import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;
import org.thymeleaf.context.IContext;
import org.thymeleaf.context.VariablesMap;
import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver;
import org.thymeleaf.templateresolver.TemplateResolver;

public class Main {
  
  public void execute(String... args) throws Exception {
    // テンプレートエンジンを初期化する
    final TemplateEngine engine = initializeTemplateEngine();
    // コンテキストを生成する
    final IContext ctx = makeContext(args);
    // 今回はWriter経由で結果を出力するのでWriterも初期化
    final Writer writer = new FileWriter("sample.html");
    // テンプレート名とコンテキストとWriterを引数としてprocessメソッドをコール
    engine.process("sample", ctx, writer);
    // Writerをクローズ
    writer.close();
  }
  
  private TemplateEngine initializeTemplateEngine() {
    // エンジンをインスタンス化
    final TemplateEngine templateEngine = new TemplateEngine();
    // テンプレート解決子をインスタンス化(今回はクラスパスからテンプレートをロードする)
    final TemplateResolver resolver = new ClassLoaderTemplateResolver();
    // テンプレートモードはXHTML
    resolver.setTemplateMode("XHTML");
    // クラスパスのtemplatesディレクトリ配下にテンプレートファイルを置くことにする
    resolver.setPrefix("templates/");
    // テンプレートの拡張子はhtml
    resolver.setSuffix(".html");
    // テンプレート解決子をエンジンに設定
    templateEngine.setTemplateResolver(resolver);
    return templateEngine;
  }
  
  private IContext makeContext(String... args) {
    final IContext ctx = new Context();
    final VariablesMap<String, Object> map = ctx.getVariables();
    // 変数マップにテンプレート変数を設定
    map.put("args", args);
    return ctx;
  }
  
  public static void main(String... args) throws Exception {
    new Main().execute(args);
  }
}

initializeTemplateEngine()メソッドでテンプレートエンジンを初期化しています。初期化の際、TemplateResolverの実装クラスが必要になります。今回はテンプレートも含めて実行可能jarに包んでしまうので、クラスパスからテンプレートを検索するClassLoaderTemplateResolverを使用しています(通常のファイルシステム上からテンプレートをロードする場合はFileTemplateResolverを使うようです)。

setPrefix()メソッドでテンプレートファイルのパスの接頭辞──つまりテンプレートを検索する際のベースパスを、setSuffix()メソッドでテンプレートファイル名の接尾辞を指定しています。今回のサンプルアプリでは、テンプレートファイルはsrc/main/resources/templates配下に、.htmlという拡張子で作成することになります。

makeContext()メソッドIContextを生成しています。このインターフェースはテンプレート変数のほかロケール情報なども保持するものです。今回は単純にテンプレート変数を設定していますが、メッセージファイルを使ってアプリケーションの国際化(多言語化)などを行う際にはロケールの設定も変更することになるかもしれません。

execute()メソッドではテンプレートエンジンとコンテキストを使って実際にレンダリング処理を行っています。レンダリング結果は単純に文字列として得ることもできますし、今回のようにWriter経由で任意のストリームに出力させることもできます。

テンプレートファイルsample.htmlの内容は以下のようになります:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
   xmlns:th="http://www.thymeleaf.org" xml:lang="ja" lang="ja">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Sample</title>
</head>
<body>
  <h1>Arguments</h1>
  <p>
    The sample application received
    <span th:text="${#arrays.isEmpty(args)} ? 'no' : 'some'">...</span>
    arguments.
  </p>
  <ul>
    <li th:each="a : ${args}"><span th:text="${a}">...</span></li>
  </ul>
</body>
</html>

Thymeleaf標準言語*1の構文については過去の記事(その1その2)を参考してください。

このファイルはsrc/main/resources/templates配下に置かれているので、ビルドされると(classpath)/templates配下に移動されます。そしてクラスローダによりロードされます。

Mavenでビルドしてから、java jar sample-0.0.1-SNAPSHOT-jar-with-dependencies.jar foo bar bazコマンドを実行すると以下のようなHTMLファイルが生成されます:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Sample</title>
</head>
<body>
  <h1>Arguments</h1>
  <p>
    The sample application received
    <span>some</span>
    arguments.
  </p>
  <ul>
    <li><span>foo</span></li>
    <li><span>bar</span></li>
    <li><span>baz</span></li>
  </ul>
</body>
</html>


Spring関連の訳出記事まとめ - M12i.

*1:"Standard Dialect"を"標準方言"と訳している記事を見かけますが、開発者の出身がスペインという点を考慮すると、"標準言語"のほうが合っている気がします