Thymeleaf 解耦模板逻辑
1. 解耦逻辑:概念
到目前为止,我们已经为我们的Grocery Store工作,模板以通常的方式完成,逻辑以属性的形式插入到我们的模板中。
但Thymeleaf也让我们彻底脱钩从逻辑模板标记,允许创建完全逻辑较少标记模板在HTML和XML模板模式。
主要思想是模板逻辑将在单独的逻辑文件中定义(更确切地说是逻辑资源,因为它不需要是文件)。默认情况下,该逻辑资源将是与模板文件位于同一位置(例如文件夹)的附加文件,具有相同的名称但具有.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或模板知识的设计师可以创建,编辑和/或理解。或者由某些外部系统提供的HTML片段,根本没有Thymeleaf挂钩。
现在让我们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>
在这里,我们可以<attr>
在thlogic块内看到很多标签。这些<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模板将是设计和开发团队之间更好的通信工件。
2. 配置解耦模板
Enabling decoupled templates
默认情况下,每个模板都不会出现解耦逻辑。相反,配置的模板解析器(实现ITemplateResolver)需要使用解耦逻辑专门标记它们解析的模板。
除了StringTemplateResolver(不允许解耦逻辑)之外,所有其他开箱即用的实现都ITemplateResolver将提供一个标记useDecoupledLogic,该标记将标记由该解析器解析的所有模板,因为它可能将其全部或部分逻辑生活在单独的资源中:
final ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver(servletContext); ... templateResolver.setUseDecoupledLogic(true);
混合耦合和解耦逻辑
启用时,解耦模板逻辑不是必需的。启用后,这意味着引擎将查找包含解耦逻辑的资源,解析并将其与原始模板(如果存在)合并。如果解耦的逻辑资源不存在,则不会引发错误。
此外,在同一模板中,我们可以混合耦合和解耦逻辑,例如通过在原始模板文件中添加一些Thymeleaf属性,但将其他属性留给单独的解耦逻辑文件。最常见的情况是使用new(in v3.0)th:ref属性。
3. th:ref属性
th:ref只是一个标记属性。它从处理的角度来看没有做任何事情,只是在处理模板时就消失了,但它的用处在于它充当标记引用,即它可以通过标记选择器的名称来解析,就像标记名称或片段一样(th:fragment)。
所以,如果我们有一个选择器,如:
<attr sel="whatever" .../>
这将匹配:
- 任何
<whatever>
标签。 - 任何带有
th:fragment="whatever"
属性的标签。 - 任何带有
th:ref="whatever"
属性的标签。
th:ref例如,使用纯HTML id属性的优点是什么?仅仅是事实,我们可能不希望添加这么多id和class属性,我们的标记作为逻辑锚,这最终可能会污染我们的产量。
而在同样的意义上,有什么缺点th:ref?好吧,显然我们要在模板中添加一些Thymeleaf逻辑(“逻辑”)。
请注意,该th:ref属性的适用性不仅适用于解耦的逻辑模板文件:它在其他类型的场景中也是如此,例如片段表达式(~{...})。
4. 解耦模板的性能影响
影响非常小。当已解析的模板被标记为使用解耦逻辑并且未缓存时,模板逻辑资源将首先被解析,解析并处理成内存中指令的序列:基本上是要注入每个标记选择器的属性列表。
但这是唯一需要的额外步骤,因为在此之后,真正的模板将被解析,并且在解析时,这些属性将由解析器本身即时注入,这要归功于AttoParser中节点选择的高级功能。因此,解析后的节点将从解析器中出来,就好像它们将注入的属性写入原始模板文件中一样。
这个的最大优点是什么?将模板配置为高速缓存时,它将被缓存,其中包含已注入的属性。因此,一旦缓存模板使用解耦模板进行缓存,其开销绝对为零。
5. 解耦逻辑的分辨率
Thymeleaf解析对应于每个模板的解耦逻辑资源的方式可由用户配置。它由扩展点确定org.thymeleaf.templateparser.markup.decoupled.IDecoupledTemplateLogicResolver,为其提供默认实现:StandardDecoupledTemplateLogicResolver。
这个标准实现有什么作用?
- 首先,它将a prefix和a 应用于模板资源suffix的基本名称(通过其ITemplateResource#getBaseName()方法获得)。前缀和后缀都可以配置,默认情况下,前缀为空,后缀为.th.xml。
- 其次,它要求模板资源通过其方法解析具有计算名称的相对资源ITemplateResource#relative(String relativeLocation)。
IDecoupledTemplateLogicResolver可以TemplateEngine轻松配置要使用的具体实现:
final StandardDecoupledTemplateLogicResolver decoupledresolver = new StandardDecoupledTemplateLogicResolver(); decoupledResolver.setPrefix("../viewlogic/"); ... templateEngine.setDecoupledTemplateLogicResolver(decoupledResolver);
下一章:Thymeleaf 表达式基本对象
始终可以调用某些对象和变量映射。我们来看看他们:基础对象#ctx:上下文对象。实施org.thymeleaf.context.IContext或org.thymeleaf.context.IWebCont ...