FreeMarker基本语法

1. FreeMarker介绍

FreeMarker是一种模板引擎,通过定义的模板和数据来生成文本(包括但不局限于html,js,java等文本格式),通俗的讲就是先定义一下模板,然后传入不同的数据,动态的生成不同的文本,但它不是面向用户的,而是面向程序员的,可以直接自动的生成代码,减少程序员重复的劳动。
FreeMarker最重要的两部分是模板和数据:
模板:FreeMarker Template Language,简称FTL,模板文件以ftl为后缀,组成:

  • 文本,包括HTML标签与静态文本等静态内容,会原样输出;
  • 插值:这部分的输出会被计算的数据来替换,使用${}这种语法;
  • 标签:给FreeMarker的指示,可以简单与指令等同,不会打印在内容中;
  • 注释:由<#–和–>表示,不会被freemarker处理
    数据结构:
    树状结构:HashMap,Scalar,Sequence

    2.基本使用

    http://freemarker.org/ 下载FreeMarker的压缩包,将其中的freemarker.jar加到项目的构建路径下
    从maven仓库中引入maven依赖的jar包,注意两个核心类:

    1
    2
    3
    4
    5
    <dependency>
    <groupId>org.freemarker</groupId>
    <artifactId>freemarker</artifactId>
    <version>2.3.24-atlassian-2</version>
    </dependency>
  • Configuration:读取模板文件

  • Template:模板
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    Configuration cfg = new Configuration(Configuration.VERSION_2_3_21);
    Map<String,String> root= new HashMap<String,String>();
    root.put("name", "Java开发日记");
    try {
    cfg.setDirectoryForTemplateLoading(new File("src/ftl"));
    Template template = cfg.getTemplate("helloworld.ftl");
    Writer writer = new FileWriter(new File("src/finish/helloworld.html"));
    template.process(root, writer);
    } catch (Exception e) {
    e.printStackTrace();
    }

使用Configuration读取配置文件,使用Map填充数据,给配置文件配置模板文件夹路径,读取模板文件,设置输出文件路径helloworld.html,执行输出文件,执行完之后就会在src目录下的finish文件夹中生成一个helloworld的html文件。
模板文件如下:

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>${name}</title>
</head>
<body>
这是我的第一个程序,${name}
</body>
</html>

3.模板

3.1 数据类型与变量

模板中的数据类型:

  • 标量:字符串,数字,布尔值,日期;
  • 容器:哈希表,序列;
  • 子程序:方法和函数,用户自定义指令;
    模板中的变量:
    简单变量,局部变量,循环变量,使用赋值指令:assign
    示例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <#assign num=10>
    ${num}

    <#assign name="Java开发日记">
    ${name}

    <#assign b=true>
    ${b?c}

    <#assign map={"name":"张三","age":15}>
    ${map.name}

    <#assign list = [1,3,5]>
    ${list[2]}

freemarker中使用指令时必须要在指令前面用#(如果是自定义指令用@,后面说),assign指令是用来声明变量的,注意:如果是布尔值,输出时一定要带?c,表示定义的变量是布尔值,不然会报错。

3.2运算符

模板中支持运算符:

  • 算术运算符
  • 比较运算符
  • 逻辑运算符
  • 空值处理运算符
    1
    2
    3
    4
    5
    <#assign b=1==2>
    ${b?c}

    <#assign b=1 gt 2>
    ${b?c}

均输出false,gt表示大于。

3.3 插值

