1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Thymeleafを学んでみた

1
Posted at

|概要・6つのテンプレートモード・処理フロー・基本実装

Spring Bootを使った画面開発を理解するために、Thymeleafについて学習した内容を整理する。

この記事ではまず初歩の初歩として、以下についてまとめる。

  1. Thymeleafとは何か
  2. Thymeleafを使う目的
  3. 6つのテンプレートモード
  4. Spring Boot + Thymeleafのテンプレート処理フロー
  5. 基本的な実装例

そもそもThymeleafとは?

Thymeleafは、Javaで利用できるサーバーサイドのテンプレートエンジンのこと。

Spring Bootと組み合わせることで、Java側で取得したデータをHTMLへ反映し、ユーザーや処理結果によって内容が変わるWeb画面を作ることができる。

公式ドキュメント

Thymeleaf公式では以下のように説明されている。

Thymeleaf is a modern server-side Java template engine

つまり、Javaのサーバー側でテンプレートを処理するための仕組みということ。

Thymeleaf公式「Using Thymeleaf」

ただ、最初は「テンプレートエンジン」と言われても何をするものなのか分からなかった。

例えば、Java側でログインユーザーの名前としてString userName = "大谷翔平";という値を取得したとする。

Javaの中には「大谷翔平」という値が存在しているが、それだけではブラウザには何も表示されない。

逆にHTMLへ直接<p>大谷翔平</p>と書けば表示できるが、今度は表示内容が「大谷翔平」で固定される。

ログインしているのが山田太郎なら山田太郎、佐藤花子なら佐藤花子と表示を変えたい。

そこで必要になるのが、

Java側で取得した値をHTMLへ渡し、その値を使ってHTMLを作る仕組みとなる。

Thymeleafでは、例えば<p th:text="${userName}">ユーザー名</p>と書くことで、Java側から渡されたuserName<p>タグの中へ表示できる。

userNameに「大谷翔平」が入っていれば、Thymeleafによる処理後のHTMLは<p>大谷翔平</p>となる。

大まかな流れは以下のとおり。

Javaでデータを取得・生成
        ↓
Controllerから画面へ渡す
        ↓
Thymeleafがテンプレートを処理
        ↓
Java側のデータをHTMLへ反映
        ↓
完成したHTMLをブラウザへ返す

Thymeleafを使う目的

通常のHTMLへ<p>大谷翔平</p>と書いた場合、ブラウザに表示される内容は固定される。

しかし、実際のWebアプリケーションでは毎回同じ内容を表示するわけではない。

例えば、

  • ログインしたユーザーによって名前を変える
  • DBから取得した商品を一覧表示する
  • 検索条件によって一覧の件数を変える
  • エラーが発生した場合だけエラーメッセージを表示する
  • ユーザーの権限によってボタンを表示・非表示にする

といったように、Java側で取得したデータや処理結果によって画面を変える必要がある。

普通のHTMLだけではJava側にある値をそのまま利用できないため、その間をつなぐ仕組みが必要になる。

そこでThymeleafを利用する。

例えばControllerでmodel.addAttribute("userName", "大谷翔平");として画面へ値を渡しておき、HTMLへ<p th:text="${userName}">ユーザー名</p>と記述する。

すると${userName}からModelに入っている値を取得し、「大谷翔平」を表示できる。

結果は、以下となる。

<p>大谷翔平</p>

別のユーザーであれば、Java側から渡すuserNameを変えるだけで画面の表示も変わる。

Thymeleafを使う目的は、単に「HTMLを動的にするため」ではなく、

Java側で取得・処理したデータをHTMLへ反映し、そのデータに応じて画面を変えるためと理解した。


Natural Templates

Thymeleafの特徴として、**Natural Templates(ナチュラルテンプレート)**という考え方がある。

例えば、以下のHTMLがあったとする。

<p th:text="${userName}">サンプルユーザー</p>

ここで疑問だったのが、th:text="${userName}"で値を入れ替えるのに、なぜタグの中に「サンプルユーザー」と書いているのかということ。

Thymeleafを使って処理した場合、「サンプルユーザー」はJava側から渡された値で置き換えられる。

userNameが「大谷翔平」であれば、結果は<p>大谷翔平</p>となる。

