Spring Boot で Mustache テンプレートエンジンを使うサンプルコード

## 概要

- Spring Boot で Mustache テンプレートエンジンを使う
- 今回の動作確認環境: Java 14 (AdoptOpenJDK 14.0.2+12) + Spring Boot 2.3.2 + Gradle 6.5.1 + macOS Catalina

## サンプルコード

### ソースコード一覧

```
├── build.gradle
└── src
    └── main
        ├── java
        │   └── com
        │       └── example
        │           ├── SampleController.java
        │           └── SampleData.java
        └── resources
            ├── application.properties
            └── templates
                ├── error
                │   ├── 4xx.html
                │   └── 5xx.html
                └── my_template.html
```

### build.gradle

Spring Boot Starter Mustache を導入する。

```groovy
plugins {
  id 'org.springframework.boot' version '2.3.2.RELEASE'
  id 'io.spring.dependency-management' version '1.0.9.RELEASE'
  id 'java'
}

group = 'com.example'
version = '0.0.1'
sourceCompatibility = '14'

repositories {
  mavenCentral()
}

dependencies {
  implementation 'org.springframework.boot:spring-boot-starter-mustache'
  implementation 'org.springframework.boot:spring-boot-starter-web'
}
```

[spring-boot-starter-mustache-2.3.2.RELEASE.pom](https://repo1.maven.org/maven2/org/springframework/boot/spring-boot-starter-mustache/2.3.2.RELEASE/spring-boot-starter-mustache-2.3.2.RELEASE.pom) の中身を見ると Spring Boot Starter Mustache が Mustache ライブラリの JMustache 1.15 を利用していることがわかる。

### src/main/resources/application.properties

Mustache に関する設定を行う。
ここでは拡張子が html のファイルをテンプレートファイルとして扱うように spring.mustache.suffix を設定する。

```properties
# テンプレートのキャッシュを有効にするかどうか (デフォルト値: false)
spring.mustache.cache=false

# テンプレートのエンコード (デフォルト値: UTF-8)
spring.mustache.charset=UTF-8

# テンプレート名に適用するプレフィックス (デフォルト値: classpath:/templates/)
spring.mustache.prefix=classpath:/templates/

# テンプレート名に適用するサフィックス (デフォルト値: .mustache)
spring.mustache.suffix=.html
```

他にも設定項目があるので必要に応じて [Spring Boot アプリケーションプロパティ一覧 \- ドキュメント](https://spring.pleiades.io/spring-boot/docs/current/reference/html/appendix-application-properties.html) などを参照すると良い。

### src/main/java/com/example/SampleController.java

コントローラークラス。

```java
package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.servlet.ModelAndView;

@SpringBootApplication
@Controller
public class SampleController {

  public static void main(String[] args) {
    SpringApplication.run(SampleController.class, args);
  }

  @GetMapping("/mypage")
  public ModelAndView mypage() {
    SampleData sampleData = new SampleData();
    ModelAndView mav = new ModelAndView();
    mav.setViewName("my_template"); // テンプレートファイル名
    mav.addObject("mydata", sampleData); // データオブジェクトをセット
    return mav;
  }

  @GetMapping("/myerror")
  public ModelAndView myerror() {
    throw new RuntimeException("エラーを発生させる");
  }
}
```

### src/main/java/com/example/SampleData.java

Mustache テンプレートに埋め込むデータオブジェクト。

```java
package com.example;

import java.util.List;
import java.util.Map;

public class SampleData {

  public String foo = "foo";

  public String getBar() {
    return "bar";
  }

  public String[] strings = {"S1", "S2", "S3"};

  public List list = List.of("L1", "L2", "L3");

  public Map map = Map.of("key1", "value1", "key2", "value2", "key3", "value3");
}
```

### src/main/resources/templates/my_template.html

データオブジェクトの内容を表示するための Mustache テンプレートファイル。

```html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{{#mydata}}
foo: {{foo}}<br>
getBar(): {{bar}}<br>
{{/mydata}}
<br>
strings:<br>
{{#mydata.strings}}
value: {{.}}<br>
{{/mydata.strings}}
<br>
list:<br>
{{#mydata.list}}
value: {{.}}<br>
{{/mydata.list}}
<br>
map:<br>
{{#mydata.map}}
{{key1}}, {{key2}}, {{key3}}
{{/mydata.map}}
</body>
</html>
```

### src/main/resources/templates/error/4xx.html

4xx 系エラー発生時のための Mustache テンプレートファイル。
必要に応じてエラー情報等のデータを埋め込むことができる。

```html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>4xx</title>
</head>
<body>
<h1>4xx</h1>
<div>timestamp: {{#timestamp}}{{.}}{{/timestamp}}</div>
<div>status: {{#status}}{{.}}{{/status}}</div>
<div>error: {{#error}}{{.}}{{/error}}</div>
<div>exception: {{#exception}}{{.}}{{/exception}}</div>
<div>message: {{#message}}{{.}}{{/message}}</div>
<div>errors: {{#errors}}{{.}}{{/errors}}</div>
<div>trace: {{#trace}}{{.}}{{/trace}}</div>
<div>path: {{#path}}{{.}}{{/path}}</div>
</body>
</html>
```

### src/main/resources/templates/error/5xx.html

５xx 系エラー発生時のための Mustache テンプレートファイル。
必要に応じてエラー情報等のデータを埋め込むことができる。

```html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>5xx</title>
</head>
<body>
<h1>5xx</h1>
<div>timestamp: {{#timestamp}}{{.}}{{/timestamp}}</div>
<div>status: {{#status}}{{.}}{{/status}}</div>
<div>error: {{#error}}{{.}}{{/error}}</div>
<div>exception: {{#exception}}{{.}}{{/exception}}</div>
<div>message: {{#message}}{{.}}{{/message}}</div>
<div>errors: {{#errors}}{{.}}{{/errors}}</div>
<div>trace: {{#trace}}{{.}}{{/trace}}</div>
<div>path: {{#path}}{{.}}{{/path}}</div>
</body>
</html>
```

## 出力結果例

curl でアクセスしてレスポンス結果を確認する。

### 正常レスポンスのとき

データオブジェクトが Mustache テンプレートに埋め込まれて出力されている。

```
$ curl http://localhost:8080/mypage
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
foo: foo<br>
getBar(): bar<br>
<br>
strings:<br>
value: S1<br>
value: S2<br>
value: S3<br>
<br>
list:<br>
value: L1<br>
value: L2<br>
value: L3<br>
<br>
map:<br>
value1, value2, value3
</body>
</html>
```

### 404 Not Found のとき

4xx 系エラー発生時のための Mustache テンプレートファイルにエラー情報が埋め込まれて出力されている。

```
$ curl --include -H "accept: text/html" http://localhost:8080/
HTTP/1.1 404 
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Content-Type: text/html;charset=UTF-8
Content-Language: ja-JP
Content-Length: 321
Date: Fri, 07 Aug 2020 07:55:53 GMT

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>4xx</title>
</head>
<body>
<h1>4xx</h1>
<div>timestamp: Fri Aug 07 16:55:53 JST 2020</div>
<div>status: 404</div>
<div>error: Not Found</div>
<div>exception: </div>
<div>message: </div>
<div>errors: </div>
<div>trace: </div>
<div>path: /</div>
</body>
</html>
```

### エラー発生時

５xx 系エラー発生時のための Mustache テンプレートファイルにエラー情報が埋め込まれて出力されている。

```
$ curl --include -H "accept: text/html" http://localhost:8080/myerror
HTTP/1.1 500 
Content-Type: text/html;charset=UTF-8
Content-Language: ja-JP
Content-Length: 340
Date: Fri, 07 Aug 2020 07:55:47 GMT
Connection: close

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>5xx</title>
</head>
<body>
<h1>5xx</h1>
<div>timestamp: Fri Aug 07 16:55:47 JST 2020</div>
<div>status: 500</div>
<div>error: Internal Server Error</div>
<div>exception: </div>
<div>message: </div>
<div>errors: </div>
<div>trace: </div>
<div>path: /myerror</div>
</body>
</html>
```

## 参考資料

[Spring Boot 「使い方」ガイド \- 公式ドキュメントの日本語訳](https://spring.pleiades.io/spring-boot/docs/current/reference/html/howto.html)

> Mustache を使用する場合、「mustacheViewResolver」という名前の MustacheViewResolver もあります。ビュー名をプレフィックスとサフィックスで囲むことにより、リソースを探します。プレフィックスは spring.mustache.prefix で、サフィックスは spring.mustache.suffix です。接頭辞と接尾辞の値は、デフォルトでそれぞれ「classpath:/templates/」と「.mustache」になります。同じ名前の Bean を提供することにより、MustacheViewResolver をオーバーライドできます。

- [Spring Boot の機能 \- 公式ドキュメントの日本語訳](https://spring.pleiades.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-developing-web-applications)
- [Spring Boot アプリケーションプロパティ一覧 \- ドキュメント](https://spring.pleiades.io/spring-boot/docs/current/reference/html/appendix-application-properties.html)
- [spring\-boot/MustacheView\.java at v2\.3\.2\.RELEASE · spring\-projects/spring\-boot · GitHub](https://github.com/spring-projects/spring-boot/blob/v2.3.2.RELEASE/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/view/MustacheView.java)
- [spring\-boot/MustacheViewResolver\.java at v2\.3\.2\.RELEASE · spring\-projects/spring\-boot · GitHub](https://github.com/spring-projects/spring-boot/blob/v2.3.2.RELEASE/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/view/MustacheViewResolver.java)
- [spring\-boot/spring\-boot\-project/spring\-boot\-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mustache at v2\.3\.2\.RELEASE · spring\-projects/spring\-boot · GitHub](https://github.com/spring-projects/spring-boot/tree/v2.3.2.RELEASE/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mustache)
- [7.2.6. エラー処理 \- Spring Boot の機能 \- 公式ドキュメントの日本語訳](https://spring.pleiades.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-webflux-error-handling)
- [DefaultErrorAttributes \(Spring Boot 2\.3\.2\.RELEASE API\)](https://docs.spring.io/spring-boot/docs/2.3.2.RELEASE/api/org/springframework/boot/web/servlet/error/DefaultErrorAttributes.html)
- [Guide to Mustache with Spring Boot \| Baeldung](https://www.baeldung.com/spring-boot-mustache)