参考文档:Freemarker 教程网

1. 介绍

Apache FreeMarker 是一个模板引擎,它仅是一个jar包,基于模板,生成文本输出(HTML网页、电子邮件、配置文件、源代码等) 。

<dependency>
 	<groupId>org.freemarker</groupId>
 	<artifactId>freemarker</artifactId>
 	<version>2.3.31</version>
 </dependency>

原理如下:

overview

FreeMarker 模板组成部分

FreeMarker 模板文件主要由如下4个部分组成:

  1. 文本:直接输出的部分
  2. 注释:使用 <#-- ... -->格式做注释,里面内容不会输出
  3. 插值:即 ${...}#{...} 格式的部分,类似于占位符,将使用数据模型中的部分替代输出
  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 -->