一方で、このHTMLファイルをThymeleafを通さずそのままブラウザで開けば、「サンプルユーザー」と表示される。

つまり、1つのHTMLを、

HTML単体で確認
        ↓
「サンプルユーザー」を表示


Spring Bootから表示
        ↓
Java側から渡されたuserNameを表示

という形で扱える。

これが何に使えるかというと、例えばまだJava側の処理が完成していない状態でも、HTML単体で仮のユーザー名を表示してレイアウトを確認できる。

Natural Templatesを使うことで、

Thymeleafを動かさなくてもHTML単体で画面の見た目を確認でき、実際にアプリケーションから表示したときはJava側のデータへ置き換えられる。

Thymeleafではth:textth:eachといったHTML属性を追加して処理を書くため、元のHTMLの形を大きく崩さずに動的な処理を追加できる。


6つのテンプレートモード

ThymeleafというとHTMLを処理するものというイメージがあったが、実際にはHTML以外もテンプレートとして扱える。

Thymeleafには以下の6つのテンプレートモードが用意されている。

テンプレートモード 主な用途
HTML Web画面などのHTML
XML XMLファイル
TEXT メール本文やテキストファイル
JAVASCRIPT JavaScript
CSS CSS
RAW 加工せずそのまま出力

公式ドキュメント

Thymeleaf公式でも以下の6種類がテンプレートモードとして定義されている。

HTML, XML, TEXT, JAVASCRIPT, CSS and RAW

Thymeleaf公式チュートリアル

なぜ6種類も存在するかというと、ThymeleafがHTML専用ではなく、さまざまな形式のテンプレートへデータを反映できるテンプレートエンジンだから。


1. HTML

Spring Boot + ThymeleafでWeb画面を作る場合、基本的に使用することになるモード。

例えばDBからユーザー一覧を取得した場合、検索結果が必ず3件になるとは限らない。

0件のこともあれば、10件、100件になることもある。

普通のHTMLであればユーザーの数だけ<tr>を書かなければならないが、取得件数が毎回変わる以上、その方法では対応できない。

そこでHTMLモードではth:eachなどを利用して、Java側から受け取ったデータの件数に合わせてHTMLを生成できる。

<tr th:each="user : ${users}">
    <td th:text="${user.id}"></td>
    <td th:text="${user.name}"></td>
</tr>

usersが3件なら3行、10件なら10行の<tr>が生成される。

つまりHTMLモードは、Java側の値やデータ件数に合わせてWeb画面の内容を変えたい場合に利用するモードとなる。


2. XML

XMLをテンプレートとして処理するモード。

XMLについてもJava側から渡されたデータを埋め込める。

<user>
    <name th:text="${userName}">sample</name>
</user>

HTMLモードとの違いとして覚えておきたいのが、XMLモードはXMLの書き方のルールを厳密に守る必要があること。

例えば<name>大谷翔平と開始タグを書いたのであれば、</name>まで書いてタグを閉じる必要がある。

以下のXMLは<name>が閉じられていないため正しいXMLではない。

<user>
    <name>大谷翔平
</user>

正しくは以下となる。

<user>
    <name>大谷翔平</name>
</user>

属性についても<user name=大谷翔平>ではなく、<user name="大谷翔平">のように値をクォートで囲む必要がある。

XMLモードが必要になるのは、

HTML画面を作りたいからではなく、XMLをThymeleafで生成したい場合。

そのためXMLとして正しい形式になっているかもチェックされる。


3. TEXT

TEXTモードは、HTMLやXMLのようなタグを持たない普通のテキストをテンプレートとして扱う。

例えば、ユーザーへ送るメールを考える。

本文へ「こんにちは、山田太郎さん」と直接書けば、全員に山田太郎と送ることになってしまう。

そこでTEXTモードを使い、こんにちは、[( ${userName} )] さんのようにユーザー名を差し込めるようにする。

userNameが「大谷翔平」であれば、結果は以下となる。

こんにちは、大谷翔平さん

そのためTEXTモードは、

  • メール本文
  • テキスト形式のドキュメント
  • テキストファイル

など、HTMLではない文章へJava側のデータを埋め込みたい場合に利用できる。


4. JAVASCRIPT

JavaScriptでもJava側で取得した値を使いたい場合がある。

