他のフレームワークには用意されていたりする

テンプレートにおける表示用の加工処理は大体 Thymeleaf が標準で備えているが、例えば CakePHP における Helper だったり Django における独自のテンプレートタグの作成のようにテンプレートの機能だけでは賄いきれない、多くはプレゼンテーション層における HTML への変換のためのロジックを使いたい時がある。 こういう時に Spring Boot における Thymeleaf 上ではどうすればいいのだろうか、というのが今回のテーマである。

勿論 ControllerModel (Form) に対応する HTML タグへの変換処理を書いたりすることはできるが MVC において本来コントローラやモデルでプレゼンテーション層の処理を書くのは好ましくないので避けたいところだ。 いろいろ試行錯誤してみた結果、プレゼンテーション層のヘルパクラスをコンポーネントとして登録して Thymeleaf 側から呼び出すのが一番シンプルな気がした。

ヘルパークラス定義

今回は Markdown で書かれたテキストを HTML に変換したいとする。 以下のようなヘルパークラスを任意のパッケージに定義する:

/**
 * Thymeleaf テンプレート上で使用するヘルパ.
 */
@Component
class Helper {

    /**
     * Markdown を HTML に変換して返す.
     *
     * @param markdown Markdown
     * @return HTML に変換された Markdown
     */
    fun toHtml(markdown: String): String {
        val (parser, renderer) = Parser.builder().build() to HtmlRenderer.builder().build()
        return renderer.render(parser.parse(markdown))
    }
}

@Component アノテーションを付与することにより Spring 管理下のコンポーネントとして機能する。 これを Thymeleaf 上で使用するには以下のように ${@helper.toHtml(xxx)} といった @ を頭につけた記法となる:

<!-- HTML エスケープされないように th:utext を使用する -->
<div class="post-content" th:utext="${@helper.toHtml(post.markdown)}"></div>

後はこういう要件が出てくる度にこの Helper クラスにメソッドを追加していけばよい。

定数の参照はどうする

同じような悩みとして Thymeleaf 上から Kotlin の定数を参照したいというのがある。 一応 Thymeleaf 上で ${T(パッケージ.クラス名).static フィールド名} という記法で任意の static フィールドやメソッドを呼び出すことはできる。 ただ、例えば Kotlin で object を使用して static を表現したとする:

object Consts {
    val DAYS = arrayListOf('月', '火', '水', '木', '金', '土', '日')
}

上記の定数を Thymeleaf 側で参照するには ${T(com.kojion.etc.Consts).INSTANCE.DAYS} のようにしなければならない。 Kotlin の object が Java コード側から見るとシングルトンな INSTANCE という static フィールドを介してアクセスするようになっているので INSTANCE といちいち付けなければならず、あまり綺麗とは言えない。

この場合あえて Kotlin でなく Java で書いてみる:

public class Consts {
    public static final List<String> DAYS = Arrays.asList("月", "火", "水", "木", "金", "土", "日");
}

これで ${T(com.kojion.etc.Consts).DAYS} とアクセスできるので少しシンプルになった。 Thymeleaf 側からは Kotlin でなく Java として見なければならないのが少し辛いところだ。

パッケージ名を書くのも気になる場合は、先程のヘルパークラスと同様にコンポーネントとして登録して定数定義するのがいいのかもしれない:

@Component
object Consts {
    val DAYS = arrayListOf("月", "火", "水", "木", "金", "土", "日")
}

これで Thymeleaf 側から ${@consts.DAYS} でアクセスできるようになった。 object で定義しているので Kotlin 側から定数としてアクセスしたい場合も自然だ。