环境
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"); 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"); 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");
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); }
|
创建宏模板测试结果:
嵌套指令<#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); }
|
include指令
include引入的文件内容freemarker将解析其中的freemarker语法并移交给模板,同时assign的值可以互相调用
include 引入ftl模板
1 2 3 4 5
| <html> <body> 我是公共的页面 ${who}引用啦我! </body> </html>
|
1
| 我是include页面 <#assign who="include.ftl"> <#include "parent.ftl"/>
|
include指令的测试结果:
1 2 3 4 5 6
| 我是include页面 <html> <body> 我是公共的页面 include.ftl引用啦我! </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
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); }
|
import 使用宏
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); }
|
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); }
|
测试结果:
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); }
|
数字:
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:是否是序列