thymeleaf + SpringでJavaの定数やEnum、Staticメソッドを使いたくなったときの対処法

# はじめに

以前に書いた記事の続編です。

https://qiita.com/n_slender/items/d0e2a861dcc806b5882e 

「thymeleaf enum」「thymeleaf constant」で検索すると、 `T(my.package.MyEnum).MONDAY`, `${T(com.baeldung.thymeleaf.model.Color).values()}`, `T(com.example.Constants).MIN_VALUE` といった、thymeleafから直接呼び出すような形の記事がでてくることが多いです。

https://stackoverflow.com/questions/24937441/comparing-the-enum-constants-in-thymeleaf

https://stackoverflow.com/questions/68686397/how-to-use-variables-from-a-constants-class-in-thymeleaf-numbers-sequence-meth

https://www.baeldung.com/thymeleaf-enums

https://springhow.com/enums-in-thymeleaf/

では、この使い方が公式ドキュメントにあるものかというと、そうではないです。
`T(` で検索をかけても、上記の使い方に対応するものはヒットしないです。

https://www.thymeleaf.org/doc/tutorials/3.1/usingthymeleaf.html

https://www.thymeleaf.org/doc/tutorials/3.1/thymeleafspring.html

この書き方は、特に考えずにすぐ動くものができるので、一見良さそうですが、保守性や可読性からみると良いといえないです。このやり方に頼らない、別の方法をまとめるのがこの記事の趣旨です。



## 結論 

* controllerのメソッド内でmodelAndViewにわたす
* controllerでModelAttributeを定義する
* Beanに登録する
* インスタンスメソッドに委譲し、テンプレートでそれを呼び出す

# T(com.example.Constants)のやり方の問題点

このやり方は、パッケージ構成やクラス名の変更が、テンプレート側にも影響してしまいます。
IDEのリファクタリング機能では、テンプレートの方までは変えてくれないので、安全にリファクタリングできなくなってしまいます。
テンプレートエンジンの基本は、「テンプレート内の変数に代入」です。テンプレートから直接アプリのコードを呼び出すのは、アプリとテンプレートを結合度を不要に高めてしまいます。

# サンプルコード

```java:UserStatus.java
public enum UserStatus {
    ACTIVE("有効"),
    INACTIVE("無効"),
    PENDING("保留");

    private final String viewName;

    ... 
}

```

```java:UserConstants.java

public class UserConstants {

  public static final int MAX_PAGE_SIZE = 100;

  public static final int MIN_PAGE_SIZE = 10;

}
```

# controllerのメソッド内でmodelAndViewにわたす

一番オーソドックスなやり方です。

enumの要素をfor-eachする場合は、Enum#values()をModelに渡します。
定数についても同様です。

```java:UserController
@Controller
public class UserController {

   // 更新画面の表示
   // ステータスのセレクトボックスを表示
   public String updateView(ModelAndView model, UserUpdateForm form) {
        ...
        model.addAttribute("statusList", UserStatus.values());
        model.addAttribute("maxPageSize", UserConstants.MAX_PAGE_SIZE);
        ... 
   }

}

```

```html:user_edit.html
<select name="status">
    <option th:each="status : statusList}" 
            th:value="${status}" th:text="${status.viewName}">
    </option>
</select>

<div th:text=${maxPageSize}>
```


# controllerでModelAttributeを定義する

ひとつのcontroller内で扱う複数の画面で、使われる場合です。
`@ModelAttribute`は、controller内の各メソッドが呼び出される前に実行されます。
テンプレートでの使い方は通常どおりです。

```java:UserController.java
@Controller
public class UserController {

  @ModelAttribute("activeStatus")
  public UserStatus active() {
       return UserStatus.ACTIVE;
  }

  ...

}
```

```html:user_create.html
<input name="status" th:value="${activeStatus}" th:text="${activeStatus.viewName}">
```

# Beanに登録する

複数のcontrollerが扱う画面で使う場合です。それぞれのcontrollerで`@ModelAttribute`を使う手もありますが、`@Bean` を使えば一箇所ですみます。
テンプレートでの使い方も、通常どおりです。

```java:Configs.java
@Configuration
public class Configs {

  @Bean("userActiveStatus")
  public UserStatus activeStatus() {
      return UserStatus.ACTIVE;
  }
}
```

```html:view.html
<input name="status" th:value="${userActiveStatus}" th:text="${userActiveStatus.viewName}">
```

# インスタンスメソッドに委譲し、テンプレートでそれを呼び出す

冒頭にあげた、よくないやり方をつかえば、テンプレートからstaticメソッドを呼び出すこともできます。

```java:User.java
public class User {

  private final String name;

  ...
}
```

```java:UserUtil.java
public class UserUtil {

  public static String formatName(String name) {...}

}
```

```html
<p th:text="${T(com.sample.UserUtil.formatName(user.name))}">
```

こういった場合は、インスタンスメソッドに移せば簡単に解決します。

```java:User.java
public class User {
 
   private final String name;

   ...

   public final String getFormatedName() {
       // UserUtil#formatNameの中身を移す
       ...
   }
}
```

このように書き直せます。

```html
<p th:text="${user.formatedName}">
```

# まとめ

ModelAttribute,Beanや、controllerとテンプレートのやりとりの部分は、Springの知識が必要ですが、それ以外は、テンプレートエンジンの一般的な使い方と、thymeleafでの基本的な記法でできます。
上手にthymeleafと付き合っていきましょう。