|概要・6つのテンプレートモード・処理フロー・基本実装
Spring Bootを使った画面開発を理解するために、Thymeleafについて学習した内容を整理する。
この記事ではまず初歩の初歩として、以下についてまとめる。
- Thymeleafとは何か
- Thymeleafを使う目的
- 6つのテンプレートモード
- Spring Boot + Thymeleafのテンプレート処理フロー
- 基本的な実装例
そもそもThymeleafとは?
Thymeleafは、Javaで利用できるサーバーサイドのテンプレートエンジンのこと。
Spring Bootと組み合わせることで、Java側で取得したデータをHTMLへ反映し、ユーザーや処理結果によって内容が変わるWeb画面を作ることができる。
ただ、最初は「テンプレートエンジン」と言われても何をするものなのか分からなかった。
例えば、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:textやth:eachといったHTML属性を追加して処理を書くため、元のHTMLの形を大きく崩さずに動的な処理を追加できる。
6つのテンプレートモード
ThymeleafというとHTMLを処理するものというイメージがあったが、実際にはHTML以外もテンプレートとして扱える。
Thymeleafには以下の6つのテンプレートモードが用意されている。
| テンプレートモード | 主な用途 |
|---|---|
| HTML | Web画面などのHTML |
| XML | XMLファイル |
| TEXT | メール本文やテキストファイル |
| JAVASCRIPT | JavaScript |
| CSS | CSS |
| RAW | 加工せずそのまま出力 |
なぜ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:textやth:eachへ反映し、完成したHTMLをブラウザへ返す。
ViewとViewResolver
以下のControllerを例にする。
@GetMapping("/users")
public String showUsers(Model model) {
model.addAttribute("message", "ユーザー一覧");
return "users";
}
最初に疑問だったのが、最後のreturn "users";。
なぜusersという文字列をreturnするだけで、users.htmlが表示されるのか分からなかった。
ここで出てくるのがViewとViewResolver。
Controllerが返しているusersはHTMLそのものではなく、論理的なView名として扱われる。
ViewResolverは、そのView名から実際に利用するViewを解決する。
今回の例では大まかに、
Controller
↓
return "users"
↓
ViewResolver
↓
usersというViewを解決
という関係になる。
Spring Boot + Thymeleafでは、このSpring MVCの仕組みとThymeleafが連携して画面を表示している。
TemplateResolverとTemplateEngine
Thymeleaf側では、テンプレートを見つける処理と、テンプレートの中身を処理する役割も分かれている。
そこで出てくるのが、TemplateResolverとTemplateEngine。
最初は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:textやth: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:textとth: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:textとth: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:textやth:eachという名前だけ覚えるのではなく、
「普通のHTMLだけだと何ができなくて、それを解決するために何を使っているのか」
までセットで理解すること。
| やりたいこと | 使用する仕組み |
|---|---|
| Javaで取得した値を画面へ渡したい | Model |
| Java側の値をHTMLの文字として表示したい | th:text |
| 件数の変わる一覧を表示したい | th:each |
| Controllerから表示する画面を指定したい | View名 |
| View名からViewを解決したい | ViewResolver |
| 実際のテンプレートを取得したい | TemplateResolver |
| HTMLとModelの値を組み合わせたい | TemplateEngine / Thymeleaf |