例えばJava側でログインユーザーの情報や設定値を取得しており、それを使ってJavaScriptの処理を変えたい場合。

HTML内でJavaScriptインライン処理を利用する場合は、以下のように記述できる。

<script th:inline="javascript">
    const userName = [[${userName}]];
</script>

userNameが「大谷翔平」であれば、JavaScript側でもその値を利用できる。

例えば、

  • Java側で取得した設定値をJavaScriptで利用する
  • Java側から渡されたデータをグラフ表示に利用する
  • ログインユーザーの情報をJavaScript側でも使う

といった場合が考えられる。

JAVASCRIPTモードは、JavaScriptの代わりにThymeleafを書くものではない。

Java側のデータをJavaScriptのテンプレートへ反映したい場合に使うモード

という理解でよさそう。


5. CSS

CSSについてもThymeleafのテンプレートとして処理できる。

通常はCSSへ固定のスタイルを書くため、HTMLほど使う機会は多くない。

ただし、Java側から渡された値によってCSS自体を動的に変更したい場合には利用できる。

例えば、ユーザーごとの設定によってテーマカラーを変えるなど、CSSの値そのものを動的に生成したいケースが考えられる。


6. RAW

RAWは、Thymeleafで内容を加工せず、そのまま出力するモード。

最初は「何も処理しないなら必要ないのでは?」と思ったが、Thymeleafのテンプレートとして扱いながら、その内容については一切解析・変更させたくない場合に使用できる。

入力された内容
        ↓
RAWモード
        ↓
内容を変更しない
        ↓
そのまま出力

公式では「no-op template mode」と説明されている。

つまり、Thymeleafによる置換などを行わず、入力されたリソースをそのまま出力したい場合のモードとなる。


Spring Boot + Thymeleafの処理フロー

ここまでで、Thymeleafを利用するとJava側のデータをHTMLへ反映できることは分かった。

次に分からなかったのが、Controllerから渡したデータが、どうやってHTMLまで届いているのかという部分。

画面表示までの流れをかなり単純化すると以下となる。

① ブラウザ
    ↓
   GET /users

② Controller
    ↓
   リクエストを受け取る

③ Serviceなど
    ↓
   必要なデータを取得・処理

④ Controller
    ↓
   Modelへデータを入れる
   View名を返す

⑤ Thymeleaf側でViewを解決
    ↓
   使用するテンプレートを取得

⑥ Thymeleaf
    ↓
   Modelの値をHTMLへ反映

⑦ 完成したHTML
    ↓

⑧ ブラウザへ返却

例えば/usersへアクセスすると、Controllerがリクエストを受け取り、必要なデータをModelへ設定する。

その後、Controllerが「どの画面を表示するのか」をView名として返し、Thymeleafが対象となるHTMLを取得する。

最後にModelへ入っているデータをHTMLのth:textth:eachへ反映し、完成したHTMLをブラウザへ返す。


ViewとViewResolver

以下のControllerを例にする。

@GetMapping("/users")
public String showUsers(Model model) {

    model.addAttribute("message", "ユーザー一覧");

    return "users";
}

最初に疑問だったのが、最後のreturn "users";

なぜusersという文字列をreturnするだけで、users.htmlが表示されるのか分からなかった。

ここで出てくるのがViewViewResolver

Controllerが返しているusersはHTMLそのものではなく、論理的なView名として扱われる。

ViewResolverは、そのView名から実際に利用するViewを解決する。

今回の例では大まかに、

Controller
    ↓
return "users"
    ↓
ViewResolver
    ↓
usersというViewを解決

という関係になる。

公式ドキュメント

ThymeleafのSpring連携ドキュメントでは、Viewは実際のHTMLインターフェースをレンダリングし、ViewResolverはView名からViewを取得する役割として説明されている。

Spring Boot + Thymeleafでは、このSpring MVCの仕組みとThymeleafが連携して画面を表示している。


TemplateResolverとTemplateEngine

Thymeleaf側では、テンプレートを見つける処理と、テンプレートの中身を処理する役割も分かれている。

そこで出てくるのが、TemplateResolverTemplateEngine

最初はViewResolverとTemplateResolverで名前が似ているため分かりにくかった。

ざっくり整理すると、

