Apache FreeMarker — це механізм шаблонів: бібліотеки Java для генерації текстового виводу (HTML-сторінки, xml, конфігураційні файли, вихідний код і. т. д. На вхід подається шаблон, наприклад html в якому є спеціальні вирази, підготовляються дані відповідні цим виразом, а Freemarker динамічно вставляє ці дані і виходить динамічно заповнений документ.
У статті 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(Конфігурація.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.звітування.outputEncoding>UTF-8</project.звітування.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>
Структура проекту
Додаток буде обробляти дві команди «save» і «search» особи (див. контролер). Всю роботу по обробці (мапінгу) вхідних параметрів, що бере на себе Spring.
Деякі пояснення до шаблону.<#if lastSearch??>Пошук: ${lastSearch}<#else></#if>
тут перевіряється, якщо параметр заданий, то вивести фразу «Пошук для: ..», інакше нічого<@ui.table id="table1" rows=persons![]/>
тут теж зроблена перевірка, що список осіб присутній, інакше порожній. Ці перевірки важливі при першому відкритті сторінки, інакше довелося б їх ініціалізувати в index(), контролера.
Робота програми
Матеріали:
Apache FreeMarker Manual