环境

  • 开发工具

    • IDEA :STS

    • JDK:1.8

  • freemarker Version:2.3.28

  • 这里将通过maven项目进行说明我们的用法。首先引入freemarker 依赖到我们的maven项目中

    1
    2
    3
    4
    5
    <dependency>
    <groupId>org.freemarker</groupId>
    <artifactId>freemarker</artifactId>
    <version>2.3.28</version>
    </dependency>

freemarker 用法之 Hello word

在resource目录下创建template目录并添加helloworld.ftl 模板文件 内容如下:

1
2
3
4
5
6
7
8
<html>
<head>
<title>hello world</title>
</head>
<body>
<h1>this is ${who} hello World</h1>
</body>
</html>

创建配置实例 并将模板和数据进行输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package cn.lijunkui.examples;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.HashMap;
import java.util.Map;

import org.junit.Test;

import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;

public class FreemarkerDemo {
@Test
public void helloWord() throws IOException, TemplateException {

Configuration cfg = new Configuration(Configuration.VERSION_2_3_28);
//指定模板文件的来源
String path = FreemarkerDemo.class.getClassLoader().getResource("template").getPath();
cfg.setDirectoryForTemplateLoading(new File(path));
//这是模板的编码
cfg.setDefaultEncoding("UTF-8");
//获取模板
Template template = cfg.getTemplate("helloworld.ftl");
//创建FreeMarker的数据模型
Map<String,String> root = new HashMap<String,String>();
root.put("who","freemarker");
//这是输出文件
File file = new File("D://" +"helloWord.html");
Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file)));
//将模板与数据模型合并
template.process(root, out);
out.flush();
out.close();
}
}

可以看到HTML文件

Data-model:数据模型

hello word的示例其实就是 Template + data-model = output 接下来我们引用一下官方的示例介绍一下数据模型

数据模型是树状结构

1
2
| + - user = "Big Joe" | + - latestProduct | + - url = "/posts/111" | + - name =
"最新文章"

以上只是一个可视化; 数据模型不是文本格式,而是来自Java对象。对于Java程序员来说,root可能是带有getUser() 和getLatestProduct()方法的Java对象,或者是Map带有”user”和”latestProducts”键的Java 。同样, latestProduct也许是一个Java对象 getUrl()和getName() 方法。

1
2
3
4
5
6
7
8
9
10
11
12
<html>
<head>
<title>欢迎!</title>
</head>
<body>
<h1>欢迎${user}!</h1>
<p>
我们的最新产品:
<a href="${latestProduct.url}"> ${latestProduct.name} </a>
</p>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Test
public void dataModel() throws IOException, TemplateException {
Configuration cfg = new Configuration(Configuration.VERSION_2_3_28);
//指定模板文件的来源
String path = FreemarkerDemo.class.getClassLoader().getResource("template").getPath();
cfg.setDirectoryForTemplateLoading(new File(path));
//这是模板的编码
cfg.setDefaultEncoding("UTF-8");
//获取模板
Template template = cfg.getTemplate("data-model.ftl");
//创建FreeMarker的数据模型
Map<String,Object> root = new HashMap<String,Object>();
root.put("user","Big Joe");
Product product = new Product();
product.setName("绿色鼠标");
product.setUrl("products/greenmouse.html");
root.put("latestProduct",product);
//这是输出文件
File file = new File("D://" +"dataModel.html");
Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file)));
//将模板与数据模型合并
template.process(root, out);
out.flush();
out.close();
}

freemarker 注释

我们可以通过 <#– 注释内容 –> 来进行注释 如下图

freemarker 条件指令

我们可以通过<#if> <#elseIf> <#else> 进行条件判断逻辑处理 具体操作如下

条件指令模板内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<html>
<head>
<title>条件指令介绍!</ title>
</head>
<body>
<#-- 条件指令介绍 -->
<#if (5 > 4) >
5 比较大
</#if>
-------------------------------
<#if (!false)>
!false == true
</#if>
-----------------------------
<#if ("a" == "b")>
a 和 b 相同
<#else>
a 和 b 不相同
</#if>
-----------------------------
<#if ( c == d)>
c 和 d 相同
<#elseif ("a" != "b")>
c 和 d 不相同
<#else>
出错了!
</#if>
</body>
</html>