插值是用来给插入具体值然后转换为文本,说白了,插值就是使用${}在那占个坑。
使用位置:

  • 文本区(如Hello ${name}!)
  • 字符串表达式(如<#include “/footer/${company}.html”>)
    语法:${表达式}
    注意:插值表达式的结果必须是字符串,数字或日期类型。

    3.4指令

  • 条件指令
    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
    <#assign score=50>
    <#if score gt 90>
    优秀
    <#elseif score gt 70>
    良好
    <#elseif score gt 60>
    及格
    <#else>
    渣渣
    </#if>

    <#assign level="B">
    <#switch level>
    <#case "A">
    优秀
    <#break >
    <#case "B">
    良好
    <#break >
    <#case "C">
    soso
    <#break >
    <#default>
    渣渣
    </#switch>

需要注意的是switch语句需要有break,不然一直往下执行。

  • 循环指令
    1
    2
    3
    4
    5
    6
    7
    8
    <#assign nums=[1,2,3,4,5]>
    <#list nums as num>
    ${num_index},${num},${num_has_next?c}
    </#list>

    <#list nums as num>
    ${num}<#if num_has_next>,</#if>
    </#list>

定义一个集合nums,使用list指令遍历,${别名_index}获取遍历的索引值,${别名_has_next}判断后面是否还有值,根据这个特性,可以结合if指令拼接不同的值(自动生成mybatis的配置文件时,不同字段最后一个后面不要逗号,用这个特性很好用)。

  • 包含指令
    1
    2
    <#include "condition.ftl">
    从这里开始是自己的内容

使用include引入指令可以引入其它的模板页面。如果页面路径写不好可以用通配符,<#include “*/condition.ftl”>

  • 其它
    原样输出指令noparse:它里面的内容是原样输出的;
    压缩指令compress:压缩所有的空格;
    设置指令setting:设置影响FreeMarker的值。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <#noparse>
    <#assign num=1>
    ${num}
    </#noparse>

    <#assign s=" test \n\n">
    <#compress>
    ${s}
    Compress
    </#compress>

    <#setting locale="hu">
    ${1.2}
  • 自定义指令
    可以将模版中重复的内容进行复用
    定义:
    使用macro指令定义或者使用Java实现。
    参数的声明:直接跟在指令名后,可以指定默认值
    嵌套内容:使用nested指令
    调用:
    使用<@指令>来调用(调用freemarker自带的指令用#命令)。

    1
    2
    3
    4
    <#macro mydirect name age=20>
    你好,${name},你今年${age}
    </#macro>
    <@mydirect name="Java开发日记" age=1/>

自定义一个指令名为mydirect,它有两个参数name和age,其中age有默认值20,使用自定义指令时用@+自定义指令,同时为参数赋值即可。注意自定义指令是闭口的,不要漏掉最后的斜杠。

3.5 空处理

有时候对象是空的,不进行判断就贸然进行处理会报错,所以要提前进行判空处理。

  • null对象的处理方式:使用!,只会做最后一个属性的判断;
  • 变量不存在的处理方式:使用!或??做判断。
    1
    2
    3
    4
    5
    6
    ${user.name!}
    <#if user.name??>
    名称存在
    <#else>
    名称不存在
    </#if>

我认为是在插值时使用!,在其它指令内使用??。

3.6名称空间

在编写可重复使用的模板时为了避免命名冲突,使用import指令导入命名空间。命名空间有点类似于java的包,即使类名相同,只要位于不同的包下,也是可以的。

1
2
3
4
5
6
7
test.ftl模板:
<#macro mydirective name>
你好,${name}
</#macro>
namespace.ftl模板
<#import "test.ftl" as ns>
<@ns.mydirective name="Java开发日记" />

在这里我定义两个模板,在namespace.ftl中使用import指令导入test.ftl模板并为它起一个名为ns的命名空间,调用时我使用命令空间调用,即使namespace.ftl模板中有其它同名的也不至于混淆。另外在test.ftl中使用的是自定义指令定义了名为mydirective的指令,并为其制定一个参数name。

3.7函数

FreeMarker中函数有如下几种:

  • 字符串函数;
  • 数字,日期布尔类型的函数;
  • 序列(list,set)与哈希的函数;
  • 自定义函数
    而且FreeMarker中的函数与java中函数调用有个很大的区别,在java中调用函数使用点号(.),而在FreeMarker中使用问号(?),这一点一定要记清。下面分别说上面提到的几种函数:
    3.7.1字符串函数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    ${"abcdef"?substring(2)}       <#--从角标是2的位置开始截取到字符串结束-->
    ${"abcdef"?substring(2,4)} <#--从角标是2的位置开始截取到角标是4的位置,包括2不包括4(包前不包后)-->
    ${"abcd mn"?cap_first} <#--将整个字符串的首字母转大写-->
    ${"Abcd"?uncap_first} <#--将整个字符串的首字母转小写-->
    ${"fden eb"?capitalize} <#--将字符串的每个单词首字母转大写-->
    ${"abcd"?ends_with("d")?c} <#--是否以字符d结尾,这个结果是布尔值,不能直接输出,不然会报错,用?c转化成字符串-->
    ${"abcd"?starts_with("d")?c} <#--是否以字符d开头-->
    ${"abac"?index_of("a")} <#--字符a首次出现的位置-->
    ${"abac"?last_index_of("a")} <#--字符a最后一次出现的位置-->
    ${"ab"?left_pad(15,"xy")} <#--将字符串ab填充成15位,如果不够15位,则左边循环填充xy-->
    ${"ab"?right_pad(15,"xy")} <#--将字符串ab填充成15位,如果不够15位,则右边循环填充xy-->
    ${"abac"?contains("ab")?c} <#--判断字符串abac是否包含ab-->
    ${"abac"?replace("ab","AB")} <#--将ab替换成AB-->
    <#list "abcabcabc"?split("c") as s>
    ${s} <#--将字符串以c字符进行分割,结果是个数组,进行遍历-->
    </#list>
    ${" abc "?trim} <#--去掉字符串空格-->
    <#list " Hello FreeMarker Yes"?word_list as s>
    ${s} <#--将字符串分割成一个个的单词,存在多个空格时,这个跟上面的split分割有一点区别-->
    </#list>
3.7.2数字,日期布尔类型的函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
${4.2?c}                <#--以字符串的形式输出,上面说的布尔类型不能直接输出只能转化成字符串这种-->
${4.2?string} <#--与?c一样-->
${0.42?string.percent} <#--以百分号输出42%-->
${4.2?string.currency} <#--以货币形式输出¥4.20-->
${4.7?round} <#--四舍五入 -->
${4.7?floor} <#--向下取整,floor地板 -->
${4.7?ceiling} <#--向上取整,ceiling天花板-->

${date?string("yyyy-MM-dd")} <#--传入日期date,以yyyy-MM-dd形式输出,2017-11-18-->
${date?date} <#--输出年月日2017-11-18-->
${date?time} <#-- 输出时分秒20:41:49-->
${date?datetime} <#--输出年月日时分秒-->

${false?string("yes","no")} <#--条件如果成功输出yes,否则输出no-->
${4.2355?string("0.##")} <#--保留小数点后两位-->
3.7.3 序列(list,set)与哈希的函数
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
<#assign seq=[1,2,3,4,5,3,10]>
${seq?first} <#-- 输出序列seq的第一个-->
${seq?last} <#-- 输出序列seq的最后一个-->
${seq?seq_contains(6)?c} <#--判断序列是否包含6-->
${seq?seq_index_of(3)} <#--3在序列中首次出现的位置-->
${seq?seq_last_index_of(3)} <#--3在序列中最后一次出现的位置-->
<#list seq?reverse as num> <#--翻转序列seq,结果还是一个序列,进行遍历-->
${num}
</#list>
${seq?size} <#--序列seq的长度-->
<#list seq?sort as num> <#--对序列进行排序-->
${num}
</#list>

<#assign seq1=[
{"name":"Tom","age":23},
{"name":"Jack","age":22},
{"name":"Rose","age":21},
{"name":"Tim","age":24}
]>
<#list seq1?sort_by("age") as u> <#--以age对哈希seq1进行排序并遍历-->
${u.name}+","+${u.age}
</#list>

<#assign users={
"name":"Tim","age":24
}>
<#list users?keys as key> <#--获取到哈希users所有的键-->
${key}
</#list>
3.7.4 自定义函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<#function add num1 num2>
<#return num1+num2>
</#function>

