即使不使用 Spring MVC,Thymeleaf 也可以在批处理作业、命令行程序、邮件服务或报表流水线中渲染 HTML。独立应用需要配置模板解析器和 TemplateEngine,提供上下文,并将渲染后的输出写入目标位置。
选择集成级别 #
非 Spring 程序需要 Thymeleaf 核心构件:
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf</artifactId>
<version>3.1.3.RELEASE</version>
</dependency>Spring 应用可以使用 thymeleaf-spring6 和 SpringTemplateEngine,以支持 Spring EL、MessageSource、类型转换和感知 Spring 的链接行为。Spring Boot 的依赖管理应选择匹配的 Thymeleaf 版本。当需要这些集成功能时,spring-boot-starter-thymeleaf 很方便,但运行核心引擎并不需要它,也不要求使用 MVC。
配置解析器和引擎 #
选择与模板所有权相匹配的解析器。ClassLoaderTemplateResolver 适用于打包在 JAR 中的不可变模板;FileTemplateResolver 适用于由运维人员管理的文件:
var resolver = new ClassLoaderTemplateResolver();
resolver.setPrefix("reports/");
resolver.setSuffix(".html");
resolver.setTemplateMode(TemplateMode.HTML);
resolver.setCharacterEncoding(StandardCharsets.UTF_8.name());
resolver.setCacheable(true);
var engine = new TemplateEngine();
engine.setTemplateResolver(resolver);引擎应配置一次,然后共享使用。Thymeleaf 文档将完成配置的 TemplateEngine 说明为线程安全;但在处理开始后更改配置不受支持。对于稳定的生产模板,应启用缓存。只有在开发或运行期间必须重新读取文件时,才应禁用缓存,或配置有效期受限的缓存。
解析器上的字符编码控制模板字节的解码方式。输出 Writer 使用的编码则独立控制生成文件的字节,因此应显式设置两者。
渲染异构报表数据 #
不要在 Java 中构造 HTML,而应暴露具有领域模型形状的值:
record WarningRow(String orderId, String message) {}
record DailyReport(List<OrderSummary> orders, List<WarningRow> warnings) {}
var context = new Context(Locale.ENGLISH);
context.setVariable("report", report);
context.setVariable("generatedAt", generatedAt);
engine.process("daily-report", context, writer);模板可以渲染包含不同行类型的独立部分:
<section>
<h2>Warnings</h2>
<table>
<tr th:each="warning : ${report.warnings}">
<td th:text="${warning.orderId}">order</td>
<td th:text="${warning.message}">message</td>
</tr>
</table>
</section>th:text 会根据输出模式对文本进行转义。th:utext 会输出未转义的标记,因此不得接收不受信任的内容。HTML 模板模式会解析并写出能够识别 HTML 的标记;只有在目标格式需要不同规则时,才选择 XML、text、JavaScript 或 CSS 模式。
片段和可测试性 #
使用 th:fragment 声明可复用标记,并通过 th:insert 或 th:replace 组合这些标记。片段参数可以使共享表格和页眉不依赖全局变量名。可以对多个解析器排序,并通过可解析模式限制其范围,从而防止范围过宽的文件系统解析器意外遮蔽已打包的模板。
将渲染保持为接收报表模型和 Writer 的纯服务。测试可以使用 StringWriter,通过 HTML 解析器解析结果,并断言结构和转义行为。解析器测试还应验证模板缺失和非 ASCII 内容的情况。
管理输出资源 #
使用显式字符集写入,并关闭 writer:
Path temporary = Files.createTempFile(outputDirectory, "daily-", ".html");
try (Writer writer = Files.newBufferedWriter(
temporary, StandardCharsets.UTF_8,
StandardOpenOption.TRUNCATE_EXISTING)) {
engine.process("daily-report", context, writer);
}
Files.move(temporary, destination,
StandardCopyOption.ATOMIC_MOVE,
StandardCopyOption.REPLACE_EXISTING);需要原子移动时,应在目标文件系统中创建临时文件。渲染或上传失败后清理临时路径,并且在处理成功之前不要发布目标文件。对于对象存储,应渲染到有界缓冲区,或通过明确规定重试和部分失败行为的上传 API 进行流式上传。
评论
在 GitHub 上阅读或评论(发表评论需要登录)