名前 役割
ViewResolver Controllerが返したView名からViewを解決する
TemplateResolver 実際に利用するテンプレートファイルを見つける
TemplateEngine テンプレートをModelの値などを使って処理する

TemplateResolver

例えばControllerがreturn "users";と返しても、「usersというテンプレートが実際にどこに置いてあるのか」までは書かれていない。

そこでTemplateResolverがテンプレートファイルを探す。

例えば、

prefix : classpath:/templates/
View名 : users
suffix : .html

という条件であれば、組み合わせたclasspath:/templates/users.htmlが対象となる。

Springとの連携ではSpringResourceTemplateResolverが用意されている。


TemplateEngine

TemplateResolverによってusers.htmlを使うことが分かっても、それだけではHTML内のth:textth:eachは処理されていない。

そこでTemplateEngineがテンプレートを処理する。

例えばHTMLに<h1 th:text="${title}">タイトル</h1>があり、Modelのtitleに「ユーザー一覧」が入っていれば、この2つを使って最終的なHTMLを生成する。

結果は、以下となる。

<h1>ユーザー一覧</h1>

SpringとThymeleafを組み合わせる場合はSpringTemplateEngineが利用される。


ModelはJavaとHTMLの橋渡し

テンプレートを取得して処理できても、Java側で取得したデータを画面へ渡せなければ意味がない。

そこで使われるのがModel

Controllerでmodel.addAttribute("message", "ユーザー一覧");とすると、「message」という名前で「ユーザー一覧」という値を画面へ渡すことができる。

Thymeleafでは${message}と書くことで、その値を参照する。

Controller
    ↓
model.addAttribute("message", "ユーザー一覧")

Model
    ↓
message = ユーザー一覧

Thymeleaf
    ↓
${message}

HTML
    ↓
ユーザー一覧を表示

そのため、ThymeleafのHTMLを読んでいて${userName}${users}といった記述が出てきた場合は、Controllerなどで同じ名前の値をModelへ設定していないか確認すると処理を追いやすい。


基本的な実装例

ここまでの内容を使って、Controllerからユーザー一覧を渡して画面へ表示する。

今回やりたいことは以下となる。

/usersへアクセス
        ↓
Controllerでユーザー一覧を用意
        ↓
Modelへ入れる
        ↓
users.htmlを表示
        ↓
Thymeleafが人数分のHTMLを生成
        ↓
ブラウザへ表示

Controller

package com.example.demo.controller;

import java.util.List;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class UserController {

    @GetMapping("/users")
    public String showUsers(Model model) {

        model.addAttribute("title", "ユーザー一覧");

        List<String> users = List.of(
                "山田太郎",
                "佐藤花子",
                "鈴木一郎"
        );

        model.addAttribute("users", users);

        return "users";
    }
}

@GetMapping("/users")GET /usersへのアクセスを受け取る。

その後、画面タイトルとしてtitle、一覧表示するデータとしてusersをModelへ登録している。

最後のreturn "users";で、表示するView名を返している。


HTML

src/main/resources/templates/users.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">

<head>
    <meta charset="UTF-8">
    <title>ユーザー一覧</title>
</head>

<body>

<h1 th:text="${title}">タイトル</h1>

<ul>
    <li th:each="user : ${users}"
        th:text="${user}">
        ユーザー
    </li>
</ul>

</body>
</html>

今回使っているThymeleafの主な処理はth:textth:eachとなる。


th:textは何のために使うのか

th:textは、HTMLタグの中に表示する文字を、Java側から渡された値に置き換えたい場合に使う。

例えば普通のHTMLで<h1>ユーザー一覧</h1>と書けば「ユーザー一覧」と表示できる。

しかし、これではHTMLに文字が固定される。

画面によって「ユーザー一覧」「管理者ユーザー一覧」「検索結果」などタイトルを変えたい場合、そのたびにHTMLを書き換えることはできない。

そこで今回のHTMLでは<h1 th:text="${title}">タイトル</h1>としている。

Controllerではmodel.addAttribute("title", "ユーザー一覧");としているため、${title}から「ユーザー一覧」を取得する。

結果は、以下となる。

<h1>ユーザー一覧</h1>

th:textを使う理由は、

