1. 概述

Thymeleaf 是一个能够直接与 Spring 集成的 Java 模板引擎。关于 Thymeleaf 和 Spring 的基础知识,请参阅这篇介绍:Thymeleaf 在 Spring MVC 中的应用

除了基本功能,Thymeleaf 还提供了一套实用工具,帮助我们在应用中执行常见任务。

本教程将讨论 Thymeleaf 3.0 版本中处理和格式化新旧 Java Date 类的功能。

2. Maven 依赖

首先,我们需要在 pom.xml 中设置配置,以便将 Thymeleaf 与 Spring 整合:

Maven 中最新的 Thymeleaf(thymeleaf)和 Thymeleaf-Spring5(thymeleaf-spring5)版本可以在中央仓库找到。对于 Spring 4 项目,应使用 thymeleaf-spring4 而非 thymeleaf-spring5。

此外,为了支持 Java 8 新的 Date 类,我们需要在 pom.xml 中添加另一个依赖:

<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-java8time</artifactId>
    <version>3.0.4.RELEASE</version>
</dependency>

thymeleaf-extras 是 Thymeleaf 官方团队完全支持的一个可选模块,用于兼容 Java 8 时间 API。它会在表达式评估时向上下文添加一个名为 #temporals 的实用对象处理器,用于处理 Object-Graph Navigation Language (OGNL) 和 Spring Expression Language (SpringEL) 表达式。

3. 旧与新:java.utiljava.time

Time 包是 Java SE 平台的新日期、时间和日历 API。新 API 与旧的遗留 Date API 的主要区别在于,新 API 区分了机器和人类对时间线的看法。机器视图显示相对于纪元的整数值序列,而人类视图则展示一系列字段(如年、月和日)。

要使用新的 Time 包,我们需要在模板引擎配置中启用新的 Java8TimeDialect

private ISpringTemplateEngine templateEngine(ITemplateResolver templateResolver) {
    SpringTemplateEngine engine = new SpringTemplateEngine();
    engine.addDialect(new Java8TimeDialect());
    engine.setTemplateResolver(templateResolver);
    return engine;
}

这将为 Thymeleaf 模板添加类似于标准方言中的 #temporals 对象,允许从模板中创建和格式化 Temporal 对象。

为了测试新旧类的处理,我们将创建以下变量,并将其作为模型对象添加到控制器类中:

model.addAttribute("standardDate", new Date());
model.addAttribute("localDateTime", LocalDateTime.now());
model.addAttribute("localDate", LocalDate.now());
model.addAttribute("timestamp", Instant.now());

现在我们可以使用 Thymeleaf 的 ExpressionTemporals 工具。

3.1. 格式化日期

我们首先来了解如何使用 Date 对象(添加到 Spring 模型参数)的格式化。我们将使用 ISO8601 格式:

<h1>Format ISO</h1>
<p th:text="${#dates.formatISO(standardDate)}"></p>
<p th:text="${#temporals.formatISO(localDateTime)}"></p>
<p th:text="${#temporals.formatISO(localDate)}"></p>
<p th:text="${#temporals.formatISO(timestamp)}"></p>

无论后端如何设置 Date,Thymeleaf 都会根据选择的标准进行显示。standardDate 将由 #dates 工具处理,而新的 LocalDateTimeLocalDateInstant 类将由 #temporals 工具处理。

若要手动设置格式,可以这样做:

<h1>Format manually</h1>
<p th:text="${#dates.format(standardDate, 'dd-MM-yyyy HH:mm')}"></p>
<p th:text="${#temporals.format(localDateTime, 'dd-MM-yyyy HH:mm')}"></p>
<p th:text="${#temporals.format(localDate, 'MM-yyyy')}"></p>

观察到的是,不能使用 #temporals.format(…) 来处理 Instant 类,这会导致 UnsupportedTemporalTypeException。此外,格式化 LocalDate 只能在指定日期字段时才可行,跳过时间字段。

最终结果如下:

屏幕截图

3.2. 获取特定日期字段

