1. 概述

Thymeleaf 是一款用于处理和生成 HTML、XML、JavaScript、CSS 以及纯文本的 Java 模板引擎。

在本篇文章中,我们将重点介绍 如何将 Thymeleaf 集成到 Spring 应用中,并探讨它在 Spring MVC 视图层的一些典型使用场景。

Thymeleaf 具有极强的可扩展性,同时天然支持模板原型化开发,这意味着即使没有后端服务也可以直接预览模板内容。相比 JSP 等传统模板技术,这种特性大大加快了前端开发效率。

2. 在 Spring 中集成 Thymeleaf

首先来看一下与 Spring 集成所需的配置项。要实现集成,需要引入 thymeleaf-spring 相关依赖。

我们先在 Maven 的 POM 文件中添加如下依赖:

<dependency>
    <groupId>org.thymeleaf</groupId>
    <artifactId>thymeleaf</artifactId>
    <version>3.1.2.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.thymeleaf</groupId>
    <artifactId>thymeleaf-spring5</artifactId>
    <version>3.1.2.RELEASE</version>
</dependency>

⚠️ 注意:如果是 Spring 4 项目,请使用 thymeleaf-spring4 依赖而不是 thymeleaf-spring5

核心类是 SpringTemplateEngine,它负责完成所有的模板引擎初始化工作。

我们可以在 Java 配置文件中将其注册为一个 Bean:

@Bean
@Description("Thymeleaf Template Resolver")
public ServletContextTemplateResolver templateResolver() {
    ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver();
    templateResolver.setPrefix("/WEB-INF/views/");
    templateResolver.setSuffix(".html");
    templateResolver.setTemplateMode("HTML5");

    return templateResolver;
}

@Bean
@Description("Thymeleaf Template Engine")
public SpringTemplateEngine templateEngine() {
    SpringTemplateEngine templateEngine = new SpringTemplateEngine();
    templateEngine.setTemplateResolver(templateResolver());
    templateEngine.setTemplateEngineMessageSource(messageSource());
    return templateEngine;
}

其中,templateResolverprefixsuffix 属性分别定义了视图页面的路径前缀和文件扩展名。

接下来是视图解析器(ViewResolver)部分。Spring MVC 中的 ViewResolver 负责把控制器返回的逻辑视图名映射为实际的视图对象。Thymeleaf 提供了 ThymeleafViewResolver 来实现这一功能。

最后一步是将 ThymeleafViewResolver 注册为 Bean:

@Bean
@Description("Thymeleaf View Resolver")
public ThymeleafViewResolver viewResolver() {
    ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
    viewResolver.setTemplateEngine(templateEngine());
    viewResolver.setOrder(1);
    return viewResolver;
}

✅ 至此,Thymeleaf 已成功集成进 Spring 项目中。

3. 在 Spring Boot 中使用 Thymeleaf

Spring Boot 对 Thymeleaf 提供了自动配置支持。只需添加以下 starter 依赖即可:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
    <version>2.3.3.RELEASE</version>
</dependency>

无需额外配置,默认情况下 HTML 模板应放在 resources/templates 目录下即可被识别。

4. 显示来自消息源(属性文件)的内容

可以使用 th:text="#{key}" 标签属性来展示来自 .properties 文件中的内容。

为此,我们需要先将属性文件注册为 messageSource Bean:

@Bean
@Description("Spring Message Resolver")
public ResourceBundleMessageSource messageSource() {
    ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
    messageSource.setBasename("messages");
    return messageSource;
}

然后在 HTML 模板中引用该 key:

<span th:text="#{welcome.message}" />

5. 显示模型属性

5.1. 简单属性

使用 th:text="${attributename}" 可以直接显示模型中的简单属性值。

比如在 Controller 中设置一个属性:

model.addAttribute("serverTime", dateFormat.format(new Date()));

在模板中就可以这样展示:

Current time is <span th:text="${serverTime}" />

5.2. 集合属性

如果模型属性是一个集合对象,可以用 th:each 进行遍历。

定义一个 Student 实体类:

public class Student implements Serializable {
    private Integer id;
    private String name;
    // standard getters and setters
}

Controller 中添加集合数据:

List<Student> students = new ArrayList<Student>();
// logic to build student data
model.addAttribute("students", students);

