1. Введение
FreeMarker — это механизм шаблонов, написанный на Java и поддерживаемый Apache Foundation. Мы можем использовать язык шаблонов FreeMarker, также известный как FTL, для создания многих текстовых форматов, таких как веб-страницы, электронная почта или XML-файлы.
В этом уроке мы увидим, что мы можем сделать с помощью FreeMarker «из коробки», хотя обратите внимание, что он вполне настраиваемый и даже хорошо интегрируется со Spring .
Давайте начнем!
2. Краткий обзор
Чтобы внедрить динамический контент на наши страницы, нам нужно использовать синтаксис, который понимает FreeMarker :
${…}
в шаблоне будет заменено в сгенерированном выводе фактическим значением выражения внутри фигурных скобок — мы называем этоинтерполяцией —
несколько примеров:${1 + 2
} и${variableName}
- Теги FTL похожи на теги HTML (но содержат
#
или@
), и FreeMarker интерпретирует их, например,<#if…></#if>
- Комментарии в FreeMarker начинаются с
<#–
и заканчиваются-->
3. Тег включения
Директива включения
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
был null
, мы бы увидели:
<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. Тег «если-иначе»
Управляющие структуры присутствуют во 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
y
x != y
, чтобы вернутьtrue
, только еслиx
отличается отy
x lt y
означает, чтоx
должен быть строго меньшеy
— мы также можем использовать<
вместоlt
x gt y
оценивается какtrue
, только еслиx
строго большеy
— мы можем использовать>
вместоgt
x lte y
проверяет , меньше лиx или равно
y
– альтернативойlte
является<=
x gte y
проверяет, больше лиx
или равноy
– альтернативаgte
>=Икс??
проверить существованиеx
sequence?seqContains(x)
проверяет наличиеx
внутри последовательности
Очень важно помнить, что FreeMarker рассматривает >= и > как закрывающие символы для тега 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>
Или, когда мы хотим повторить Hash
, получая доступ как к ключу, так и к значению:
<#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
для извлечения только значений.
Последовательность более сложная; мы можем сгруппировать наиболее полезные функции:
фрагмент
исоединение
, чтобы получить подпоследовательность или объединить две последовательностиreverse
,sort
иsortBy
для изменения порядка элементовfirst
иlast
извлекут первый или последний элемент соответственноsize
представляет количество элементов в последовательности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
,пол
ипотолок
могут помочь с округлением чисел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
представляет текущую дату и времяdate
,time
иdatetime
могут возвращать разделы даты и времени объекта даты и времени.строка
преобразует дату и время в строки — мы также можем передать желаемый формат или использовать предопределенный
Теперь мы собираемся получить текущее время и отформатировать вывод в строку, содержащую только часы и минуты:
<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>
Когда attributeWithPossibleValue
отсутствует, мы увидим:
<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>
Второй способ — настроить 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 = по умолчанию
9. Методы вызова
Иногда мы хотим вызывать методы Java из наших шаблонов FreeMarker. Сейчас мы посмотрим, как это сделать.
9.1. Статические члены
Чтобы начать доступ к статическим членам, мы можем либо обновить нашу глобальную конфигурацию FreeMarker, либо добавить в модель атрибут типа S taticModels
под именем атрибута statics
:
model.addAttribute("statics", new DefaultObjectWrapperBuilder(new Version("2.3.28"))
.build().getStaticModels());
Доступ к статическим элементам прост.
Сначала мы импортируем статические элементы нашего класса, используя тег assign, затем выбираем имя и, наконец, путь к классам Java.
Вот как мы импортируем класс Math
в наш шаблон, показываем значение статического поля PI
и используем статический метод pow :
<#assign MathUtils=statics['java.lang.Math']>
<p>PI value: ${MathUtils.PI}</p>
<p>2*10 is: ${MathUtils.pow(2, 10)}</p>
Результирующий HTML:
<p>PI value: 3.142</p>
<p>2*10 is: 1,024</p>
9.2. Члены бина
Доступ к членам компонента очень прост: используйте точку (.) , и все!
В нашем следующем примере мы добавим в нашу модель объект Random :
model.addAttribute("random", new Random());
В нашем шаблоне FreeMarker сгенерируем случайное число:
<p>Random value: ${random.nextInt()}</p>
Это приведет к выводу, подобному:
<p>Random value: 1,329,970,768</p>
9.3. Пользовательские методы
Первым шагом для добавления пользовательского метода является наличие класса, который реализует интерфейс TemplateMethodModelEx
FreeMarker и определяет нашу логику внутри метода exec :
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);
}
}
Мы добавим экземпляр нашего нового класса в качестве атрибута модели:
model.addAttribute("lastChar", new LastCharMethod());
Следующим шагом будет использование нашего нового метода внутри нашего шаблона:
<p>Last char example: ${lastChar('mystring')}</p>
Наконец, в результате получается:
<p>Last char example: g</p>
10. Заключение
В этой статье мы увидели, как использовать механизм шаблонов FreeMarker внутри нашего проекта. Мы сосредоточились на общих операциях, на том, как манипулировать различными объектами, и на нескольких более сложных темах.
Реализация всех этих фрагментов доступна на GitHub .