条件指令测试用例:

这里我们对测试用例进行了封装,后面的测试用例都将调用 initTemplate 来进行测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Test
public void condition() throws IOException, TemplateException {
Map root = new HashMap();
root.put("c", "c");
root.put("d", "d");
initTemplate("condition",root);
}

public void initTemplate(String templateName,Map root) throws IOException, TemplateException {
Configuration cfg = new Configuration(Configuration.VERSION_2_3_28);
//指定模板文件的来源
String path = FreemarkerDemo.class.getClassLoader().getResource("template").getPath();
cfg.setDirectoryForTemplateLoading(new File(path));
//这是模板的编码
cfg.setDefaultEncoding("UTF-8");
//获取模板
Template template = cfg.getTemplate(templateName+".ftl");
//创建FreeMarker的数据模型

//这是输出文件
File file = new File("D://" +templateName+".html");
Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file)));
//将模板与数据模型合并
template.process(root, out);
out.flush();
out.close();
}

freemarker list 指令

我们可以通过 <#list 序列 as item> 来进行序列的遍历。另外list 还有一些内置的序列的函数

  • ?size:序列的数量
  • _index :序列中元素的角标
  • _has_next:是否是当前迭代循环中的最后一项
  • ?sort:对序列中的元素进行排序
  • ?sort_by:根据实例中的具体的否个字段进行排序
  • ?is_enumerable:是否是集合

测试list模板内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
<html>
<head>
<title>list 指令介绍!</ title>
</head>
<body>
<#list 0..5 as item>
${item}
</#list>
-----------------------------
_index:交标值 ,?size 获取集合的长度
list的长度:${wordList?size}
<#list wordList as word>
当前的交标是:${word_index}值是:${word}
</#list>
--------------------------------
_has_next:是否是当前迭代循环中的最后一项
<#list wordList as word>
<#if word_has_next>
不是最后一项:${word},
<#else>
是最后一项:${word}
</#if>
</#list>
---------------------------------
字符串(按首字母排序),数字,日期值
正常遍历
<#list wordList as word>
${word}
</#list>
升序
<#list wordList?sort as word>
${word}
</#list>
降序
<#list wordList?sort?reverse as word>
${word}
</#list>
反向遍历
<#list wordList?reverse as word>
${word}
</#list>

-------------------------------------
正常遍历
<#list productList as p>
${p.name}#${p.url}#${p.saleNum}
</#list>
升序
<#list productList?sort_by("saleNum") as p>
${p.name}#${p.url}#${p.saleNum}
</#list>
降序
<#list productList?sort_by("saleNum")?reverse as p>
${p.name}#${p.url}#${p.saleNum}
</#list>
反向遍历
<#list productList?reverse as p>
${p.name}#${p.url}#${p.saleNum}
</#list>
---------------------------------------
<#list map?keys as item>
<#if (item == "productMap3")>
<#list map[item] as p>
${p.name}#${p.url}#${p.saleNum}
</#list>
<#else>
${map[item]}
</#if>
</#list>
----------------------------------------
?is_string:是否是字符串
<#list map?keys as item>
<#if map[item]?is_string>

${map[item]}
<#else>
<#list map[item] as p>
${p.name}#${p.url}#${p.saleNum}
</#list>
</#if>
</#list>
-----------------------------------------
?is_enumerable:是否是集合
<#list map?keys as item>
<#if map[item]?is_enumerable>
<#list map[item] as p>
${p.name}#${p.url}#${p.saleNum}
</#list>
<#else>
${map[item]}
</#if>
</#list>
</body>
</html>

