解耦模板逻辑

Python/Java学习交流群:369606713


17 解耦模板逻辑

17.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模板将是设计和开发团队之间更好的通信工件。

17.2Configuring decoupled templates

Enabling decoupled templates

默认情况下,每个模板都不会出现解耦逻辑。相反,配置的模板解析器(实现ITemplateResolver)需要使用解耦逻辑专门标记它们解析的模板。

除了StringTemplateResolver(不允许解耦逻辑)之外,所有其他开箱即用的实现都ITemplateResolver将提供一个标记useDecoupledLogic,该标记将标记由该解析器解析的所有模板,因为它可能将其全部或部分逻辑生活在单独的资源中:

final ServletContextTemplateResolver templateResolver =
        new ServletContextTemplateResolver(servletContext);
...
templateResolver.setUseDecoupledLogic(true);

混合耦合和解耦逻辑

启用时,解耦模板逻辑不是必需的。启用后,这意味着引擎将查找包含解耦逻辑的资源,解析并将其与原始模板(如果存在)合并。如果解耦的逻辑资源不存在,则不会引发错误。

此外,在同一模板中,我们可以混合耦合和解耦逻辑,例如通过在原始模板文件中添加一些Thymeleaf属性,但将其他属性留给单独的解耦逻辑文件。最常见的情况是使用new(in v3.0)th:ref属性。

17.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属性的适用性不仅适用于解耦的逻辑模板文件:它在其他类型的场景中也是如此,例如片段表达式(~{...})。

17.4解耦模板的性能影响

影响非常小。当已解析的模板被标记为使用解耦逻辑并且未缓存时,模板逻辑资源将首先被解析,解析并处理成内存中指令的序列:基本上是要注入每个标记选择器的属性列表。

但这是唯一需要的额外步骤,因为在此之后,真正的模板将被解析,并且在解析时,这些属性将由解析器本身即时注入,这要归功于AttoParser中节点选择的高级功能。因此,解析后的节点将从解析器中出来,就好像它们将注入的属性写入原始模板文件中一样。

这个的最大优点是什么?将模板配置为高速缓存时,它将被缓存,其中包含已注入的属性。因此,一旦缓存模板使用解耦模板进行缓存,其开销绝对为零。

17.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);