<#function addAll nums...>
<#local total=0>
<#list nums as num>
<#local total=total+num>
</#list>
<#return total>
</#function>

<#--使用function指令定义函数add,有两个参数num1和num2,使用return返回计算之后的结果,使用${add(1,3)}调用函数 -->
${add(1,3)}

<#--使用function指令定义函数addAll,参数个数不固定,定义局部变量total来存储临时计算的结果,
遍历所有的参数并进行运算,使用return返回计算之后的结果,使用${addAll(1,2,3,4,5)}调用函数 -->
${addAll(1,2,3,4,5)}

4.数据模型

4.1数据类型

使用基本数据类型来派生数字类型
使用java.lang.String来构建字符串。
使用java.lang.Number来派生数字类型。
使用java.lang.Boolean来构建布尔值。
使用java.util.List,java.util.Set或Java数组来构建序列。
使用java.util.Map来构建哈希表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Map root=new HashMap();
root.put("d1", 100);
root.put("d2", 100.99);
root.put("d3", 'a');
root.put("d4", true);
root.put("d5", new Integer(200));
root.put("d6", new Boolean(false));

List<String> names= Arrays.asList("abc","def","ghi");
root.put("d7", names);

Set<String> names1=new HashSet<>();
names1.add("ABC");
names1.add("DEF");
names1.add("GHI");
root.put("d8", names1);

