12.

Java のテンプレートエンジン比較|Thymeleaf・FreeMarker・JSP

編集
この記事の要点
  • Spring Boot の標準は Thymeleaf。依存を足すだけで使える
  • Spring Boot で JSP は推奨されない。実行可能 jar では動かず war が必要になる
  • テンプレートは src/main/resources/templates/ に置く。戻り値の文字列がファイル名
  • 速度が要るなら FreeMarker、HTML として開ける利点が要るなら Thymeleaf
  • どれを使っても出力のエスケープを切らない。切ると XSS になる

比較

エンジン拡張子Spring Boot特徴
Thymeleaf.html標準ブラウザで直接開いても崩れない。情報が多い
FreeMarker.ftlh公式対応速い。表現力が高い。Spring 外でも使える
Mustache.mustache公式対応ロジックを書けない。学習が最も簡単
Groovy Templates.tpl公式対応Groovy で書く。使用例は少ない
JSP + JSTL.jsp非推奨古い資産向け。jar では動かない
Pebble / Jteサードパーティ速度重視。Jte は型安全

新規なら Thymeleaf、既存の JSP 資産があるなら JSP のまま、というのが実務での分かれ目です。

Thymeleaf

// build.gradle
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
@Controller
public class UserController {
    @GetMapping("/users")
    public String index(Model model) {
        model.addAttribute("users", userService.findAll());
        return "users/index";        // templates/users/index.html
    }
}
<!-- templates/users/index.html -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<body>
  <h1 th:text="${title}">ここは開発中のダミー</h1>

  <ul>
    <li th:each="u : ${users}" th:text="${u.name}">サンプル</li>
  </ul>

  <p th:if="${#lists.isEmpty(users)}">該当なし</p>
  <a th:href="@{/users/{id}(id=${u.id})}">詳細</a>
  <img th:src="@{/img/logo.png}" alt="ロゴ">
</body>
</html>

Thymeleaf の最大の特徴は「そのまま HTML として開ける」ことです。th:text は属性なので、ブラウザで直接開くとタグの中身(ダミー)が表示され、デザイナーがサーバーなしで確認できます。

JSP との書き方の違い

やりたいことJSP + JSTLThymeleaf
値の表示${user.name}th:text="${user.name}"
繰り返し<c:forEach>th:each
条件<c:if>th:if / th:unless
URL<c:url>@{/path}
共通部品<jsp:include>th:replace / th:insert
エスケープ<c:out>${}素通しth:text既定でエスケープ
<!-- Thymeleaf: 既定でエスケープされる(安全) -->
<p th:text="${comment}"></p>

<!-- エスケープしない(HTML として解釈させる) -->
<p th:utext="${comment}"></p>

th:utext に利用者の入力を渡すと XSS になります。使ってよいのは、自分で組み立てた安全な HTML か、サニタイズ済みの値だけです。

共通部分をまとめる

<!-- templates/fragments/layout.html -->
<header th:fragment="header">
  <nav><a th:href="@{/}">トップ</a></nav>
</header>
<!-- 使う側 -->
<div th:replace="~{fragments/layout :: header}"></div>

th:replaceその要素ごと置き換えth:insert中身に差し込みます。詳細は テンプレートフラグメント(ヘッダー等の共有化) を参照してください。

FreeMarker

implementation 'org.springframework.boot:spring-boot-starter-freemarker'
<!-- templates/users/index.ftlh -->
<h1>${title}</h1>

<ul>
<#list users as u>
  <li>${u.name}</li>
<#else>
  <li>該当なし</li>
</#list>
</ul>

<#-- null なら既定値 -->
${name!"名無し"}
<#if user??>ログイン中</#if>

拡張子を .ftlh にすると自動的に HTML エスケープされます。.ftl はエスケープされないため、必ず .ftlh を使ってください。

Spring Boot で JSP を避ける理由

  • 実行可能 jar では動かない。war にして外部の Tomcat に置く必要がある
  • 組み込み Jetty / Undertow では動かない
  • src/main/webapp/WEB-INF/jsp/ という Maven / Gradle の標準から外れた場所に置く必要がある
  • ${} がエスケープされないため、<c:out> の書き忘れが XSS に直結する
# どうしても JSP を使う場合の設定
spring.mvc.view.prefix=/WEB-INF/jsp/
spring.mvc.view.suffix=.jsp

設定

# 開発中はキャッシュを切る(保存すると即反映される)
spring.thymeleaf.cache=false

# 置き場所と拡張子(既定値)
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
spring.thymeleaf.encoding=UTF-8

# テンプレートが見つからないときにエラーにする
spring.thymeleaf.check-template-location=true

