解耦逻辑:概念
到目前为止,模板都是以正常的方式完成,逻辑以属性的形式插入模板中。
但是Thymeleaf还允许我们将模板标记与其逻辑完全分离,从而允许在 HTML 和 XML 模板模式下创建完全无逻辑的标记模板。
主要思想是模板逻辑将在单独的逻辑文件中定义(更确切地说是逻辑资源,因为它不必是file)。默认情况下,该逻辑资源将是与模板文件位于同一位置(例如,文件夹)的附加文件,其名称相同,但扩展名为 .th.xml:
/templates +->/home.html +->/home.th.xml
因此,home.html 文件可以完全没有逻辑。它可能看起来像这样:
<!DOCTYPE html>
<html>
<body>
<table id="usersTable">
<tr>
<td class="username">Jeremy Grapefruit</td>
<td class="usertype">Normal User</td>
</tr>
<tr>
<td class="username">Alice Watermelon</td>
<td class="usertype">Administrator</td>
</tr>
</table>
</body>
</html>
这里绝对没有Thymeleaf代码,这是一个模板文件,不具备Thymeleaf或模板知识的设计人员可以创建,编辑或理解、或某些外部系统完全没有Thymeleaf钩子提供的HTML片段。
现在,home.html 通过创建如下所示的其他 home.th.xml 文件,将该模板转换为Thymeleaf模板:
<?xml version="1.0"?>
<thlogic>
<attr sel="#usersTable" th:remove="all-but-first">
<attr sel="/tr[0]" th:each="user : ${users}">
<attr sel="td.username" th:text="${user.name}" />
<attr sel="td.usertype" th:text="#{|user.type.${user.type}|}" />
</attr>
</attr>
</thlogic>
在这里,我们可以看到一个 thlogic 块内有很多 <attr> 标签。这些 <attr> 标签对通过其属性选择的原始模板的节点执行属性注入,这些 sel 属性包含Thymeleaf标记选择器(实际上是AttoParser标记选择器)。
另请注意,<attr> 可以嵌套标签,以便附加选择器。例如,上述中的 sel="/tr[0]",将被处理为sel="#usersTable/tr[0]"。用户名的选择器 <td> 将被处理为 sel="#usersTable/tr[0]//td.username"。
因此,一旦合并,上面看到的两个文件将与以下内容相同:
<!DOCTYPE html>
<html>
<body>
<table id="usersTable" th:remove="all-but-first">
<tr th:each="user : ${users}">
<td class="username" th:text="${user.name}">Jeremy Grapefruit</td>
<td class="usertype" th:text="#{|user.type.${user.type}|}">Normal User</td>
</tr>
<tr>
<td class="username">Alice Watermelon</td>
<td class="usertype">Administrator</td>
</tr>
</table>
</body>
</html>
与创建两个单独的文件相比,这看起来更熟悉,并且确实不那么冗长。但是,解耦模板的优势在于,我们可以使模板完全独立于Thymeleaf,因此从设计的角度来看,它具有更好的可维护性。
当然,设计人员和开发人员之间仍然需要一些约定,例如,用户 <table> 将需要一个id="usersTable",但是在许多情况下,纯HTML模板将是设计团队和开发团队之间更好的沟通工具。
配置解耦模板
默认情况下,不会期望每个模板都需求解耦逻辑。相反,已配置的模板解析器(ITemplateResolver的实现)需要专门标记他们解析的模板为使用解耦逻辑。
除了StringTemplateResolver(不允许解耦逻辑)外,的所有其他现成 ITemplateResolver 实现都将提供一个useDecoupledLogic 标志,该标志将该解析器解析的所有模板标记为可能将其全部或部分逻辑存储在单独的资源中:
final ServletContextTemplateResolver templateResolver =
new ServletContextTemplateResolver(servletContext);
...
templateResolver.setUseDecoupledLogic(true);
混合耦合和解耦逻辑
启用后,解耦模板逻辑不是必需的。启用后,这意味着引擎将查找包含解耦逻辑的资源,如果存在,则将其解析并与原始模板合并。如果解耦的逻辑资源不存在,则不会引发任何错误。
同样,在同一模板中,我们可以混合使用耦合逻辑和解耦逻辑,例如,通过在原始模板文件中添加一些Thymeleaf属性,而将其他属性留给单独的解耦逻辑文件。最常见的情况是使用新的(在v3.0中)th:ref 属性。
th:ref 属性
th:ref 只是标记属性。从处理的角度来看,它什么也没做,只是在处理模板后消失,但是它的作用在于它充当标记引用,即它可以像标记名或片段(th:fragment)一样通过标记选择器中的名称来解析。
因此,如果我们有一个选择器,例如:
<attr sel="whatever" .../>
这将匹配:
- 任何 <whatever> 标签。
- 具有 th:fragment="whatever" 属性的任何标签。
- 具有 th:ref="whatever" 属性的任何标签。
例如,使用纯HTML id属性,使用 th:ref 的优势是什么?事实上,我们可能不想在标签中添加这么多 id 和 class 属性来充当逻辑锚,这可能会污染我们的输出。
从同样的意义上说,th:ref 的缺点是什么?很显然,我们会在模板中添加一些Thymeleaf逻辑(“逻辑”)。
请注意,th:ref 属性的适用性不仅适用于解耦的逻辑模板文件:它在其他类型的场景中也一样工作,例如在片段表达式(~{...})中。
解耦模板的性能影响
影响极小。当一个已解析的模板被标记为使用解耦逻辑并且不被缓存时,该模板逻辑资源将首先被解析,解析并处理为一系列内存中的指令:基本上是要注入到每个标记选择器的属性列表。
但这是唯一需要执行的附加步骤,因为在此之后,将解析真实模板,并且在解析这些模板时,由于AttoParser中节点选择的高级功能,这些属性将由解析器本身即时注入。。因此,已解析的节点将从解析器中出来,就像它们的注入属性写在原始模板文件中一样。
这样最大的优势?将模板配置为要缓存时,它将缓存已包含注入属性的模板。因此,一旦对高速缓存的模板使用解耦模板,其开销将绝对为零。
解耦逻辑的解析
Thymeleaf解析与每个模板相对应的解耦逻辑资源的方式可由用户配置。它由扩展点org.thymeleaf.templateparser.markup.decoupled.IDecoupledTemplateLogicResolver决定,并且为其提供了默认实现:StandardDecoupledTemplateLogicResolver。
此标准实现有什么作用?
- 首先,它将 prefix 和 suffix 应用于模板资源的基本名称(通过其 ITemplateResource#getBaseName() 方法获得)。前缀和后缀都可以配置,默认情况下,前缀为空,后缀为 .th.xml。
- 其次,它要求模板资源通过其ITemplateResource#relative(String relativeLocation)方法来解析具有所计算名称的相对资源。
IDecoupledTemplateLogicResolver 可以 TemplateEngine 轻松配置要使用的具体实现:
final StandardDecoupledTemplateLogicResolver decoupledresolver =
new StandardDecoupledTemplateLogicResolver();
decoupledResolver.setPrefix("../viewlogic/");
...
templateEngine.setDecoupledTemplateLogicResolver(decoupledResolver);

回复以下关键字,获取更多资源
SpringCloud进阶之路 | Java 基础 | 微服务 | JAVA WEB | JAVA 进阶 | JAVA 面试 | MK 精讲

笔者开通了个人微信公众号【银河架构师】,分享工作、生活过程中的心得体会,填坑指南,技术感悟等内容,会比博客提前更新,欢迎订阅。