list模板内容测试用例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Test
public void list() throws IOException, TemplateException {
Map root = new HashMap();
List wordList = new ArrayList();
wordList.add(5);
wordList.add(3);
wordList.add(6);
root.put("wordList", wordList);

List<Product> productList = new ArrayList<Product>();
productList.add(new Product("123.html", "苹果", 5));
productList.add(new Product("13.html", "香蕉", 3));
productList.add(new Product("13.html", "芒果", 15));
root.put("productList", productList);

Map map = new HashMap();
map.put("productMap", "a");
map.put("productMap2", "b");
map.put("productMap3", productList);
root.put("map", map);

initTemplate("list",root);
}

list模板内容测试结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
<html>
<head>
<title>list 指令介绍!</ title>
</head>
<body>
0
1
2
3
4
5
-----------------------------
_index:交标值 ,?size 获取集合的长度
list的长度:3
当前的交标是:0值是:5
当前的交标是:1值是:3
当前的交标是:2值是:6
--------------------------------
_has_next:是否是当前迭代循环中的最后一项
不是最后一项:5,

不是最后一项:3,

是最后一项:6

---------------------------------
字符串(按首字母排序),数字,日期值
正常遍历
5
3
6
升序
3
5
6
降序
6
5
3
反向遍历
6
3
5

-------------------------------------
正常遍历
苹果#123.html#5
香蕉#13.html#3
芒果#13.html#15
升序
香蕉#13.html#3
苹果#123.html#5
芒果#13.html#15
降序
芒果#13.html#15
苹果#123.html#5
香蕉#13.html#3
反向遍历
芒果#13.html#15
香蕉#13.html#3
苹果#123.html#5
---------------------------------------
a
b
苹果#123.html#5
香蕉#13.html#3
芒果#13.html#15
----------------------------------------

a

b
苹果#123.html#5
香蕉#13.html#3
芒果#13.html#15
-----------------------------------------
a
b
苹果#123.html#5
香蕉#13.html#3
芒果#13.html#15
</body>
</html>

freemarker assign指令

我们可以通过 assign指令在ftl中进行值的定义

assign指令测试模板内容:

1
2
3
4
5
<#assign name="zhuoqianmingyue">
${name}
-----------------------------------------------------------
<#assign product={"name":"苹果","url":"123.html","saleNum":23} >
${product.name},${product.url},${product.saleNum}

assign指令测试用例:

1
2
3
4
5
@Test
public void assign() throws IOException, TemplateException {
Map root = new HashMap();
initTemplate("assign",root);
}

assign指令测试结果:

1
2
zhuoqianmingyue -----------------------------------------------------------
苹果,123,23

创建宏

这里的宏 我们可以理解成创建方法。我们可以通过 <#macro>标签来定义宏。

创建宏模板内容如下

1
2
3
<#macro fruit name>
我是水果:${name}
</#macro>
1
2
<#include "marco.ftl">
<@fruit name='苹果'></@fruit>

创建宏模板测试用例:

1
2
3
4
5
@Test
public void marco() throws IOException, TemplateException {
Map map = new HashMap();
initTemplate("macroShow", map);
}

创建宏模板测试结果:

1
我是水果:苹果

嵌套指令<#nested> 可以执行两次相同的调用

1
2
3
4
<#macro jieba>
<#nested>
<#nested>
</#macro>
1
2
3
<#include "nested.ftl">
<@jieba>我叫</@jieba>
小明
1
2
3
4
5
@Test
public void nested() throws IOException, TemplateException {
Map map = new HashMap();
initTemplate("nestedShow", map);
}
1
我叫我叫 小明

include指令

include引入的文件内容freemarker将解析其中的freemarker语法并移交给模板,同时assign的值可以互相调用

include 引入ftl模板

  • parent.ftl
1
2
3
4
5
<html>
<body>
我是公共的页面 ${who}引用啦我!
</body>
</html>
  • include.ftl
1
我是include页面 <#assign who="include.ftl"> <#include "parent.ftl"/>

include指令的测试结果:

1
2
3
4
5
6
我是include页面
<html>
<body>
我是公共的页面 include.ftl引用啦我!
</body>
</html>
  • include 引入html

  • 在resource 目录下创建include.html

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <!doctype html>
    <html>
    <head>
    <title>Insert title here</title>
    <meta charset="UTF-8" />
    </head>
    <body>
    我是include.html
    </body>
    </html>