本番では cache=true に戻してください。毎回テンプレートを読み直すと大きく遅くなります。

選び方

状況選ぶもの
Spring Boot で新規に画面を作るThymeleaf
大量のページを高速に描画したいFreeMarker / Jte
テンプレートにロジックを書かせたくないMustache
既存の JSP が大量にあるJSP のまま(war で運用)
画面は React / Vue で作るテンプレートエンジンを使わない@RestController で JSON を返す)

Thymeleaf でよく使う書き方

やりたいこと書き方
テキストを出すth:text="${name}"
HTML として出すth:utext="${html}"XSS 注意
属性に入れるth:value th:src th:alt th:placeholder
URL を組み立てるth:href="@{/users/{id}(id=${u.id})}"
クラスを付け外しth:classappend="${active} ? 'on' : ''"
繰り返しth:each="u, stat : ${users}"
条件th:if / th:unless / th:switch
文字列の連結th:text="|${a} と ${b}|"
既定値${name} ?: '名無し'
タグを出さずに囲む<th:block th:each="...">
<!-- 繰り返しの状態変数 -->
<tr th:each="u, stat : ${users}"
    th:class="${stat.odd} ? 'odd' : 'even'">
  <td th:text="${stat.count}"></td>   <!-- 1 から -->
  <td th:text="${stat.index}"></td>   <!-- 0 から -->
  <td th:text="${u.name}"></td>
</tr>

<!-- 分岐 -->
<div th:switch="${user.role}">
  <p th:case="'admin'">管理者</p>
  <p th:case="*">一般</p>
</div>

<!-- ユーティリティオブジェクト -->
<span th:text="${#strings.abbreviate(text, 20)}"></span>
<span th:text="${#temporals.format(date, 'yyyy/MM/dd')}"></span>
<span th:text="${#numbers.formatInteger(price, 1, 'COMMA')}"></span>
<span th:if="${#lists.isEmpty(users)}">0 件</span>

JavaScript に値を渡す

<!-- インラインで埋め込む(JSON としてエスケープされる) -->
<script th:inline="javascript">
  const user = /*[[${user}]]*/ null;
  const name = /*[[${user.name}]]*/ "ダミー";
</script>

<!-- data 属性で渡す(こちらのほうが安全で分かりやすい) -->
<div id="app" th:data-user-id="${user.id}"></div>
<script>
  const id = document.getElementById("app").dataset.userId;
</script>

/*[[...]]*/ の後ろに書いたダミー値は、ブラウザで直接開いたときだけ使われます。文字列連結でスクリプトを組み立てないでください。値に </script> や引用符が入ると抜け出されます。

エラー画面のテンプレート

src/main/resources/templates/error.html          全エラー共通
src/main/resources/templates/error/404.html      404 専用
src/main/resources/templates/error/5xx.html      500 番台

この場所にファイルを置くだけで、Spring Boot 既定の Whitelabel Error Page が置き換わります。詳細は 独自のエラーページを定義する方法 を参照してください。

関連

編集
Post Share
子ページ
  1. Thymeleaf
同階層のページ
  1. インストール(eclipseプラグイン)
  2. クイックスタート
  3. プロジェクトの作成
  4. Spring Bootプロジェクトの作成
  5. Spring Bootプロジェクトの実行
  6. Spring BootでHello World!
  7. アノテーション一覧
  8. DB接続設定からエンティティおよびリポジトリの作成、値の取得まで(JPA編)
  9. DB接続設定や値の取得(JdbcTemplate編)
  10. ビューから値をモデルに格納しコントローラーで受け取る方法
  11. コントローラーにてモデルに値を格納してビューに渡す方法
  12. テンプレートエンジン
  13. ModelとModelAndViewの違い
  14. AOPの使用方法
  15. classpath: 内部ファイルの読み込み
  16. file: 外部ファイルの読み込み
  17. CSVファイルアップロード方法(Ajax)
  18. CSVファイルダウンロード方法(Ajax)
  19. Spring Bootプロジェクトのビルドと本番環境へのデプロイ方法(内部tomcat使用)
  20. Application.propertiesの環境依存設定の分割方法
  21. JPAにおけるEntityManagerの取得方法
  22. JPAにおけるjava.sql.Connectionの取得方法
  23. エラー一覧
  24. jarの引数を受け取る方法
  25. Spring BootでGmailからメール送信
  26. 複数のDBに接続する設定(Spring Boot & JPA編)
  27. ポート番号の変更
  28. Basic認証の実装と特定のURLに限定する方法
  29. Spring SecurityのBasic認証の無効化
  30. 独自のエラーページを定義する方法
  31. プロパティファイルの値やjar実行時の引数を取得する方法

最近更新/作成されたページ