Оглавление

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 в шаблон Thymeleaf, создав дополнительный файл home.th.xml следующим образом:

<?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.2 Настройка разделенных шаблонов


Включение разделенных шаблонов

Логика разделения не будет ожидаться для каждого шаблона по умолчанию. Вместо этого настроенные resolvers шаблонов (реализации ITemplateResolver) должны будут специально маркировать шаблоны, которые они находят с использованием разделенной логики.

За исключением StringTemplateResolver (который не допускает разделенной логики), все другие готовые версии ITemplateResolver будут предоставлять флаг под названием useDecoupledLogic, который будет отмечать все шаблоны, найденные этим резольвером, как потенциально имеющие всю или часть своей логики в отдельном ресурсе:

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

Смешивание соединенной и разделенной логики

Разделенная логика шаблонов не является обязательным требованием. Когда это разрешено, это означает, что движок будет искать ресурс, содержащий разделенную логику, разбор и слияние с исходным шаблоном, если он существует. При отсутствии разделенного логического ресурса ошибка не будет выдана.

Кроме того, в том же шаблоне мы можем смешивать как соединенную, так и разделенную логику, например, добавляя некоторые атрибуты Thymeleaf в исходный файл шаблона, но оставляя другие для отдельного разделенного логического файла. Наиболее распространенным случаем для этого является использование нового атрибута ref (в v3.0) th:ref.

17.3 Атрибут th:ref


th:ref — только атрибут маркера. Он ничего не делает с точки зрения обработки и просто исчезает при обработке шаблона, но его полезность заключается в том, что он действует как ссылка на разметку, то есть его можно разрешить по имени из селектора разметки точно так же, как имя тега или фрагмент (th:fragment).

Поэтому, если у нас есть селектор типа:

<attr sel="whatever" .../>

Это будет соответствовать:

  • Любому <whatever> тегу
  • Любому тегу с th:fragment=«whatever» аттрибутом
  • Любому тегу с th:ref=«whatever» аттрибутом

В чем преимущество th:ref против, например, использования pure-HTML атрибута id? Просто факт, что мы, возможно, не хотим добавлять так много атрибутов id и class к нашим тегам, чтобы действовать как логические привязки, которые могут в конечном итоге загрязнить наш вывод.

И все же, какой недостаток th:ref? Очевидно, что мы добавим немного логики Thymeleaf к нашим шаблонам.

Обратите внимание, что применимость атрибута th:ref применима не только к разделенным файлам логических шаблонов: он работает одинаково в других типах сценариев, например, в выражениях фрагментов (~{...}).

17.4 Производительность разделенных шаблонов


Воздействие очень мало. Когда найденный шаблон помечен для использования разделенной логики и он не кэшируется, логика шаблона будет сначала проанализирована и обработана в виде последовательности инструкций в памяти: в основном список атрибутов, которые будут вставляться в каждый селектор разметки.

Но это единственный дополнительный шаг, потому что после этого реальный шаблон будет анализироваться, и в то время как он анализируется, эти атрибуты будут инъецированы на лету самим парсером, благодаря расширенным возможностям выбора узлов в AttoParser. Таким образом, анализируемые узлы выйдут из анализатора, как если бы у них были введенные атрибуты, записанные в исходном файле шаблона.

Самое большое преимущество этого? Когда шаблон сконфигурирован для кэширования, он будет кэшироваться, уже содержащим введенные атрибуты. Таким образом, накладные расходы на использование разделенных шаблонов для кэшируемых шаблонов, как только они будут кэшированы, будут абсолютно нулевыми.

17.5. Анализ разделенной логики


Thymeleaf находит разделенные логические ресурсы, соответствующие каждому шаблону, и настраивается пользователем. Он определяется точкой расширения, org.thymeleaf.templateparser.markup.decoupled.IDecoupledTemplateLogicResolver, для которой предоставляется реализация по умолчанию: StandardDecoupledTemplateLogicResolver.

Что делает эта стандартная реализация?

  • Во-первых, он применяет префикс и суффикс к базовому имени ресурса шаблона (полученный с помощью метода ITemplateResource#getBaseName()). Оба префикс и суффикс могут быть настроены и по умолчанию префикс будет пустым, а суффикс будет .th.xml
  • Во-вторых, он просит ресурс шаблона найти относительный ресурс с вычисленным именем с помощью метода ITemplateResource#relative(String relativeLocation)

Конкретную реализацию IDecoupledTemplateLogicResolver, которую нужно использовать, можно легко настроить в TemplateEngine:

final StandardDecoupledTemplateLogicResolver decoupledresolver = 
        new StandardDecoupledTemplateLogicResolver();
decoupledResolver.setPrefix("../viewlogic/");
...
templateEngine.setDecoupledTemplateLogicResolver(decoupledResolver);

Комментарии (0)