FreeMarker

2022-01-15/2022-01-15

参考文档: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>

原理如下:

overview

FreeMarker 模板组成部分

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

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