今年になってSpring bootを使う案件に関わることがあり、thymeleafやenumについての得られた知見を整理してみました。
「Thymeleaf enum」でググるとよくでてくるもの
SpringやSpring bootを使うと、テンプレートエンジンでThymeleafを使うのが通例でしょう。
入力フォームのセレクトボックスの値や表示名を、enumを使って管理する、というのも、よくあることでしょう。
https://www.baeldung.com/thymeleaf-enums より抜粋ですが、ググると下記のような例がそこそこ見かけます。
<select name="color">
<option th:each="colorOpt : ${T(com.baeldung.thymeleaf.model.Color).values()}"
th:value="${colorOpt}" th:text="${colorOpt.displayValue}"></option>
</select>
初期値の判定などで、特定のenumかどうかを判定する場合の例として、こういうのもあります。
<div th:if="${widget.color == T(com.baeldung.thymeleaf.model.Color).RED}">
このやり方で難がある点
よく見かけるのですが、これがベストかと言われると、なんともいえないです。
- 型のFQDNの部分が読みづらい (長い割に情報量がない)
- パッケージ構成を見直そうとすると、テンプレートまで改修しなければならない
- enumのリネームでも、テンプレートまで改修しなければならない
要は、テンプレートとは関係ないものが入ってくるため、可読性や保守性に悪影響がでています。
とりあえず動くものを作るなら、このやり方で問題ないですが、少々つらいです。
どうするとよいか
基本的には、テンプレートで使うものをenumに実装するのがよいです。
また、テンプレートから直接呼び出すよりも、必要なインスタンスをControllerから渡す形に変えるとよいです。
enumの全要素を使う場合(セレクトボックスなど)
Javaのenumには、暗黙的なpublic static T[] values()
というメソッドがあります。
全要素を列挙するには、これを利用するといいです。(定番といえば、定番ネタです。)
public enum UserStatus {
ACTIVE(1, "有効"),
INACTIVE(0, "無効"),
PENDING(2, "保留"),
FORBIDDEN(3, "強制停止")
;
private final int value;
private final String viewName;
private UserStatus(int value, String viewName) {
this.value = value;
this.viewName = viewName;
}
public int getValue() {
return this.value;
}
public String getViewName() {
return this.viewName;
}
}
@Controller
public class SampleController
@RequestGetMapping
public ModelAndView index() {
ModelAndView model = new ModelAndView();
model.setAttribute("statusList", UserStatus.values());
return model;
}
<select name="status">
<option th:each="status : statusList}"
th:value="${status.value}" th:text="${status.viewName}">
</option>
</select>
フォーム入力されたものをenumとして受け取る
上のやり方では、enumとして値を受け取るには不便です。
enumには name()
とvalueOf(String name)
という、暗黙的に宣言されたメソッドがあります。
name()
につけた名前が返却され、valueOf(String name)
は、名前が一致したら対応するインスタンスを返すものです。Springがこれらを使って対応づけしてくれます。
<select name="status">
<option th:each="status : statusList}"
th:value="${status.name()}" th:text="${status.viewName}">
</option>
</select>
public class NewUserForm {
private UserStatus status;
// ...
}
@RequestPutMapping
public ModelAndView create(NewUserForm form) {
ModelAndView model = new ModelAndView();
// ...
return model;
}
とすると、フォームで選択されたものを、enumにマッピングして受け取ることができます。
enumのまま渡しても、暗黙のうちに、name()が使われるので、下記のように書けます。
<option th:each="status : statusList}"
th:value="${status}" th:text="${status.viewName}">
</option>
特定のenumの判定
これはenumにisXXXというメソッドを足すやり方があります。
public enum UserStatus {
// ...
public boolean isActive() {
return this == ACTIVE;
}
public boolean isForbidden() {
return this == FORBIDDEN;
}
}
<div th:if="${status.isForbidden()}">
This user is forbidden.
</div>
このisXXXメソッドは、テンプレートだけでなく、ビジネスロジックを書く場面でも有効です。
たとえば、有効なユーザだけに絞り込むなら、こんな風になります。
public class User {
//...
private final UserStatus status;
public UserStatus getStatus() {
return this.status
}
}
List<User> users = ...
List<User> activeUsers = users.stream()
.filter(x -> x.getStatus().isActive())
.collect(Collectors.toList());
まとめ
ぐぐって、ベストプラクティスが見つかることもあれば、そうでないこともあります。
また、検索して見つかった方法が自分の現状にあっているか、その判断基準も検索してみつかることもあまりないです。
テンプレートはいろんな関心事が入りやすいので、実装するときにはいろんな判断に迫られます。
参考リンク