模板中遍历并显示:

<tbody>
    <tr th:each="student: ${students}">
        <td th:text="${student.id}" />
        <td th:text="${student.name}" />
    </tr>
</tbody>

6. 条件判断语句

6.1. if 与 unless

使用 th:if="${condition}" 来控制元素是否渲染,而 th:unless="${condition}" 则相反。

例如,在 Student 类中新增 gender 字段:

public class Student implements Serializable {
    private Integer id;
    private String name;
    private Character gender;
    
    // standard getters and setters
}

假设该字段只允许 M/F 值,我们可以用如下方式显示性别:

<td>
    <span th:if="${student.gender} == 'M'" th:text="Male" /> 
    <span th:unless="${student.gender} == 'M'" th:text="Female" />
</td>

6.2. switch 与 case

更优雅的方式是使用 th:switchth:case 实现条件分支:

<td th:switch="${student.gender}">
    <span th:case="'M'" th:text="Male" /> 
    <span th:case="'F'" th:text="Female" />
</td>

7. 处理用户输入

处理表单提交时,可使用 th:action="@{url}"th:object="${object}"

  • th:action:指定表单提交的目标 URL;
  • th:object:绑定表单数据的对象;
  • th:field="*{name}":绑定具体字段。

以 Student 类为例创建一个表单:

<form action="#" th:action="@{/saveStudent}" th:object="${student}" method="post">
    <table border="1">
        <tr>
            <td><label th:text="#{msg.id}" /></td>
            <td><input type="number" th:field="*{id}" /></td>
        </tr>
        <tr>
            <td><label th:text="#{msg.name}" /></td>
            <td><input type="text" th:field="*{name}" /></td>
        </tr>
        <tr>
            <td><input type="submit" value="Submit" /></td>
        </tr>
    </table>
</form>

对应的 Controller 方法:

@RequestMapping(value = "/saveStudent", method = RequestMethod.POST)
public String saveStudent(Model model, @ModelAttribute("student") Student student) {
    // logic to process input data
}
  • @RequestMapping 将 URL 映射到方法;
  • @ModelAttribute 绑定表单字段到对象;
  • 方法内部执行业务逻辑处理。

8. 显示校验错误信息

可以通过 #fields.hasErrors() 判断是否有错误,使用 #fields.errors() 获取错误信息。

显示特定字段的错误:

<ul>
    <li th:each="err : ${#fields.errors('id')}" th:text="${err}" />
    <li th:each="err : ${#fields.errors('name')}" th:text="${err}" />
</ul>

也可使用通配符 * 或常量 all 表示所有字段:

<ul>
    <li th:each="err : ${#fields.errors('*')}" th:text="${err}" />
</ul>

或使用 th:errors 属性简化写法:

<ul>
    <li th:errors="*{id}" />
    <li th:errors="*{name}" />
</ul>

全局错误可通过 global 常量获取:

<ul>
    <li th:each="err : ${#fields.errors('global')}" th:text="${err}" />
</ul>

9. 数据格式化显示

使用双大括号语法 {{}} 可对字段进行格式化显示。这依赖于上下文中配置的 conversionService 中的格式化器。

示例格式化 Student 的 name 字段:

<tr th:each="student: ${students}">
    <td th:text="${{student.name}}" />
</tr>

格式化器需继承 Spring 的 Formatter 接口,并通过 WebMvcConfigurer 注册:

@Configuration
public class WebMVCConfig extends WebMvcConfigurerAdapter {
    // ...
    @Override
    @Description("Custom Conversion Service")
    public void addFormatters(FormatterRegistry registry) {
        registry.addFormatter(new NameFormatter());
    }
}

还可以使用 #conversions.convert() 工具函数转换类型:

<tr th:each="student: ${students}">
    <td th:text="${#conversions.convert(student.percentage, 'Integer')}" />
</tr>

10. 总结

本文详细介绍了如何在 Spring MVC 中集成和使用 Thymeleaf 模板引擎,包括:

✅ 展示模型属性
✅ 渲染条件语句
✅ 表单处理与输入绑定
✅ 错误信息展示
✅ 字段格式化输出

完整代码示例可在 GitHub 仓库 中找到。


原始标题:Introduction to Using Thymeleaf in Spring