模板内容:include2.ftl

1
<#include "include.html" />

测试用例:

1
2
3
4
5
@Test
public void include2() throws IOException, TemplateException {
Map map = new HashMap();
initTemplate("include2", map);
}

测试结果:include2.html

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html>
<head>
<title>Insert title here</title>
<meta charset="UTF-8">
</head>
<body>
我是include.html
</body>
</html>

import指令

import引入的文件内容freemarker将不会解析其中的freemarker语法,同时assign的值可以可以互相调用。他可以创建一个命名空间 通过该命名调用import 模板中的变量和宏

  • parent2.ftl

    1
    <#assign fruits="香蕉">
  • parent.ftl

    1
    2
    3
    4
    5
    <#assign fruits="苹果">
    <#if (5 > 2) >
    5比较大
    </#if>
    parent.ftl
  • importFun.ftl

    1
    2
    <#import "parent.ftl" as parent> <#import "parent2.ftl" as parent2>
    ${parent.fruits} ${parent2.fruits}
1
2
3
4
5
@Test
public void importFun() throws IOException, TemplateException {
Map map = new HashMap();
initTemplate("importFun", map);
}
1
香蕉 苹果

import 使用宏

  • marco.ftl
1
2
3
<#macro fruit name>
我是水果:${name}
</#macro>
1
<#import "marco.ftl" as m> <@m.fruit name='苹果' />
1
2
3
4
5
@Test
public void macroImportShow() throws IOException, TemplateException {
Map map = new HashMap();
initTemplate("macroImportShow", map);
}
1
我是水果:苹果

freemarker 处理不存在或则为null的值

当数据模型的key不存在或者key 的value是null 时 我们执行模板引擎进行渲染的化或报如下图的错误

freemarker.log._JULLoggerFactory$JULLogger error Error executing FreeMarker template

我们可以通过在flt页面中使用! 或者 ?if_exists 使其不做任何显示 通过我们也可以通过 ?if_exists 和??指令进行是否为空的判断。

测试模板内容:

1
2
3
4
5
6
7
8
9
10
11
12
${word!}
${word?if_exists}
${product?if_exists.name?if_exists}

<#if word??>
<#else>
word的值为空
</#if>
<#if word?if_exists>
<#else>
word的值为空
</#if>

测试用例:

1
2
3
4
5
6
7
8
9
10
11
12
@Test
public void ifExists() throws IOException, TemplateException {
Map root = new HashMap();
String word = null;
root.put("word", word);

Product product = new Product();
product.setName(null);
root.put("product", product);

initTemplate("ifExists",root);
}

测试结果:

1
word的值为空 word的值为空

freemarker 内置函数

具体请参考:https://freemarker.apache.org/docs/ref_builtins.html

我们这里介绍一下经常使用的内置函数 ,我们上面在介绍list的时候 使用的 ?size就只一个内置函数

字符串

  • ?length :字符串的长度 例如:${“zhuoqianmingyue”?length}
  • ?index_of :字符串中字符的位置交标 例如:${“zhuoqianmingyue”?index_of(‘yue’)}
  • ?substring:截取字符串 例如:${“zhuoqianmingyue”?substring(1)} ${“zhuoqianmingyue”?substring(1,2)}
  • ?trim:去掉字符串的空格 例如:${“ Hello world “?trim}
  • ?contains:是否包含否个字符 例如:${“Hello world “?contains(‘Hello’)?string}
  • ?date:日期的转换 ,?datetime datetime的转换
  • <#assign date1=”2009-10-12”?date(“yyyy-MM-dd”)>
  • <#assign date2=”09:28:20”?datetime(“HH:mm:ss”)>
  • ?string:字符串格式输出 例如:${date1?string}
  • ?is_string:是否是字符串 例如:${date1?is_string?string}

以上语法模板内容

1
2
3
4
5
6
7
8
9
10
11
length: ${"zhuoqianmingyue"?length}
index_of: ${"zhuoqianmingyue"?index_of('yue')}
substring: ${"zhuoqianmingyue"?substring(1)} ${"zhuoqianmingyue"?substring(1,2)}
trim: ${" Hello world "?trim}
contains:${"Hello world "?contains('Hello')?string}

