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