HTMLに表示内容を固定せず、Java側の処理結果によって表示する文字を変えられるようにするため。


${title}とは何か

th:text="${title}"${title}は、Modelに入っているtitleという値を参照している。

今回であればControllerで、

model.addAttribute("title", "ユーザー一覧");

としている。

このとき、以下のような状態になる。

Model

title
 └─ ユーザー一覧

そのためHTMLの${title}から「ユーザー一覧」を取得できる。

つまりThymeleafのHTMLを読んでいて${〇〇}を見つけた場合は、

「Java側のどこで〇〇という名前の値をModelへ登録しているのか」

を探すとよい。


th:eachは何のために使うのか

次にth:each

今回のControllerでは3人のユーザーをListとして用意している。

List<String> users = List.of(
        "山田太郎",
        "佐藤花子",
        "鈴木一郎"
);

3人しかいないことが最初から分かっているなら、HTMLへ3つの<li>を書けば表示できる。

しかし実際にDBから検索する場合、結果が何件になるかは実行するまで分からない。

検索結果が、

0件
3件
10件
100件

と変わる可能性がある。

この件数に合わせて毎回HTMLを書き換えることはできない。

そこでth:eachを利用する。

今回のth:each="user : ${users}"は、Modelに入っているusersからデータを1件ずつ取り出し、その1件をuserという名前で扱う、という意味になる。

Javaで考えると、以下の拡張for文に近い。

for (String user : users) {
    // userを使って処理する
}

今回のHTMLではth:eachで1件ずつ取り出し、同じタグに設定したth:text="${user}"でそのユーザー名を表示している。

3件入っていれば<li>も3回生成される。

結果は、以下となる。

ユーザー一覧

・山田太郎
・佐藤花子
・鈴木一郎

th:eachを使う理由は、

Listなどの件数が変わるデータに合わせて、HTMLを必要な回数だけ繰り返し生成するため。

DBの検索結果を一覧画面へ表示するような処理で利用できる。


th:textth:eachを組み合わせる

今回のHTMLでは以下のように2つを組み合わせている。

<li th:each="user : ${users}"
    th:text="${user}">
    ユーザー
</li>

それぞれの役割は、

属性 今回の役割
th:each usersの件数分だけ<li>を繰り返す
th:text その回に取り出したuserの名前を表示する

となる。

例えばusersが3件なら、イメージは以下となる。

1回目
user = 山田太郎
↓
<li>山田太郎</li>

2回目
user = 佐藤花子
↓
<li>佐藤花子</li>

3回目
user = 鈴木一郎
↓
<li>鈴木一郎</li>

つまり、

th:eachで「何件表示するか」を決め、th:textで「何を表示するか」を決めている

ということ。


処理をもう一度整理する

最後に、/usersへアクセスしてから画面へユーザー一覧が表示されるまでを整理する。

ブラウザ
│
│ GET /users
↓
UserController
│
│ showUsers()
↓
Java側でデータを用意
│
├─ title
│   └─ ユーザー一覧
│
└─ users
    ├─ 山田太郎
    ├─ 佐藤花子
    └─ 鈴木一郎
        ↓
Modelへ登録
        ↓
return "users"
        ↓
Viewを解決
        ↓
users.htmlを取得
        ↓
Thymeleafがテンプレートを処理
│
├─ ${title}
│    └─ ユーザー一覧
│
└─ ${users}
     └─ th:eachで3回繰り返す
          ↓
     th:textで1人ずつ名前を表示
        ↓
HTML完成
        ↓
ブラウザへ返却

結果は、以下となる。

ユーザー一覧

・山田太郎
・佐藤花子
・鈴木一郎

今回学習していて重要だと感じたのは、th:textth:eachという名前だけ覚えるのではなく、

「普通のHTMLだけだと何ができなくて、それを解決するために何を使っているのか」

までセットで理解すること。

やりたいこと 使用する仕組み
Javaで取得した値を画面へ渡したい Model
Java側の値をHTMLの文字として表示したい th:text
件数の変わる一覧を表示したい th:each
Controllerから表示する画面を指定したい View名
View名からViewを解決したい ViewResolver
実際のテンプレートを取得したい TemplateResolver
HTMLとModelの値を組み合わせたい TemplateEngine / Thymeleaf
1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?