FreeMarker шаблоны

от автора

Apache FreeMarker — это механизм шаблонов: библиотека Java для генерации текстового вывода (HTML-страницы, xml, файлы конфигурации, исходный код и.т.д. На вход подается шаблон, например html в котором есть специальные выражения, подготавливаются данные соответствующие этим выражением, а Freemarker динамически вставляет эти данные и получается динамически заполненный документ.
image

В статье FreeMarker
Spring boot
Macros
REST API

Т.е. простое выражение на freemarker это например ${name}, в выражения поддерживаются вычисления, операции сравнения, условия, циклы, списки, встроенные функции, макрос и много др. Пример html с выражением ${name} (шаблон test.ftl)

<!DOCTYPE html> <html lang="en"> <head>     <meta charset="UTF-8">     <title>${name}!</title> </head> <body>    <h2>Hello ${name}!</h2>     </body> </html> 

Если теперь создать в java модель данных

import freemarker.template.Configuration; import freemarker.template.Template; ... // Конфигурация Configuration cfg = new Configuration(Configuration.VERSION_2_3_27); // модель данных Map<String, Object> root = new HashMap<>(); root.put("name", "Freemarker"); // шаблон Template temp = cfg.getTemplate("test.ftl"); // обработка шаблона и модели данных Writer out = new OutputStreamWriter(System.out); // вывод в консоль temp.process(root, out); 

то получим html документ с заполненным name.
Если надо обработать список, то используется конструкция #list, например для html списка

<ul>   <#list father as item>       <li>${item}</li>   </#list> </ul> 

В java, в модель данных подать список можно так
Map<String, Object> root = new HashMap<>();
....
root.put("father", Arrays.asList("Alexander", "Petrov", 47));

Перейдем к Spring

В Spring boot есть поддержка Freemarker. На сайте SPRING INITIALIZR можно получить pom файл проекта.

pom файл

<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 	<modelVersion>4.0.0</modelVersion>  	<groupId>com.example</groupId> 	<artifactId>demoFreeMarker</artifactId> 	<version>0.0.1-SNAPSHOT</version> 	<packaging>jar</packaging>  	<name>demoFreeMarker</name> 	<description>Demo project for Spring Boot</description>  	<parent> 		<groupId>org.springframework.boot</groupId> 		<artifactId>spring-boot-starter-parent</artifactId> 		<version>2.0.4.RELEASE</version> 		<relativePath/> <!-- lookup parent from repository --> 	</parent>  	<properties> 		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> 		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> 		<java.version>1.8</java.version> 	</properties>  	<dependencies> 		<dependency> 			<groupId>org.springframework.boot</groupId> 			<artifactId>spring-boot-starter-freemarker</artifactId> 		</dependency>  		<dependency> 			<groupId>org.springframework.boot</groupId> 			<artifactId>spring-boot-devtools</artifactId> 			<scope>runtime</scope> 		</dependency> 		<dependency> 			<groupId>org.springframework.boot</groupId> 			<artifactId>spring-boot-starter-test</artifactId> 			<scope>test</scope> 		</dependency> 	</dependencies>  	<build> 		<plugins> 			<plugin> 				<groupId>org.springframework.boot</groupId> 				<artifactId>spring-boot-maven-plugin</artifactId> 			</plugin> 		</plugins> 	</build>   </project> 

класс DemoFreeMarkerApplication

@SpringBootApplication public class DemoFreeMarkerApplication {  	public static void main(String[] args) { 		SpringApplication.run(DemoFreeMarkerApplication.class, args); 	} } 

В Spring есть уже подготовленный компонент конфигурации Configuration для freemarker.
Для примера консольного приложения возьму spring интерфейс для обработки командной строки(CommandLineRunner) и подготовлю модель данных для следующего шаблона ftl (hello_test.ftl)

Шаблон hello_test.ftl

<!DOCTYPE html> <html lang="en"> <head>     <meta charset="UTF-8">     <title>Hello ${name}!</title> </head> <body>  <input type="text" placeholder="${name}">  <table>     <#list persons as row>     <tr>         <#list row as field>             <td>${field}</td>         </#list>     </tr>     </#list> </table>  </body> </html> 

Java код для модели данных шаблона hello_test.ftl

класс CommandLine и модель данных

@Component public class CommandLine implements CommandLineRunner {      @Autowired     private Configuration configuration;      public void run(String... args) {         Map<String, Object> root = new HashMap<>();         // для ${name}         root.put("name", "Fremarker");         // для <#list persons         List<List> persons = new ArrayList<>();         persons.add(Arrays.asList("Alexander", "Petrov", 47));         persons.add(Arrays.asList("Slava", "Petrov", 13));         root.put("persons", persons);          try {             Template template = configuration.getTemplate("hello_test.ftl");             Writer out = new OutputStreamWriter(System.out);             try {                 template.process(root, out);             } catch (TemplateException e) {                 e.printStackTrace();             }         } catch (IOException e) {             e.printStackTrace();         }     } } 

После обработки получим html документ

Output html

<!DOCTYPE html> <html lang="en"> <head>     <meta charset="UTF-8">     <title>Hello Fremarker!</title> </head> <body>  <input type="text" placeholder="Fremarker">  <table>     <tr>             <td>Alexander</td>             <td>Petrov</td>             <td>47</td>     </tr>     <tr>             <td>Slava</td>             <td>Petrov</td>             <td>13</td>     </tr> </table> </body> 

Макросы

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

<#macro textInput id value="">   <input type="text" id="${id}" value="${value}"> </#macro>

Это макрос с именем textInput и параметрами id (он обязательный) и value (он не обязательный, т.к. имеет значение по умолчанию). Далее идет его тело и использование входных параметров. В шаблоне файл с макросами подключается так:

<#import "ui.ftl" as ui/>

Из шаблона макрос вызывается так:

<@ui.textInput id="name" value="${name}"/>

Где ui это алиас который указали при подключении, ${name} переменная в модели, далее через алиас ссылаемся на имя макроса textInput и указываем его параметры, как минимум обязательные. Подготовлю простые макросы для html Input и Table

файл макросов ui.ftl

<#-- textInput macro for html input --> <#macro textInput id placeholder="" value="">   <input type="text" id="${id}" placeholder="${placeholder}" value="${value}"> </#macro>  <#-- table macro for html table --> <#macro table id rows> <table id="${id}">     <#list rows as row>     <tr>         <td>${row?index + 1}</td>         <#list row as field>             <td>${field}</td>         </#list>     </tr>     </#list> </table> </#macro>

${row?index + 1} это встроенная поддержка индекса элемента списка, подобных встроенных функций много. Если теперь изменить предыдущий основной шаблон и заменить в нем input и table на макросы, то получится такой документ

Шаблон hello.ftl

<#import "ui.ftl" as ui/>  <!DOCTYPE html> <html lang="en"> <head>     <meta charset="UTF-8">     <title>Hello ${name}!</title> </head> <body>  <@ui.textInput id="name" placeholder="Enter name" value="${name}"/> <@ui.table id="table1" rows=persons/>  </body> </html> 

REST

Конечно такую модель удобно использовать в web приложении. Подключаю зависимость в pom

		<dependency> 			<groupId>org.springframework.boot</groupId> 			<artifactId>spring-boot-starter-web</artifactId> 		</dependency> 

Добавляю REST Controller

DemoController.java

@Controller public class DemoController {      @Autowired     private RepositoryService repositoryService;      @GetMapping("/")     public String index() {         return "persons";     }      @RequestMapping(value = "/search", method = RequestMethod.POST)     public String hello(Model model, @RequestParam(defaultValue = "") String searchName) {         List<List<String>> persons = repositoryService.getRepository();         List<List<String>> filterList = persons.stream()                 .filter(p -> p.get(0).contains(searchName))                 .collect(Collectors.toList());         model.addAttribute("persons", filterList);         model.addAttribute("lastSearch", searchName);         return "persons";     }      @RequestMapping(value = "/save", method = RequestMethod.POST)     public String save(Model model, @ModelAttribute("person") Person person) {         List<List<String>> persons = repositoryService.addPerson(person);         model.addAttribute("persons", persons);         return "persons";     } } 

Service репозиторий для лиц

RepositoryService.java

@Service public class RepositoryService {      private static List<List<String>> repository = new ArrayList<>();      public List<List<String>> getRepository() {         return repository;     }      public List<List<String>> addPerson(Person person) {         repository.add(Arrays.asList(person.getFirstName(), person.getAge().toString()));         return repository;     } } 

Класс лицо

Person.java

public class Person {      public Person(String firstName, Integer age) {         this.firstName = firstName;         this.age = age;     }      private String firstName;     private Integer age;      public String getFirstName() {         return firstName;     }      public Integer getAge() {         return age;     } } 

Шаблон макросов

ui.ftl

<#macro formInput id name label type="text" value=""> <label for="${id}">${label}</label> <input type="${type}" id="${id}" name="${name}" value="${value}"> </#macro>  <#macro table id rows> <table id="${id}" border="1px" cellspacing="2" border="1" cellpadding="5">     <#list rows as row>         <tr>             <td>${row?index + 1}</td>             <#list row as field>                 <td>${field}</td>             </#list>         </tr>     </#list> </table> </#macro> 

Основной шаблон

persons.ftl

<#import "ui.ftl" as ui/>  <!DOCTYPE html> <html lang="en"> <head>     <meta charset="UTF-8">     <title>Person</title>     <link href="style/my.css" rel="stylesheet"> </head> <body>  <div>     <fieldset>         <legend>Добавить лицо</legend>         <form name="person" action="save" method="POST">             <@ui.formInput id="t1" name="firstName" label="Имя"/> <br/>             <@ui.formInput id="t2" name="age" label="Возраст"/> <br/>             <input type="submit" value="Save" />         </form>     </fieldset> </div>  <div>     <fieldset>         <legend>Поиск</legend>         <form name="searchForm" action="search" method="POST">         <@ui.formInput id="t3" name="searchName" label="Поиск"/> <br/>             <input type="submit" value="Search" />         </form>     </fieldset> </div> <p><#if lastSearch??>Поиск для: ${lastSearch}<#else></#if></p>  <@ui.table id="table1" rows=persons![]/>  </body> </html> 

Структура проекта
image

Приложение будет обрабатывать две команды «save» и «search» лица (см. контроллер). Всю работу по обработке (мапингу) входных параметров, берет на себя Spring.

Некоторые пояснения к шаблону.
<#if lastSearch??>Поиск для: ${lastSearch}<#else></#if>
здесь проверяется, если параметр задан, то вывести фразу «Поиск для: ..», иначе ничего
<@ui.table id="table1" rows=persons![]/>
здесь тоже сделана проверка, что список лиц присутствует, иначе пустой. Эти проверки важны при первом открытии страницы, иначе пришлось бы их инициализировать в index(), контроллера.

Работа приложения
image

Материалы:
Apache FreeMarker Manual


ссылка на оригинал статьи https://habr.com/post/420549/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *