首先我们要先通过 官网:https://freemarker.apache.org/ 了解什么是freemarker 以及他的使用方法。官方介绍如下:
Apache FreeMarker™是一个模板引擎:一个Java库,用于根据模板和更改数据生成文本输出(HTML网页,电子邮件,配置文件,源代码等)。模板是用FreeMarker模板语言(FTL)编写的,这是一种简单的专用语言(不像PHP这样的完整编程语言)。通常,使用通用编程语言(如Java)来准备数据(发布数据库查询,进行业务计算)。然后,Apache FreeMarker使用模板显示准备好的数据。在模板中,您将关注如何呈现数据,而在模板之外,您将关注于要呈现的数据。
这种方法通常被称为MVC(模型视图控制器)模式,并且特别受动态网页的欢迎。它有助于将网页设计者(HTML作者)与开发人员(通常是Java程序员)分开。设计人员不会在模板中面对复杂的逻辑,并且可以在程序员不必更改或重新编译代码的情况下更改页面的外观。
虽然FreeMarker最初是为在MVC Web应用程序框架中生成HTML页面而创建的,但它并没有绑定到servlet或HTML或任何与Web相关的内容。它也用于非Web应用程序环境。
通过点击下图红色框文档手册 阅读如何使用 freemarker
接下来开始介绍freemarker 基本用法。介绍之前先说明一下我们的开发环境
IDEA :STS
JDK:1.8
freemarker Version:2.3.28
这里将通过maven项目进行说明我们的用法。首先引入freemarker 依赖到我们的maven项目中
org.freemarker
freemarker
2.3.28
在resource目录下创建template目录并添加helloworld.ftl 模板文件 内容如下:
hello world
this is ${who} hello World
创建配置实例 并将模板和数据进行输出
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 root = new HashMap();
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();
}
}
hello word的示例其实就是 Template + data-model = output 接下来我们引用一下官方的示例介绍一下数据模型
数据模型是树状结构 如下图
以上只是一个可视化; 数据模型不是文本格式,而是来自Java对象。对于Java程序员来说,root可能是带有getUser()
和getLatestProduct()
方法的Java对象,或者是Map
带有"user"
和"latestProducts"
键的Java 。同样, latestProduct
也许是一个Java对象 getUrl()
和getName()
方法。
欢迎! title>
欢迎${user}! h1>
我们的最新产品:
${latestProduct.name} !
@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 root = new HashMap();
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();
}
我们可以通过 <#-- 注释内容 --> 来进行注释 如下图
我们可以通过<#if> <#elseIf> <#else> 进行条件判断逻辑处理 具体操作如下
条件指令模板内容:
条件指令介绍! title>
<#-- 条件指令介绍 -->
<#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>
条件指令测试用例:
这里我们对测试用例进行了封装,后面的测试用例都将调用 initTemplate 来进行测试
@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();
}
我们可以通过 <#list 序列 as item> 来进行序列的遍历。另外list 还有一些内置的序列的函数
?size:序列的数量
_index :序列中元素的角标
_has_next:是否是当前迭代循环中的最后一项
?sort:对序列中的元素进行排序
?sort_by:根据实例中的具体的否个字段进行排序
?is_enumerable:是否是集合
测试list模板内容
list 指令介绍! title>
<#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>
list模板内容测试用例:
@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 productList = new ArrayList();
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模板内容测试结果:
list 指令介绍! title>
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
我们可以通过 assign指令在ftl中进行值的定义
assign指令测试模板内容:
<#assign name="zhuoqianmingyue">
${name}
-----------------------------------------------------------
<#assign product={"name":"苹果","url":"123.html","saleNum":23} >
${product.name},${product.url},${product.saleNum}
assign指令测试用例:
@Test
public void assign() throws IOException, TemplateException {
Map root = new HashMap();
initTemplate("assign",root);
}
assign指令测试结果:
这里的宏 我们可以理解成创建方法。我们可以通过 <#macro>标签来定义宏。
创建宏模板内容如下
创建宏模板测试用例:
@Test
public void marco() throws IOException, TemplateException {
Map map = new HashMap();
initTemplate("macroShow", map);
}
创建宏模板测试结果:
嵌套指令<#nested> 可以执行两次相同的调用
@Test
public void nested() throws IOException, TemplateException {
Map map = new HashMap();
initTemplate("nestedShow", map);
}
include引入的文件内容freemarker将解析其中的freemarker语法并移交给模板,同时assign的值可以互相调用
include 引入ftl模板
parent.ftl
我是公共的页面 ${who}引用啦我!
include.ftl
我是include页面
<#assign who="include.ftl">
<#include "parent.ftl"/>
include指令的测试用例:
@Test
public void include() throws IOException, TemplateException {
Map map = new HashMap();
initTemplate("include", map);
}
include指令的测试结果:
include 引入html
在resource 目录下创建include.html
模板内容:
测试用例:
@Test
public void include2() throws IOException, TemplateException {
Map map = new HashMap();
initTemplate("include2", map);
}
测试结果:
import引入的文件内容freemarker将不会解析其中的freemarker语法,同时assign的值可以可以互相调用。他可以创建一个命名空间 通过该命名调用import 模板中的变量和宏
@Test
public void importFun() throws IOException, TemplateException {
Map map = new HashMap();
initTemplate("importFun", map);
}
import 使用宏
@Test
public void macroImportShow() throws IOException, TemplateException {
Map map = new HashMap();
initTemplate("macroImportShow", map);
}
当数据模型的key不存在或者key 的value是null 时 我们执行模板引擎进行渲染的化或报如下图的错误
我们可以通过在flt页面中使用! 或者 ?if_exists 使其不做任何显示 通过我们也可以通过 ?if_exists 和??指令进行是否为空的判断。
测试模板内容:
${word!}
${word?if_exists}
${product?if_exists.name?if_exists}
<#if word??>
<#else>
word的值为空
#if>
<#if word?if_exists>
<#else>
word的值为空
#if>
测试用例:
@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);
}
测试结果:
具体请参考: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}
以上语法模板内容
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}
测试用例:
@Test
public void string() throws IOException, TemplateException {
Map map = new HashMap();
initTemplate("string", map);
}
测试结果:
.now 获取当前时间
日期格式转换
<#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}
@Test
public void date() throws IOException, TemplateException {
Map map = new HashMap();
initTemplate("date", map);
}
?size ?reverse ?sort ?sort_by 我们已经在list 指令进行了演示 这里就不在做介绍了
?chunk:序列分块遍历
?first ?last:获取序列中的第一个和最后一个元素
?join:拼接序列中的内容
?seq_contains:序列中是否包含某个元素
?seq_index_of:序列中元素的交标
<#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")}
测试用例:
@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补齐
${-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"]}
@Test
public void number() throws IOException, TemplateException {
Map map = new HashMap();
initTemplate("number", map);
}
下面的内置函数返回的结果都是布尔型的
is_string :是否是String
is_number :是否是数字
is_boolean :是否是布尔类型
is_date :是否是日期
is_macro :是否是宏
is_sequence:是否是序列