要获取 java.util.Date 类的特定字段,应使用以下工具:

${#dates.day(date)}
${#dates.month(date)}
${#dates.monthName(date)}
${#dates.monthNameShort(date)}
${#dates.year(date)}
${#dates.dayOfWeek(date)}
${#dates.dayOfWeekName(date)}
${#dates.dayOfWeekNameShort(date)}
${#dates.hour(date)}
${#dates.minute(date)}
${#dates.second(date)}
${#dates.millisecond(date)}

对于新的 java.time 包,应继续使用 #temporals 工具:

${#temporals.day(date)}
${#temporals.month(date)}
${#temporals.monthName(date)}
${#temporals.monthNameShort(date)}
${#temporals.year(date)}
${#temporals.dayOfWeek(date)}
${#temporals.dayOfWeekName(date)}
${#temporals.dayOfWeekNameShort(date)}
${#temporals.hour(date)}
${#temporals.minute(date)}
${#temporals.second(date)}
${#temporals.millisecond(date)}

来看几个例子。首先,显示今天的星期几:

<h1>Show only which day of a week</h1>
<p th:text="${#dates.day(standardDate)}"></p>
<p th:text="${#temporals.day(localDateTime)}"></p>
<p th:text="${#temporals.day(localDate)}"></p>

接下来,显示星期几的名称:

<h1>Show the name of the week day</h1>
<p th:text="${#dates.dayOfWeekName(standardDate)}"></p>
<p th:text="${#temporals.dayOfWeekName(localDateTime)}"></p>
<p th:text="${#temporals.dayOfWeekName(localDate)}"></p>

最后,显示一天中的当前秒数:

<h1>Show the second of the day</h1>
<p th:text="${#dates.second(standardDate)}"></p>
<p th:text="${#temporals.second(localDateTime)}"></p>

注意,为了处理时间部分,我们需要使用 LocalDateTime,因为 LocalDate 会抛出错误。

4. 如何在表单中使用日期选择器

让我们来看看如何在 Thymeleaf 表单中使用日期选择器提交 Date 值。

首先,创建一个带有 Date 属性的 Student 类:

public class Student implements Serializable {
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date birthDate;
}

@DateTimeFormat 注解声明 birthDate 字段应以 Date 格式进行格式化。

接下来,创建一个 Thymeleaf 表单来提交 Date 输入:

<form th:action="@{/saveStudent}" method="post" th:object="${student}">
    <div>
        <label for="student-birth-date">Date of birth:</label>
        <input type="date" th:field="${student.birthDate}" id="student-birth-date"/>
    </div>
    <div>
        <button type="submit" class="button">Submit</button>
    </div>
</form>

当我们提交表单时,控制器会拦截带有 th:object 属性的映射 Student 对象。同时,th:field 属性将输入值绑定到 birthDate 字段。

现在,创建一个控制器来拦截 POST 请求:

@RequestMapping(value = "/saveStudent", method = RequestMethod.POST)
public String saveStudent(Model model, @ModelAttribute("student") Student student) {
    model.addAttribute("student", student);
    return "datePicker/displayDate.html";
}

提交表单后,我们将在另一个页面上显示 birthDate 值,格式为 dd/MM/yyyy

<h1>Student birth date</h1>
<p th:text="${#dates.format(student.birthDate, 'dd/MM/yyyy')}"></p>

结果显示带有日期选择器的表单:

日期选择器

提交表单后,我们会看到选择的日期:

显示日期

5. 总结

在这篇快速教程中,我们讨论了 Thymeleaf 3.0 版本中实现的 Java Date 处理功能。

如何测试? 我们的建议是在浏览器中先自行尝试,然后检查现有的 JUnit 测试。

请注意,我们的示例并未涵盖 Thymeleaf 所有可用选项。若想了解所有类型工具的详细信息,请参考我们的文章:Spring 和 Thymeleaf 表达式

这个教程的完整实现可在 GitHub 中找到。


« 上一篇: Apache Tomcat入门介绍
» 下一篇: Java MongoDB 使用指南