<#assign date1="2009-10-12"?date("yyyy-MM-dd")>
<#assign date2="09:28:20"?datetime("HH:mm:ss")>
${date1?is_string?string}
${date1?string}
${date2?string}

测试用例:

1
2
3
4
5
@Test
public void string() throws IOException, TemplateException {
Map map = new HashMap();
initTemplate("string", map);
}

测试结果:

1
2
length: 15 index_of: 12 substring: huoqianmingyue h trim: Hello world
contains:true false 2009-10-12 1970-1-1 9:28:20

日期

.now 获取当前时间

日期格式转换

1
2
3
4
5
6
<#assign aDateTime = .now>
${.now}
${aDateTime?string["dd.MM.yyyy, HH:mm"]}
${aDateTime?string["EEEE, MMMM dd, yyyy, hh:mm a '('zzz')'"]}
${aDateTime?string["EEE, MMM d, ''yy"]}
${aDateTime?string.yyyy}
1
2
3
4
5
@Test
public void date() throws IOException, TemplateException {
Map map = new HashMap();
initTemplate("date", map);
}
1
2
3
4
5
2020-01-06 19:00:00
06.01.2020 19:00
星期一, 一月 06, 2020, 19:00 下午 (CST)
星期一, 一月 06, '20
2020

序列 (Sequence)

  • ?size ?reverse ?sort ?sort_by 我们已经在list 指令进行了演示 这里就不在做介绍了
  • ?chunk:序列分块遍历
  • ?first ?last:获取序列中的第一个和最后一个元素
  • ?join:拼接序列中的内容
  • ?seq_contains:序列中是否包含某个元素
  • ?seq_index_of:序列中元素的交标
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<#assign seq = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']>

<#list seq?chunk(4) as row>
<#list row as cell>${cell} </#list>
</#list>

<#list seq?chunk(4, '-') as row>
<#list row as cell>${cell} </#list>
</#list>
---------------------------------------------
${seq[1]}
${seq?first} ${seq?last}
----------------------------------------------
<#assign colors = ["red", "green", "blue"]>
${colors?join(", ")}
---------------------------------------------
<#assign x = ["red", 16, "blue", "cyan"]>
"blue": ${x?seq_contains("blue")?string("yes", "no")}
"yellow": ${x?seq_contains("yellow")?string("yes", "no")}
16: ${x?seq_contains(16)?string("yes", "no")}
"16": ${x?seq_contains("16")?string("yes", "no")}
-------------------------------------------------
<#assign colors = ["red", "green", "blue"]>
${colors?seq_index_of("blue")}
${colors?seq_index_of("red")}
${colors?seq_index_of("purple")}

测试用例:

1
2
3
4
5
@Test
public void sequences() throws IOException, TemplateException {
Map map = new HashMap();
initTemplate("sequences", map);
}

数字:

  • abs:绝对值 2.3.20

  • round:四舍五入

  • floor:向下取整

  • ceiling:向上取整

  • string.number:整数数字输出

  • string.currency:货币格式输出

  • string.percent:百分数格式输出

  • ?string[“0.##”]:数字显示2为小数

  • ?string[“000.00”]:小数左面不够0补齐

1
2
3
4
5
6
7
8
9
10
11
12
13
${-5?abs}
${4.5?round} ${4.4?round}
${4.5?floor} ${4.5?ceiling}

<#assign x = 42>
${x}
${x?string}
${x?string.number}
${x?string.currency}
${x?string.percent}

${1.2345?string["0.##"]}
${1.2345?string["000.00"]}
1
2
3
4
5
@Test
public void number() throws IOException, TemplateException {
Map map = new HashMap();
initTemplate("number", map);
}

has_api

下面的内置函数返回的结果都是布尔型的

  • is_string :是否是String
  • is_number :是否是数字
  • is_boolean :是否是布尔类型
  • is_date :是否是日期
  • is_macro :是否是宏
  • is_sequence:是否是序列