SpELで簡易テンプレートエンジンを作る！！

Springのコア機能の一つであるSpELを使って簡易的なテンプレートエンジンを作る方法を紹介します。
というのも・・・現在参画している案件にて、シンプルなテンプレート（分岐などは不要なテンプレート）に値を埋め込んだテキスト（通知メッセージ）を生成したいのですが、Thymeleaf/FreeMarker/Velocityといったテンプレートエンジンライブラリを使う必要もなさそうなので、SpELで実現できないかな〜と思って調べたら簡単に実現することができました。

# 検証バージョン

* Spring Framework 5.3.9
* Spring Boot 2.5.4

# 検証アプリケーション

* https://github.com/kazuki43zoo/spel-template-demo/tree/main

# テンプレートエンジンの作成

以下のように`SpelExpressionParser`を使用して、SpELを使ってテンプレート処理をするクラスを作成します。
デフォルトではテンプレート処理をサポートされていませんが、SpEL式を解析する際に`TemplateParserContext`を指定することでテンプレート処理を行うことができます。

```java:テンプレートエンジンの実装例
package com.example.demo;

import org.springframework.expression.Expression;
import org.springframework.expression.ParserContext;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.lang.reflect.Method;
import java.util.Map;
import java.util.stream.Stream;

@Component
public class SpelTemplateEngine {

  private final SpelExpressionParser parser = new SpelExpressionParser();
  private final ParserContext parserContext = new TemplateParserContext("${", "}");
  private final Method[] functions = StringUtils.class.getMethods(); 

  public String process(String template, Map<String, Object> variables) {
    StandardEvaluationContext context = new StandardEvaluationContext();
    context.setVariables(variables); // 変数の追加
    Stream.of(functions).forEach(x -> context.registerFunction(x.getName(), x)); // SpringのStringUtilsのメソッドをカスタム関数として追加

    Expression expression = parser.parseExpression(template, parserContext); // テンプレート(SpEL)の評価
    return expression.getValue(context, String.class); // テンプレート処理
  }

}
```

# テンプレートファイルの用意

以下のようなテンプレートファイルを用意しておきます。

```:テンプレートファイルの指定例
Hi ${#trimWhitespace(#user.name) ?: 'Taro'}.

Welcome developer team of ${#project.name ?: 'Demo'}!!
```

# テンプレート処理の実行

テンプレートエンジンとテンプレートファイルを使ってテンプレート処理を実行してみましょう。

```java:テンプレートエンジンの呼び出し例
package com.example.demo;

import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.core.io.ClassPathResource;
import org.springframework.util.StreamUtils;

import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

@SpringBootApplication
public class SpelTemplateDemoApplication {

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

  @Bean
  ApplicationRunner run(SpelTemplateEngine templateEngine) {
    return args -> {
      // テンプレートファイルの読み込み
      String template = StreamUtils.copyToString(new ClassPathResource("welcome-template.txt").getInputStream(), StandardCharsets.UTF_8);
      // 変数の生成
      Map<String, Object> variables = new HashMap<>();
      User user = new User();
      user.setName("Kazuki  ");
      Project project = new Project();
      project.setName("MyBatis");
      variables.put("user", user);
      variables.put("project", project);
      // テンプレート処理の実装
      String text = templateEngine.process(template, variables);
      // テンプレート処理後のテキストを標準出力
      System.out.println(text);
    };
  }

  static class User {
    private String name;
    public String getName() {
      return name;
    }
    public void setName(String name) {
      this.name = name;
    }
  }

  static class Project {
    private String name;
    public String getName() {
      return name;
    }
    public void setName(String name) {
      this.name = name;
    }
  }

}
```

# テンプレート処理後のテキスト

テンプレート処理を実行すると、テンプレートに変数値が埋め込まれたものが出力されることが確認できます。

```:テンプレート処理後のテキスト
Hi Kazuki.

Welcome developer team of MyBatis!!
```

```:テンプレート処理後のテキスト(変数値が未指定の場合)
Hi Taro.

Welcome developer team of Demo!!
```

# まとめ

簡単にテンプレート処理を実現することができました！！
Springで構築するアプリケーションにて、Thymeleaf/FreeMarker/Velocityといった本格的なテンプレートエンジンライブラリを入れるほどでもないけどちょっとしたテンプレートを使いたい場合は、SpELの利用を検討してみてもよいかと思います。

# 参考サイト

* https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#expressions