«1. Введение

FreeMarker — это механизм шаблонов, написанный на Java и поддерживаемый Apache Foundation. Мы можем использовать язык шаблонов FreeMarker, также известный как FTL, для создания многих текстовых форматов, таких как веб-страницы, электронная почта или XML-файлы.

В этом уроке мы увидим, что мы можем сделать с помощью FreeMarker «из коробки», хотя обратите внимание, что он вполне настраиваемый и даже хорошо интегрируется со Spring.

Начнем!

2. Краткий обзор

Чтобы добавить динамическое содержимое на наши страницы, нам нужно использовать синтаксис, который понимает FreeMarker:

    ${…} в шаблоне будет заменено в сгенерированном выводе фактическим значение выражения в фигурных скобках — мы называем это интерполяцией — несколько примеров: ${1 + 2} и ${variableName} Теги FTL похожи на теги HTML (но содержат # или @) и FreeMarker интерпретирует их, например \u003c#if…\u003e\u003c/#if\u003e Комментарии в FreeMarker начинаются с \u003c#– и заканчиваются на –\u003e

3. Тег Include

Директива включения FTL — это способ для нас, чтобы следовать принципу DRY в нашем приложении. Мы определим повторяющийся контент в файле и повторно используем его в разных шаблонах FreeMarker с одним включенным тегом.

Один из таких вариантов использования — это когда мы хотим включить раздел меню во многие страницы. Во-первых, мы определим раздел меню в файле — назовем его menu.ftl — со следующим содержимым:

<a href="#dashboard">Dashboard</a>
<a href="#newEndpoint">Add new endpoint</a>

И на нашу HTML-страницу давайте включим созданный menu.ftl:

<!DOCTYPE html>
<html>
<body>
<#include 'fragments/menu.ftl'>
    <h6>Dashboard page</h6>
</body>
</html>

И мы также можем включить FTL в наши фрагменты, и это здорово.

4. Обработка существования значения

FTL будет рассматривать любое нулевое значение как отсутствующее значение. Таким образом, нам нужно быть особенно осторожными и добавить логику для обработки null внутри нашего шаблона.

Мы можем использовать ?? оператор, чтобы проверить, существует ли атрибут или вложенное свойство. Результатом является логическое значение:

${attribute??}

Итак, мы проверили атрибут на значение null, но этого не всегда достаточно. Давайте теперь определим значение по умолчанию в качестве запасного варианта для этого отсутствующего значения. Для этого нам понадобится ! оператор, помещаемый после имени переменной:

${attribute!'default value'}

Используя круглые скобки, мы можем обернуть многие вложенные атрибуты.

Например, чтобы проверить, существует ли атрибут и имеет ли он вложенное свойство с другим вложенным свойством, мы обертываем все:

${(attribute.nestedProperty.nestedProperty)??}

Наконец, собрав все вместе, мы можем встроить их в статическое содержимое:

<p>Testing is student property exists: ${student???c}</p>
<p>Using default value for missing student: ${student!'John Doe'}</p>
<p>Wrapping student nested properties: ${(student.address.street)???c}</p>

~ ~~ И, если бы student был нулевым, мы бы увидели:

<p>Testing is student property exists: false</p>
<p>Using default value for missing student: John Doe</p>
<p>Wrapping student nested properties: false</p>

Обратите внимание на дополнительную директиву ?c, используемую после ??. Мы сделали это, чтобы преобразовать логическое значение в удобочитаемую строку.

5. Тег If-Else

Управляющие структуры присутствуют во FreeMarker, и традиционный if-else, вероятно, знаком:

<#if condition>
    <!-- block to execute if condition is true -->
<#elseif condition2>
    <!-- block to execute if condition2 is the first true condition -->
<#elseif condition3>
    <!-- block to execute if condition3 is the first true condition -->
<#else>
    <!-- block to execute if no condition is true -->
</#if>

Хотя ветки elseif и else являются необязательными, условия должны разрешить логическое значение.

Чтобы помочь нам с нашими вычислениями, мы, вероятно, воспользуемся одним из:

    x == y для проверки того, что x равен yx != y для возврата true, только если x отличается от yx lt y означает, что x должен быть строго меньше y — мы также можем использовать \u003c вместо lt x gt y оценивается как истинное, только если x строго больше y — мы можем использовать \u003e вместо gt x lte y проверяет, если x меньше или равно y — альтернатива lte — \u003c= x gte y проверяет, больше ли x или равно y — альтернатива gte — \u003e= x?? проверить наличие последовательности x?seqContains(x) проверяет наличие x внутри последовательности

Очень важно иметь в виду, что FreeMarker рассматривает \u003e= и \u003e как закрывающие символы для тега FTL. Решение состоит в том, чтобы заключить их использование в круглые скобки или вместо этого использовать gte или gt.

Собираем все вместе для следующего шаблона:

<#if status??>
    <p>${status.reason}</p>
<#else>
    <p>Missing status!</p>
</#if>

В итоге получаем HTML-код:

 <!-- When status attribute exists -->
<p>404 Not Found</p>

<!-- When status attribute is missing -->
<p>Missing status!</p>

6. Контейнеры подпеременных

Во FreeMarker у нас есть три типа контейнеров для подпеременных:

    «Хеши представляют собой последовательность пар ключ-значение — ключ должен быть уникальным внутри хеша, и у нас нет упорядочения. Последовательности — это списки, в которых у нас есть индекс, связанный с каждым значением — примечательным фактом является то, что -переменные могут быть разных типов. Коллекции — это особый случай последовательностей, где мы не можем получить доступ к размеру или получить значения по индексу — однако мы можем перебирать их с помощью тега списка!

6.1. Перебор элементов

Мы можем перебирать контейнер двумя основными способами. В первом случае мы перебираем каждое значение и выполняем логику для каждого из них:

<#list sequence as item>
    <!-- do something with ${item} -->
</#list>

Или, когда мы хотим перебрать хеш, обращаясь как к ключу, так и к значению:

<#list hash as key, value>
    <!-- do something with ${key} and ${value} -->
</#list>

вторая форма более мощная, потому что она также позволяет нам определить логику, которая должна происходить на различных этапах итерации:

<#list sequence>
    <!-- one-time logic if the sequence is not empty -->
    <#items as item>
        <!-- logic repeated for every item in sequence -->
    </#items>
    <!-- one-time logic if the sequence is not empty -->
<#else>
    <!-- one-time logic if the sequence is empty -->
</#list>

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

Для практического примера определим шаблон, в котором мы перечисляем некоторые статусы:

<#list statuses>
    <ul>
    <#items as status>
        <li>${status}</li>
    </#items>
    </ul>
<#else>
    <p>No statuses available</p>
</#list>

Это вернет нам следующий HTML-код, когда наш контейнер будет [\»200 OK\», \»404 Not Found\» , «500 Internal Server Error»]:

<ul>
<li>200 OK</li>
<li>404 Not Found</li>
<li>500 Internal Server Error</li>
</ul>

6.2. Обработка элементов

Хэш позволяет нам выполнять две простые функции: keys для извлечения только содержащихся ключей и values ​​для извлечения только значений.

Последовательность более сложная; мы можем сгруппировать наиболее полезные функции:

    chunk и join для получения подпоследовательности или объединения двух последовательностей reverse, sort и sortBy для изменения порядка элементов first и last получат первый или последний элемент, соответственно размер представляет количество элементов в последовательности seqContains, seqIndexOf или seqLastIndexOf для поиска элемента

7. Обработка типов

FreeMarker поставляется с огромным набором встроенных функций для работы с объектами. Давайте посмотрим на некоторые часто используемые функции.

7.1. Обработка строк

    url и urlPath будут экранировать строку в URL-адресе, за исключением того, что urlPath не будет экранировать косую черту / jString, jsString и jsonString будут применять правила экранирования для Java, Javascript и JSON, соответственно capFirst, uncapFirst, upperCase, lowerCase и capitalize полезны для изменения регистра нашей строки, как следует из их имен boolean, date, time, datetime и number — это функции для преобразования строки в другие типы

Давайте теперь воспользуемся некоторыми из этих функций: ~ ~~

<p>${'http://myurl.com/?search=Hello World'?urlPath}</p>
<p>${'Using " in text'?jsString}</p>
<p>${'my value?upperCase}</p>
<p>${'2019-01-12'?date('yyyy-MM-dd')}</p>

Вывод для приведенного выше шаблона будет таким:

<p>http%3A//myurl.com/%3Fsearch%3DHello%20World</p>
<p>MY VALUE</p>
<p>Using \" in text</p>
<p>12.01.2019</p>

При использовании функции даты мы также передали шаблон для анализа объекта String. FreeMarker использует локальный формат, если не указано иное, например, в строковой функции, доступной для объектов даты.

7.2. Обработка чисел

    round, Floor и потолок могут помочь с округлением чисел abs вернет абсолютное значение числа string преобразует число в строку. Мы также можем передать четыре предопределенных числовых формата: компьютер, валюта, число или процент или определить свой собственный формат, например [ «0.###»]

Давайте выполним цепочку из нескольких математических операций:

<p>${(7.3?round + 3.4?ceiling + 0.1234)?string('0.##')}</p>
<!-- (7 + 4 + 0.1234) with 2 decimals -->

И, как и ожидалось, результирующее значение равно 11,12.

7.3. Обработка даты

    .now представляет текущую дату-время. используйте предопределенный

Теперь мы собираемся получить текущее время и отформатировать вывод в строку, содержащую только часы и минуты:

<p>${.now?time?string('HH:mm')}</p>

Результирующий HTML будет:

<p>15:39</p>

~~ ~ 8. Обработка исключений

Мы увидим два способа обработки исключений для шаблона FreeMarker.

Первый способ — использовать теги попытки восстановления, чтобы определить, что мы должны попытаться выполнить, и блок кода, который должен выполниться в случае ошибки.

Синтаксис:

<#attempt>
    <!-- block to try -->
<#recover>
    <!-- block to execute in case of exception -->
</#attempt>

Теги попытки и восстановления являются обязательными. В случае ошибки он откатывает предпринятый блок и выполняет только код в разделе восстановления.

Помня об этом синтаксисе, давайте определим наш шаблон как:

<p>Preparing to evaluate</p>
<#attempt>
    <p>Attribute is ${attributeWithPossibleValue??}</p>
<#recover>
    <p>Attribute is missing</p>
</#attempt>
<p>Done with the evaluation</p>

«

<p>Preparing to evaluate</p>
    <p>Attribute is missing</p>
<p>Done with the evaluation</p>

«Когда attributeWithPossibleValue отсутствует, мы увидим:

<p>Preparing to evaluate</p>
    <p>Attribute is 200 OK</p>
<p>Done with the evaluation</p>

И вывод, когда attributeWithPossibleValue существует:

Второй способ — настроить FreeMarker, что должно происходить в случае исключений.

    С Spring Boot мы легко настраиваем это через файл свойств; вот некоторые доступные конфигурации:

spring.freemarker.setting.template_exception_handler=rethrow повторно выдает исключение spring.freemarker.setting.template_exception_handler=debug выводит клиенту информацию о трассировке стека, а затем повторно выдает исключение. spring.freemarker.setting.template_exception_handler=html_debug выводит клиенту информацию о трассировке стека, форматируя ее так, чтобы она обычно хорошо читалась в браузере, а затем повторно выдает исключение. spring.freemarker.setting.template_exception_handler=ignore пропускает ошибочные инструкции, позволяя шаблону продолжить выполнение. spring.freemarker.setting.template_exception_handler=default

9. Вызов методов

Иногда нам нужно вызывать методы Java из наших шаблонов FreeMarker. Сейчас мы посмотрим, как это сделать.

9.1. Статические элементы

model.addAttribute("statics", new DefaultObjectWrapperBuilder(new Version("2.3.28"))
    .build().getStaticModels());

Чтобы начать доступ к статическим элементам, мы могли бы либо обновить нашу глобальную конфигурацию FreeMarker, либо добавить в модель атрибут типа StaticModels под именем атрибута statics:

Доступ к статическим элементам прост.

Сначала мы импортируем статические элементы нашего класса, используя тег assign, затем выбираем имя и, наконец, путь к классам Java.

<#assign MathUtils=statics['java.lang.Math']>
<p>PI value: ${MathUtils.PI}</p>
<p>2*10 is: ${MathUtils.pow(2, 10)}</p>

Вот как мы импортируем класс Math в наш шаблон, показываем значение статического поля PI и используем статический метод pow:

<p>PI value: 3.142</p>
<p>2*10 is: 1,024</p>

Результирующий HTML:

~ ~~ 9.2. Bean Members

Bean Members очень легко получить доступ: используйте точку (.) и все!

model.addAttribute("random", new Random());

В нашем следующем примере мы добавим объект Random в нашу модель:

<p>Random value: ${random.nextInt()}</p>

В нашем шаблоне FreeMarker давайте сгенерируем случайное число:

<p>Random value: 1,329,970,768</p>

Это приведет к выводу, подобному :

9.3. Пользовательские методы

public class LastCharMethod implements TemplateMethodModelEx {
    public Object exec(List arguments) throws TemplateModelException {
        if (arguments.size() != 1 || StringUtils.isEmpty(arguments.get(0)))
            throw new TemplateModelException("Wrong arguments!");
        String argument = arguments.get(0).toString();
        return argument.charAt(argument.length() - 1);
    }
}

Первым шагом для добавления пользовательского метода является наличие класса, который реализует интерфейс FreeMarker TemplateMethodModelEx и определяет нашу логику внутри метода exec:

model.addAttribute("lastChar", new LastCharMethod());

Мы добавим экземпляр нашего нового класса как атрибут модели:

<p>Last char example: ${lastChar('mystring')}</p>

Следующим шагом является использование нашего нового метода внутри нашего шаблона:

<p>Last char example: g</p>

Наконец, в результате получается:

10. Заключение

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