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.util
和 java.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 的 Expression
和 Temporals
工具。
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
工具处理,而新的 LocalDateTime
、LocalDate
和 Instant
类将由 #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 中找到。