Java
Thymeleaf
spring-boot

SpringBootからThymeleafのテキストテンプレートモードを使う

はじめに

Thymeleafは本来Webページ(HTML)用のテンプレートエンジンで、HTMLの出力に特化したものでしたが、Thymeleaf3系からは、プレーンテキストも出力できます。これによりメールやチャットへの投稿用テンプレートにも使えます。

前準備

本記事ではmavenを使った最小限のSpringBootで、テキストを標準出力するものを用意します。SpringBoot2系と1系とで、依存関係にあるライブラリが異なります。プロジェクト作成の際には、SpringToolsSuiteないしはmavenが利用できるIDEをお使いください。pom.xmlは記事の最後に紹介しています。

テキストモードの設定

テキストモードでThymeleafを動かすための設定を、Spring経由で行います。
Thymeleafのテンプレートは TemplateEngine インタフェースを継承したクラスから扱います。今回はSpringBootから扱うので、Spring用の SpringTemplateEngine を用います。

ThymeleafConfig.java
package com.github.apz.springsample.config;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.core.env.Environment;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.spring5.SpringTemplateEngine;    // SpringBoot1系は、import org.thymeleaf.spring4.SpringTemplateEngine;
import org.thymeleaf.templatemode.TemplateMode;
import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver;
import org.thymeleaf.templateresolver.ITemplateResolver;

@Configuration
public class ThymeleafConfig implements ApplicationContextAware, EnvironmentAware {
    @Bean
    public TemplateEngine textTemplateEngine() {
        final SpringTemplateEngine templateEngine = new SpringTemplateEngine();
        templateEngine.addTemplateResolver(textTemplateResolver());
        templateEngine.setTemplateEngineMessageSource(textMessageSource());
        return templateEngine;
    }

    private ITemplateResolver textTemplateResolver() {
        final ClassLoaderTemplateResolver templateResolver = new ClassLoaderTemplateResolver();
        templateResolver.setOrder(Integer.valueOf(1));
        templateResolver.setPrefix("/mail/");
        templateResolver.setSuffix(".txt");
        templateResolver.setTemplateMode(TemplateMode.TEXT);
        templateResolver.setCharacterEncoding("utf-8");
        templateResolver.setCacheable(false);
        return templateResolver;
    }

    @Bean
    public ResourceBundleMessageSource textMessageSource() {
        final ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
        messageSource.setBasename("mail/Messages");
        return messageSource;
    }

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    private ApplicationContext applicationContext;
    private Environment environment;
}

TemplateEngineを動かすには、テンプレートファイルの読み込み設定を司る TemplateResolver が必須です。さらにメッセージ定義プロパティからメッセージを出力できるよう、Springで扱っているMessageSource も渡せます。

このクラスの記述により、Springは textTemplateEngine の名前で参照でき、今回定義したTemplateResolverの内容でテキストテンプレートを使います。TemplateResolverでは特に以下の設定に注視してください。

// テンプレートファイルの配置ディレクトリ(src/main/resources/templates/ 以下のディレクトリ)
templateResolver.setPrefix("/mail/");

// テンプレートファイルの拡張子。ここに設定された値以外は読み込まなくなる
templateResolver.setSuffix(".txt");

// Thymeleafのテンプレートモード。テキスト出力なので、TEXTを指定
templateResolver.setTemplateMode(TemplateMode.TEXT);

// 読み込むテンプレートファイルの文字エンコード
templateResolver.setCharacterEncoding("utf-8");

実際に出力する

設定が完了したので、次は出力するクラスです。

package com.github.apz.springsample.component;

import java.util.Arrays;
import java.util.Date;
import java.util.Locale;

import org.springframework.stereotype.Component;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;

import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;

@Component
@RequiredArgsConstructor  // ※
@Log4j2                   // ※
public class ThymeleafText {
    private final TemplateEngine templateEngine;

    public void process() {
        final Context ctx = new Context(Locale.getDefault());
        ctx.setVariable("subscriptionDate", new Date());        
        String text = this.templateEngine.process("/mail/sample.txt", ctx);

        log.info(text);
    }
}

※RequiredArgsConstructorとLog4j2はproject Lombokのアノテーションです。

Thymeleafテンプレートエンジンから出力するには、以下のステップで完了です。

  1. ThymeleafのContextインスタンスを作り
  2. Thymeleafのテンプレート内で使う変数をContextに入れ
  3. テンプレートエンジンに、読み込むテンプレートとコンテキストを渡して processを実行するだけです

起動クラス

SpringBootの起動クラスを作って、動作をしてみます。

package com.github.apz.springsample;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;

import com.github.apz.springsample.component.ThymeleafText;

@SpringBootApplication
public class SpringThymeleaf3TextApplication implements CommandLineRunner {

    @Autowired
    private ThymeleafText thymeleafText;

    public static void main(String[] args) {
        new SpringApplicationBuilder(SpringThymeleaf3TextApplication.class).web(false).run(args);
    }

    @Override
    public void run(String... args) throws Exception {
        thymeleafText.process();
    }
}

テンプレートファイル

これはThymeleafテキストサンプルです。
[# th:if=${display}]======
  displayがtrueのときは[# th:utext="${#dates.format(subscriptionDate, 'dd/MMM/yyyy HH:mm')}" /]表示されます。
======
[/]末尾です。

テキストモードのThymeleafは、[# th:属性名] をタグとして扱います。それ以外はThymeleafの標準機能がごく一部を除きそのまま使えます。

実行結果は次のようになるでしょう。(Log4J2の出力は省略しています)

これはThymeleafテキストサンプルです。
======
  displayがtrueのときは11/7/2018 07:32表示されます。
======
末尾です。

以上です(・ω・)

Appendix

参考文献

Tutorial: Using Thymeleaf (ja)
Tutorial: Thymeleaf + Spring

サンプルコード

https://github.com/A-pZ/spring-thymelaf3-text

pom.xml (SpringBoot2系)

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.github.a-pz.sample</groupId>
    <artifactId>spring-thymeleaf3-text</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>spring-thymeleaf3-text</name>
    <description>spring-thymeleaf-text-sample</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.11.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
            <version>2.11.0</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

pom.xml (SpringBoot1系)

SpringBoot1系は、デフォルトはThymeleaf2系になってしまうので、3系を指定し、さらに必要なプラグインを追加します。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.github.a-pz.sample</groupId>
    <artifactId>spring-thymeleaf3-text</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>spring-thymeleaf3-text</name>
    <description>spring-thymeleaf-text-sample</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.14.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.11.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
            <version>2.11.0</version>
        </dependency>
        <dependency>
            <groupId>org.thymeleaf</groupId>
            <artifactId>thymeleaf</artifactId>
            <version>3.0.9.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.thymeleaf</groupId>
            <artifactId>thymeleaf-spring4</artifactId>
            <version>3.0.9.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>nz.net.ultraq.thymeleaf</groupId>
            <artifactId>thymeleaf-layout-dialect</artifactId>
            <version>2.3.0</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>