root.put("d9", new String[]{"a","b","c"});

Map map=new HashMap();
map.put("name", "Java开发日记");
map.put("age", 18);

root.put("map", map);

就是java中常见的整型,浮点型,布尔型,字符,数组,list,set,map,不再多说

4.2 加载模板

使用Configuration的方法加载模版:
1,void setDirectoryForTemplateLoading(File dir);
2,void setClassForTemplateLoading(Class cl, String prefix);
3,void setServletContextForTemplateLoading(Object servletContext, String path);
加载多个位置的模版:
1,FileTemplateLoader
2,ClassTemplateLoader
3,TemplateLoader
4,MultiTemplateLoader
5,setTemplateLoader(MultiTemplateLoader mtl);

1
2
3
4
5
6
7
8
9
10
11
12
Configuration cfg = new Configuration(Configuration.VERSION_2_3_22);
//cfg.setClassForTemplateLoading(TemplateLoad.class, "../../../ftl");

FileTemplateLoader ftl1=new FileTemplateLoader(new File("src/ftl"));
FileTemplateLoader ftl2=new FileTemplateLoader(new File("src/ftl2"));
ClassTemplateLoader ctl=new ClassTemplateLoader(TemplateLoad.class, "../../../ftl3");
TemplateLoader[] loaders={ctl,ftl2};
MultiTemplateLoader mtl=new MultiTemplateLoader(loaders);
cfg.setTemplateLoader(mtl);

Template template=cfg.getTemplate("ftl2.ftl");
System.out.println(template);

javase项目中使用setDirectoryForTemplateLoading来加载模板所在文件夹,javaee项目中使用setClassForTemplateLoading路径,servlet中可以使用setServletContextForTemplateLoading来加载模板路径。

4.3其它配置

配置就是在对象中存储常用的设置和定义某些想在所有模板中可用的变量,配置对象是freemarker.template.Configuration的实例,可以通过构造方法来创建它。而且一个应用程序通常只使用一个共享的Configuration实例。

  • 设置共享变量:setSharedVariable()
  • 国家地区:setLocale();
  • 数字格式:setNumberFormat(“0.##”);
  • 通用设置:setSetting(String name, String value)方法
  • 缓存:设置缓存:setCacheStorage(new freemarker.cache.MruCacheStorage(20, 250))
    或setSetting(Configuration.CACHE_STORAGE_KEY, “strong:20, soft:250”);
  • 清空缓存:clearTemplateCache
1
2
3
4
5
6
7
8
9
10
11
12
Configuration cfg=new Configuration(Configuration.VERSION_2_3_22);
cfg.setDirectoryForTemplateLoading(new File("src/ftl"));
//设置共享便令
cfg.setSharedVariable("site", "Java开发日记");
//设置小数点后保留两位
cfg.setNumberFormat("0.##");、
//设置缓存,一级缓存20个,2级缓存250个
cfg. setCacheStorage(new freemarker.cache.MruCacheStorage(20, 250));
//cfg.setSetting(Configuration.CACHE_STORAGE_KEY, "strong:20, soft:250");
//cfg.clearTemplateCache(); //清楚缓存
Template template = cfg.getTemplate("config.ftl");
Writer writer = new FileWriter(new File("src/finish/config.html"));

总结:上面这些就是FreeMarker模板引擎的基本操作,首先需要进入jar包,之后设置模板所在的路径并引入模板,然后组装数据(实际操作中通过jdbc连接数据库来操作),所以主要学的就两方面内容:模板和数据,而模板又与这许许多多的指令相关,所以必须要掌握这些指令。

刘俊重 wechat
欢迎关注我的微信公众号
坚持原创技术分享