Spring 整合 Thymeleaf 模板引擎

1、概览

Thymeleaf 是一个 Java 模板引擎,用于处理和创建 HTML、XML、JavaScript、CSS 和文本。

本文将带你了解如何在 Spring 和 Spring Boot 应用中整合、使用 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;
}

templateResolver Bean 的 prefixsuffix 属性分别表示视图页面在 webapp 目录中的位置及其文件扩展名。

Spring MVC 的 ViewResolver 接口将 Controller 返回的视图名称映射为实际的视图对象。ThymeleafViewResolver 实现了 ViewResolver 接口,用于根据视图名称确定要渲染的 Thymeleaf 视图。

整合的最后一步是将 ThymeleafViewResolver 添加为一个 Bean:

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

3、Spring Boot 整合 Thymeleaf

Spring Boot 通过添加 spring-boot-starter-thymeleaf 依赖,为 Thymeleaf 提供了自动配置功能:

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

无需明确配置。默认情况下,HTML 文件应放在 resources/templates 位置。

4、渲染 Message Source(Properties 文件)中的值

可以使用 th:text="#{key}" 标签属性来渲染 Properties 文件中的值。

为此,需要将 Properties 文件配置为 messageSource Bean:

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

渲染 welcome.message 的 KEY 值:

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

5、渲染 Model 属性

5.1、 示例属性

使用 th:text="${attributename}" 标签属性来渲染 Model 属性。

在 Controller 类中添加一个名为 serverTime 的 Model 属性:

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

渲染 serverTime 属性:

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

5.2、集合属性

如果 Model 属性是一个对象集合,可以使用 th:each 标签属性对其进行遍历。

定义一个包含 idname 两个字段的 Student Model 类:

public class Student implements Serializable {
    private Integer id;
    private String name;
    // 标准的 GET / SET
}

在 Controller 类中添加一个 List<Student> 作为 Model 属性:

List<Student> students = new ArrayList<Student>();
// 构建 students 的逻辑 ...
model.addAttribute("students", students);

最后,遍历 students 列表并渲染所有字段值:

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

6、条件分支

6.1、ifunless

使用 th:if="${condition}" 属性在满足条件时渲染视图的某个部分。如果不满足条件,使用 th:unless="${condition}" 属性来渲染视图的某个部分。

Student model 中添加一个 gender 字段:

public class Student implements Serializable {
    private Integer id;
    private String name;
    private Character gender;
    
    // 标准的 GET 、SET 方法
}

假设这个字段有两个枚举值 (MF)来表示学生的性别。

如果想渲染 “Male” 或 “Female” 字样,而不是单个字符,可以使用 th:if 来实现:

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

6.2、 switchcase

使用 th:switchth:case 属性,通过 switch 语句结构有条件地渲染内容。

使用 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 提供表单 action URL,使用 th:object 指定要将提交的表单数据绑定到的对象。

单个字段使用 th:field="{name}" 属性映射,其中 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>

在上述代码中,/saveStudent 是表单 action URL,student 是保存所提交表单数据的对象。

saveStudent 方法处理表单提交:

@RequestMapping(value = "/saveStudent", method = RequestMethod.POST)
public String saveStudent(Model model, @ModelAttribute("student") Student student) {
    // 处理输入数据的逻辑
}

@RequestMapping 注解将 Controller 方法与表单中提供的 URL 映射。注解方法 saveStudent() 对提交的表单执行所需的处理。最后,@ModelAttribute 注解将表单字段与 student 对象绑定。

8、渲染 Validation 验证错误

可以使用 #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 来表示所有字段。使用 th:each 属性来遍历每个字段可能存在的多个错误。

下面是使用通配符重写的 HTML 代码:

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

下例使用了常量 all

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

同样,也可以使用 global 常量在 Spring 中渲染全局错误。如下:

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

此外,还可以使用 th:errors 属性来渲染错误信息。

使用 th:errors 属性重写之前在表单中渲染错误的代码:

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

9、 格式化和转换

使用双括号语法 {{}} 来格式化要渲染的数据。这使用了在 conversionService Bean 中为该类型字段配置的 formatters

格式化 Student 类中的 name 字段:

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

上述代码使用了 NameFormatter 类。该类通过 WebMvcConfigurer 接口中的 addFormatters() 方法进行配置。如下:

@Configuration 类继承了 WebMvcConfigurerAdapter 类:

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

NameFormatter 类实现了 Spring Formatter 接口。

还可以使用 #conversions 来转换对象。其语法是 #conversions.convert(Object, Class),其中 Object 将转换为 Class 类型。

去掉 student 对象 percentage 字段的小数部分(转换为 Integer):

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

10、总结

本文介绍了如何在 Spring 应用中整合和使用 Thymeleaf。还介绍了如何渲染字段、处理表单输入、渲染验证错误和转换数据。


Ref:https://www.baeldung.com/thymeleaf-in-spring-mvc