元素。
通过 ID 引用非片段
片段表达式selector的部分不仅支持值。也可以通过其属性直接引用元素,类似于 CSS 选择器:th:fragmentid
< div id = "copy-section" > © 2024 belief-driven-design.com </ div >
< div th:replace = "~{this :: #copy-section}" > </ div >
|
参数化片段
片段不仅仅是可重复使用的文本的静态块;它们可以像任何其他模板一样动态并接受参数,从而使它们在各种环境中更具可重复使用性。
该th:fragment值接受在片段中使用的逗号分隔的变量名:
< button th:fragment = "button (btnType,label)" type = "button" class = "btn" th:classappend = "${btnType}" > < span th:text = "${label}" >按钮标签</ span > </ button >
|
selector当引用片段时,参数在后面的括号中传递:
<!-- 隐式名称 --> < div th:replace = "::button ('btn-primary','Login')" > </ div >
<!-- 显式名称 --> < div th:replace = "::button (btnType='btn-primary',label='Login')" > </ div >
<!-- 使用来自上下文的变量 --> < div th:replace = "::button (btnType=${typeValue},label=${btnLabel})" > </ div >
|
布局方言:使用基础模板简化布局
片段已经是重用部分模板的强大工具。但它们的目的是明确地包含在片段中,而不是创建稍后要填充的扩展点。
但是,有一种方法可以用另一种Dialect来实现这一点。
在 Thymeleaf 中,方言是自定义处理器、表达式对象和实用方法的集合,扩展了基本的模板功能。
布局方言是 Thymeleaf 的官方功能,它通过提供基础模板来改善 Thymeleaf 的布局功能,这些模板可以反转包含内容的控制方向。这样就可以创建更复杂但更易于重用的布局。
所需依赖项和设置
要使用它,我们需要添加相应的依赖项并向模板引擎注册方言。
对于 Maven,将以下内容添加到pom.xml:
<dependency> <groupId>nz.net.ultraq.thymeleaf</groupId> <artifactId>thymeleaf-layout-dialect</artifactId> <version>3.3.0</version> </dependency>
|
要将方言添加到引擎上,需要进行以下操作:
var layoutDialect = new LayoutDialect(); templateEngine.addDialect(layoutDialect);
|
创建基本模板
添加依赖项并且模板引擎了解方言后,我们可以使用layout命名空间来创建基本布局:
<!-- layout.html --> <!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<head> <title layout:title-pattern="$LAYOUT_TITLE - $CONTENT_TITLE">My Awesome App</title> </head>
<body> <header th:replace="~{fragments/shared :: header}"></header>
<main layout:fragment="content"> <!-- Extension point for Layout Dialect--> </main>
<footer th:replace="~{fragments/shared :: footer}"></footer> </body>
</html>
|
此基本模板使用多种布局功能:
- xmlns:layout:需要命名空间规范才能使layout:...属性发挥作用。
- layout:title-pattern:这将根据指定的模式组合基本布局和内容模板的标题。
- th:replace:页眉和页脚的片段。
- layout:fragment:模板中可重复使用部分的标记。
页面特定内容将替换layout:fragment元素的内部内容。如果不替换,元素将为基本模板提供合理的默认内容。
扩展基础模板
现在我们有了一个基础模板,是时候创建一个模板来装饰它了:
<!DOCTYPE html> <html xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout}">
<head> <title>Home</title> </head>
<body> <div layout:fragment="content"> <h1>Welcome to My Awesome App</h1> <p> Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. </p> </div>
<footer>...</footer> </body>
</html>
|
这里的关键是 layout:decorate,该属性用于查找基础模板,表示当前模板扩展或装饰了基础布局。 任何标有 layout:fragment 的占位符都会被扩展模板中的相应片段替换,输出结果如下:
<!DOCTYPE html> <html>
<head> <title>My Awesome App - Home</title> </head>
<body> <header>...</header>
<main> <h1>Welcome to My Awesome App</h1> <p> Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. </p> </main>
<footer>...</footer> </body>
</html>
|
此功能允许扩展模板仅关注独特的内容,同时重用基础模板中定义的结构和布局。
使用布局方言的主要优势
布局方言提供了几个强大的优势,特别是在处理具有更复杂布局的大型项目时:
- 模板继承和结构一致性可以通过其他模板扩展的基础模板通过将通用结构集中在一个地方来促进DRY(不要重复自己)原则。
- 模块化和可维护性通过将通用布局(结构)与单个页面内容分离,布局方言和片段极大地提高了代码模块化。我们不需要每个模板中都有完整的基本布局,每个常用组件都可以重构为片段。
- 默认内容布局片段无需在扩展的基础模板中提供。任何扩展都是可选的,可以填充合理的默认值,并在需要时进行替换。
何时(不)使用布局方言
在以下情况下使用布局方言:
- 您有一个大型项目,其中许多页面共享一个通用的布局或元素
- 您需要在所有模板中保持一致的外观和感觉
- 您的模板具有基本结构,但需要动态内容部分
- 你想要清晰地区分结构和内容
这些都是使用方言的充分理由。但是,如果你正在处理一个没有太多共享元素的小型简单项目,那么引入方言会使事情变得比需要的更复杂。
最大化可重用性的策略
随着项目的增长,可重用性成为确保可维护性的关键方面。
以下是使用 Thymeleaf 时最大化可重用性的一些策略:
按功能或目的模块化
可以将多个片段分组到一个文件中,因此根据它们的功能或目的对它们进行分组可以使其按逻辑方式组织,并使它们更容易被发现。
逻辑分组可提高可维护性并减少代码库的混乱。这样,如果我们需要更新您的导航栏,我们就知道该在哪里查看。
利用参数化片段
使用参数化片段为您的组件添加动态行为。这减少了为类似任务创建多个片段的需要,并允许保持模板DRY。
结构一致性的布局方言
使用布局方言可以简化常见结构的管理并确保所有模板的一致性。
通过定义包含页眉、页脚和常见布局元素的基本模板,我们可以减少代码重复并确保您的网站具有统一的外观。
并且不要忘记,布局片段不一定需要替换;在适当的地方使用默认内容。
文档片段
与任何代码一样,拥有清晰的文档非常棒;也许不是现在,但你未来的自己最终会感谢你。
在(非布局)片段旁边添加 HTML 注释不会影响输出。
记录良好的片段使它们更易于维护和供合作者访问,从而减少了学习曲线和潜在的误用。
自定义方言和处理器
自定义方言允许您通过定义可以在整个模板中重复使用的新处理器(标签、属性)和功能来扩展 Thymeleaf 的功能。
方言是一组自定义处理器和实用方法,可提供扩展功能。借助自定义方言,您可以引入新属性或元素,以简化复杂任务、封装通用逻辑或使模板更符合您的业务需求。
创建自定义方言
作为我们的例子,我们将实现一个自定义的方言来格式化日期。
一般的想法是指定一个日期格式并用它来呈现当前日期:
< span dateformat :format = "yyyy-MM-dd " > 2024-10-18 </span>
|
要创建我们的自定义方言,您需要:
- 定义处理器(即自定义标签或属性)。
- 注册方言以使其在您的模板中可用。
让我们从创建一个处理器开始吧!
创建处理器
处理器是处理模板不同部分的单元。 Thymeleaf 提供了不同类型的处理器来处理属性、元素甚至模板结构本身。 对于我们的预期用例,我们需要一个元素处理器。 为了避免从头开始,我们扩展了 AbstractElementTagProcessor。 这样,我们只需调用超级构造函数来设置处理器,然后自己实现 doProcess(...):
public class DateFormatProcessor extends AbstractAttributeTagProcessor {
public DateFormatProcessor ( final String dialectPrefix) { super ( TemplateMode.HTML, // TemplateMode:处理器在 HTML 模式下工作 dialectPrefix, // 方言前缀 null , // 要匹配的元素名称(null -> any) false , // 元素名称是否需要加前缀? "format" , // 要匹配的属性名称 true , // 将方言前缀应用于属性名称 10_000 , // 优先级(在方言的优先级内) true ); // 删除属性 }
@Override protected void doProcess ( ITemplateContext context, IProcessableElementTag tag, AttributeName attributeName, String attributeValue, IElementTagStructureHandler structureHandler) {
// 步骤 1:验证
if (attributeValue == null || attributeValue.isBlank()) { throw new TemplateProcessingException ( "属性值不能为空" ); }
// 步骤 2:创建格式化程序并格式化数据
var formatter = DateTimeFormatter.ofPattern(attributeValue); var formattedDate = LocalDateTime.now().format(formatter);
// 步骤 3:设置元素主体
structureHandler.setBody(formattedDate, false ); } }
|
超级调用为处理器配置了所有必需的功能,如在哪种模板模式下工作、如何匹配等。
在我们的例子中,处理器会查找一个以方言前缀为前缀的名为 format 的属性。
第 1 步是进行一些验证,而不是抛出 TemplateProcessingException,我们可以直接在输出中输出错误信息或合理的回退。 我们可以直接在输出中输出错误信息或合理的回退,而不是抛出一个 TemplateProcessingException。
第 2 步是格式化当前日期的实际工作。
最后,第 3 步是用格式化后的日期替换元素的正文内容。 通过 structureHandler,我们可以访问当前处理的元素及其上下文。 第二个参数设置为 false,用于确定新文本是否可由其他处理器处理。
有了处理器,我们就可以用它创建方言了。
创建方言
让我们创建一个简单的自定义方言,为插入和格式化当前日期添加一个新属性:
public class DateFormatDialect extends AbstractProcessorDialect {
public DateFormatDialect() { super( "Date Format Dialect", // Dialect name "dateformat", // Dialect prefix 1_000); // Precendence }
@Override public Set<IProcessor> getProcessors(String dialectPrefix) { return Set.of(new DateFormatProcessor(dialectPrefix)); } }
|
像往常一样,我们不会直接实现 IDialect,而是扩展 AbstractProcessorDialect。
超级类设置了方言的名称、前缀和总体优先级,getProcessors(...) 返回属于该方言的 Set。
优先级定义了处理器完成工作的顺序。 如果一个处理器生成的内容需要进一步处理,那么必须让它先于其他处理器完成工作。
现在,我们可以将方言添加到模板引擎中:
TemplateEngine templateEngine = new TemplateEngine(); templateEngine.addDialect(new DateFormatDialect());
|
就是这样!
这些都是创建自定义处理器、将其包装到方言中并使其可用于我们的模板所涉及的部分。
我们的例子非常简单;你一定要检查一下在不同方面工作的其他处理器类型:
- 元素:对 HTML 元素或标签进行操作(我们在这里已经看到过)
- 属性:由标签上的自定义或标准属性触发
- Text:处理元素内的文本内容
- 模板:转换整个模板或其部分
- 文档:处理之前或之后修改整个文档
- 内联:操作内联表达式或文本。
查看文档以获取更多详细信息:
表达式实用对象
表达式实用程序对象是提供附加功能的实用程序对象。它们简化了常见任务,例如字符串操作或格式化值。
#这些对象可以使用(数字符号)语法访问,例如#numbers。Thymeleaf 有 17 个内置表达式对象:
- #execInfo:访问模板/处理信息
- #messages:获取外部化消息
- #dates:使用java.util.Date对象
- #calendars:类似于,#dates但适用于java.util.Calendar对象
- #numbers:格式化数值
- #uris:转义/取消转义 URI/URL 部分
- #conversions:使用转换服务进行类型强制
- #temporals:使用java.timeAPI
- #strings:操作字符串
- #objects: 使用一般对象
- #bools:布尔运算
- #arrays/lists/sets/maps:使用不同的容器类型
- #aggregates:数组或集合上的聚合
- #ids:处理id属性的助手
您可以在这里找到所有可用的对象及其方法:
除了表达式实用对象外,还有表达式基本对象:
- #ctx:IContext模板的
- #vars/root:同义词#ctx(不推荐)
- #locale:直接访问当前请求的语言环境
自定义模板解析
Thymeleaf 提供了一系列开箱即用的模板解析器,例如ClassLoaderTemplateResolver或FileTemplateResolver
但是如果我们的模板存储在自定义位置(例如数据库或远程服务)怎么办?
不要担心,我们可以轻松创建自己的模板解析器来从任何来源加载模板!
数据转换的自定义处理
作为示例,我们假装实现一个基于数据库的加载器。但是,实际的加载过程并未显示出来,因为本文我们只关注与 Thymeleaf 相关的部分。
与之前显示的许多其他自定义一样,我们不从最低级别开始,ITemplateResolver而是从一个abstract提供大量功能的类开始:AbstractConfigurableTemplateResolver。这样,我们只需要一个构造函数和一个方法来实现简约的实现,但如果需要,仍然可以覆盖更多方法:
public class DatabaseTemplateResolver extends AbstractConfigurableTemplateResolver {
// 这是我们访问数据库的方式,取决于 您的应用程序(超出了本文的范围) private final TemplateDAO dao;
public DatabaseTemplateResolver (TemplateDAO dao) { super (); this .dao = dao; setTemplateMode(TemplateMode.HTML); // 冗余,这是默认的设置默认模板模式。 setCharacterEncoding( "UTF-8" ); // 这可能是必要的,没有默认值 }
@Override protected ITemplateResource computeTemplateResource ( IEngineConfiguration configuration, StringownerTemplate, Stringtemplate, StringresourceName, StringcharacterEncoding, Map<String, Object>templateResolutionAttributes) { //
步骤 1:从数据库获取模板 StringtemplateContent = this.dao.findTemplateByName (templateName);
// 步骤 2:验证if (templateContent == null ) { return null ; }
// 步骤 3:创建 ITemplateResource return new StringTemplateResource (templateContent); } }
|
然后,我们需要向模板引擎注册解析器:
DatabaseTemplateResolver resolver = new DatabaseTemplateResolver(...); templateEngine.addTemplateResolver(resolver);
|
就是这样!
使用自定义模板解析器进行缓存
如果我们只需要对缓存进行二进制选择,我们可以在构造函数中或在注册解析器之前调用 setCacheable(boolean)。 不过,我们还可以通过覆盖 computeValidity(...) 来实现更精细的控制:
@Override protected ICacheEntryValidity computeValidity( IEngineConfiguration configuration, String ownerTemplate, String template, Map<String, Object> templateResolutionAttributes) {
// CUSTOM CONDITION FOR CACHING GOES HERE if (...) { return AlwaysValidCacheEntryValidity.INSTANCE; }
return NonCacheableCacheEntryValidity.INSTANCE; }
|
方法参数并不能提供太多信息来做出明智的决定,内置的解析器大多使用模式匹配,以及解析器是否可缓存。 如果调用模板引擎时使用的是模板规范(TemplateSpec)而不是模板名称,则会设置模板解析属性(templateResolutionAttributes):
TemplateSpec spec = new TemplateSpec("template-name", Map.of("cacheable", false));
templateEngine.process(spec, context);
|
控制缓存可以平衡性能和新鲜度,确保我们不会获得过时的模板内容。 不过,缓存有时会很棘手,所以要谨慎行事。
结论
Thymeleaf 已被证明是一种多功能且强大的解决方案,可满足任何模板需求。其自然的模板方法使与他人的协作更加简单,而不会牺牲任何功能。广泛的表达语言和内置处理器使 Thymeleaf 非常适合任何规模和复杂程度的应用程序。
Thymeleaf 的突出特点之一是其可扩展性。创建自定义处理器或表达式对象可以使 Thymeleaf 满足特定领域的需求。这确保了 Thymeleaf 能够适应独特的项目需求并面向未来。
Thymeleaf 提供与 Spring 生态系统的无缝集成。它的原生支持(包括表单绑定和验证)使其成为基于 Spring 的 Web 应用程序的理想伴侣。
总之,Thymeleaf 结合了两全其美的优势:自然模板的简单性与功能齐全且易于定制的模板引擎的强大功能。