FreeMarker
参考文档:Freemarker 教程网
1. 介绍
Apache FreeMarker 是一个模板引擎,它仅是一个jar包,基于模板,生成文本输出(HTML网页、电子邮件、配置文件、源代码等) 。
1<dependency>
2 <groupId>org.freemarker</groupId>
3 <artifactId>freemarker</artifactId>
4 <version>2.3.31</version>
5 </dependency>
原理如下:
FreeMarker 模板组成部分
FreeMarker 模板文件主要由如下4个部分组成:
- 文本:直接输出的部分
- 注释:使用
<#-- ... -->
格式做注释,里面内容不会输出 - 插值:即
${...}
或#{...}
格式的部分,类似于占位符,将使用数据模型中的部分替代输出 - FTL指令:即 FreeMarker 指令,全称是:FreeMarker Template Language,和HTML标记类似,但名字前加#予以区分,不会输出。
下面是一个 FreeMarker 模板的例子,包含了以上所说的4个部分:
1<html>
2<head>
3 <title>Welcome to FreeMarker 中文官网</title><br>
4</head>
5<body>
6 <#-- 注释部分 -->
7 <#-- 下面使用插值 -->
8 <h1>Welcome ${user} !</h1><br>
9 <p>We have these animals:<br>
10 <u1>
11 <#-- 使用FTL指令 -->
12 <#list animals as being><br>
13 <li>${being.name} for ${being.price} Euros<br>
14 <#list>
15 <u1>
16</body>
17</html>
2. 快速入手
FreeMarker 使用步骤
templates/hello.ftl
1<#ftl attributes={"content_type":"text/html; charset=UTF-8"}>
2<?xml version="1.0" encoding="utf-8"?>
3<html>
4<head>
5 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
6 <title>Rainsheep</title>
7</head>
8<body>
9${words}
10</body>
11</html>
1public class FreeMarkerGenerator {
2 public static void main(String[] args) throws Exception {
3 // 第一步:创建一个Configuration对象,直接new一个对象。构造方法的参数就是FreeMarker对于的版本号。
4 Configuration configuration = new Configuration(Configuration.getVersion());
5 // 获取 classes 目录
6 String classPath = FreeMarkerGenerator.class.getResource("/").getPath();
7 // 第二步:设置模板文件所在的路径。
8 configuration.setDirectoryForTemplateLoading(new File(classPath, "templates"));
9 // 第三步:设置模板文件使用的字符集。一般就是utf-8.
10 configuration.setDefaultEncoding("UTF-8");
11 // 第四步:加载一个模板,创建一个模板对象。
12 Template template = configuration.getTemplate("hello.ftl");
13 // 第五步:创建一个模板使用的数据集,可以是pojo也可以是map。一般是Map。
14 HashMap<String, String> map = new HashMap<>();
15 map.put("words", "hello world");
16 // 第六步:创建一个Writer对象,一般创建一FileWriter对象,指定生成的文件名。
17 FileWriter out = new FileWriter(classPath + "/templates/hello.html");
18 // 第七步:调用模板对象的process方法输出文件。
19 template.process(map, out);
20 out.close();
21 }
22}
生成的 hello.html
1<html>
2<head>
3 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
4 <title>Rainsheep</title>
5</head>
6<body>
7hello world
8</body>
9</html>
3. assign
assign 指令用于为该模板页面创建或替换一个顶层变量,或者创建或替换多个变量等。它的最简单的语法如下:
1<#assign name=value [in namespacehash]>
这个用法用于指定一个名为 name 的变量,该变量的值为 value。此外,FreeMarker 允许在使用 assign 指令里增加 in 子句。in 子句用于将创建的 name 变量放入 namespacehash 命名空间中。
FreeMarker assign 指令用于在页面上定义一个变量,而变量又分为下面几种类型:
1// 简单类型
2<#assign name="Tom" email="11@qq.com">
3// 序列
4<#assign seq = ["foo", "bar", "baz"]>
5// 对象类型
6<#assign info={"mobile":"xxxxxx","address":"china"} >
4. if 和 null
if 和 null
FreeMarker 中显示某对象使用 ${name},但如果 name 为 null,FreeMarker 就会报错。需要进行空值判断,例如需要判断对象是否为空:
1<#if mouse??>
2 Mouse found
3<#else>
4 No mouse found
5</#if>
6Creating mouse...
7<#assign mouse = "Jerry">
8<#if mouse??>
9 Mouse found
10<#else>
11 No mouse found
12</#if>
生成文件
1No mouse found
2Creating mouse...
3Mouse found
默认
若 name 为 null 则为 aa
1${(name)!'aa'}
5. 遍历
简单遍历 list:
1<#list userList as user>
2 用户名:${user.userName},密码:${user.userPassword},年龄: ${user.age}
3</#list>
FreeMarker遍历list并应用list隐含变量item_index:
1<#list userList as user>
2 第${user_index+1}个用户用户名:${user.userName},密码:${user.userPassword},年龄: ${user.age}
3</#list>
FreeMarker遍历list并应用list隐含变量item_has_next:
1<#list userList as user>
2 用户名:${user.userName},密码:${user.userPassword},年龄: ${user.age}
3 <#if !user_has_next>共有${userList?size},最后一个用户是:${user.userName}</#if>
4</#list>
sort对序列(sequence)进行排序,要求序列中的变量必须是:字符串(按首字母排序),数字,日期值。
1<#list list?sort as l>
sort_by函数
1sort_by 有一个参数,该参数用于指定想要排序的子变量,排序是按照变量对应的值进行排序,如:
2<#list userList?sort_by("age") as user>。age是User对象的属性,排序是按age的值进行的。
reverse降序排序
1函数<#list list?reverse as l>,reverse使用同sort相同。reverse还可以同sort_by一起使用如:想让用户按年龄降序排序,那么可以这个样写
2<#list userList?sort_by("age")?reverse as user>。
FreeMarker遍历list当用户年龄大于21岁时,停止输出,list中应用break:
1<#list userList?sort_by("age")?reverse as user>
2 用户名:${user.userName}密码:${user.userPassword}年龄: ${user.age}
3 <#if (user.age>21) ><#break></#if>
4</#list>
6. macro 宏定义
6.1 基本用法
宏是和某个变量关联的模板片断,以便在模板中通过用户定义的指令使用该变量,而该变量表示模板片段。宏在 FreeMarker 模板中使用 macro 指令定义。
定义宏
1<#macro greet>
2 <font size="+2">Hello World!</font>
3</#macro>
使用宏
1<@greet></@greet>
6.2 宏变量定义参数
1<#macro greet person>
2 <font size="+2">Hello ${person}!</font>
3</#macro>
使用
1<@greet person="Fred" />
可以定义多个参数
1<#macro greet person color>
2 <font size="+2" color="${color}">Hello ${person}!</font>
3</#macro>
可以指定缺省值
1<#macro greet person color="black">
2 <font size="+2" color="${color}">Hello ${person}!</font>
3</#macro>
6.3 宏的嵌套
宏可以有嵌套内容,<#nested> 指令会执行宏调用指令开始和结束标记之间的模板片断,举一个简单的例子:
1<#macro border>
2 <table border=4 cellspacing=0 cellpadding=4>
3 <tr>
4 <td>
5 <#nested>
6 </td>
7 </tr>
8 </table>
9</#macro>
执行宏调用: <@border>Hello World!</@border>
,输出结果:
1<table border=4 cellspacing=0 cellpadding=4>
2<tr>
3<td>
4 Hello World!
5</td>
6</tr>
7</table>
<#nested> 指令可以被多次调用,每次都会执行相同的内容。
宏定义中的局部变量对嵌套内容是不可见的,例如:
1<#macro repeat count>
2 <#local y = "test">
3 <#list 1..count as x>
4 ${y} ${count}/${x}: <#nested>
5 </#list>
6 </#macro>
7
8<@repeat count=3>${y?default("?")} ${x?default("?")} ${count?default("?")}</@repeat>
输出
1test 3/1: ? ? ?
2test 3/2: ? ? ?
3test 3/3: ? ? ?
7. 命名空间
通常情况,FreeMarker只使用一个命名空间,称为主命名空间,但为了创建可重用的宏或其它变量的集合(通常称库),必须使用多命名空间,其目的是防止同名冲突。
创建库
下面是一个创建库的例子(假设保存在lib/my_test.ftl中):
1<#macro copyright date>
2 <p>Copyright (C) ${date} FreeMarker中文官网. All rights reserved.
3 <br>Email: ${mail}
4 </p>
5</#macro>
6<#assign mail = "admin@FreeMarker.cn">
代码说明:上面的库定义了一个宏变量和一个普通变量mail。
导入库
使用import指令导入库到模板中,FreeMarker会为导入的库创建新的名字空间,并可以通过import指令中指定的散列变量访问库中的变量,如下所示:
1<#import "/lib/my_test.ftl" as my>
2
3<#assign mail="root@FreeMarker.cn">
4
5<@my.copyright date="2000-2020"/>
6
7${my.mail}
8
9${mail}
输出结果:
1<p>Copyright (C) 2000-2020 FreeMarker中文官网. All rights reserved.
2 <br>Email: admin@FreeMarker.cn
3</p>
4
5admin@FreeMarker.cn
6
7root@FreeMarker.cn
可以看到例子中使用的两个同名变量并没有冲突,因为它们位于不同的名字空间。还可以使用assign指令在导入的命名空间中创建或替代变量,下面是一个例子:
1<#import "/lib/my_test.ftl" as my>
2
3${my.mail}
4
5<#assign mail="root@other.com" in my>
6
7${my.mail}
输出结果
1admin@FreeMarker.cn
2
3root@other.com
8. 变量
在模板中定义的变量有三种类型:
- plain变量:可以在模板的任何地方访问,包括使用include指令插入的模板,使用assign指令创建和替换。
- 局部变量:在宏定义体中有效,使用local指令创建和替换。
- 循环变量:只能存在于指令的嵌套内容,由指令(如list)自动创建;宏的参数是局部变量,而不是循环变量
局部变量隐藏(而不是覆盖)同名的plain变量;循环变量隐藏同名的局部变量和plain变量,下面是一个例子:
1<#assign x = "plain">
21.${x}
3
4<#macro test>
5 2. ${x}
6 <#local x = "local">
7 3. ${x}
8 <#list ["loop"] as x>
9 4. ${x}
10 </#list>
11 5. ${x}
12</#macro>
13
14<@test/>
15
166. ${x}
17
18<#list ["loop"] as x>
19 7. ${x}
20 <#assign x = "plain2">
21 8. ${x}
22</#list>
23
249. ${x}
输出
11. plain
22. plain
33. local
44. loop
55. local
66. plain
77. loop
88. loop
99. plain2
内部循环变量隐藏同名的外部循环变量,如:
1<#list ["loop1"] as x>
2 ${x}
3 <#list ["loop2"] as x>
4 ${x}
5 <#list ["loop3"] as x>
6 ${x}
7 </#list>
8 ${x}
9 </#list>
10 ${x}
11</#list>
输出
1loop1
2 loop2
3 loop3
4 loop2
5loop1
9. 模板和数据模型
为模板准备的数据整体被称作为数据模型。数据模型是树形结构,就像硬盘上的文件夹和文件,在视觉效果上, 数据模型可以是:
1(root)
2 |
3 +- user = "Big Joe"
4 |
5 +- latestProduct
6 |
7 +- url = "products/greenmouse.html"
8 |
9 +- name = "green mouse"
模板 + 数据模型 = 输出,但是模板中的变量会隐藏(而不是覆盖)数据模型中同名变量,如果需要访问数据模型中的同名变量,使用特殊变量global,下面的例子假设数据模型中的user的值是Big Joe:
1<#assign user = "Joe Hider">
2
3${user} <#-- prints: Joe Hider -->
4${.global.user} <#-- prints: Big Joe -->