MENU

Freemarker 语法简介

• November 20, 2016 • Read: 43158 • Codes

FreeMarker是一款模板引擎: 即一种基于模板和要改变的数据, 并用来生成输出文本(HTML网页、电子邮件、配置文件、源代码等)的通用工具。

选择freemarker的原因:
  1. 性能。velocity应该是最好的,其次是jsp,普通的页面freemarker性能最差(虽然只是几毫秒到十几毫秒的差距)。但是在复杂页面上(包含大量判断、日期金额格式化)的页面上,freemarker的性能比使用tag和el的jsp好。
  2. 宏定义比jsp tag方便
  3. 内置大量常用功能。比如html过滤,日期金额格式化等等,使用非常方便
  4. 支持jsp标签
  5. 可以实现严格的mvc分离
FreeMarker模板文件主要由如下4个部分组成:
  1. 文本:直接输出的部分。
  2. 注释:<#-- ... —>格式部分,不会输出。
  3. 插值:即${…}#{…}格式的部分,将使用数据模型中的部分替代输出。
  4. FTL指令:FreeMarker指定,和HTML标记类似,名字前加#予以区分,不会输出。
例子
<html>
    <head>
        <title>Welcome!</title>
    </head>
    <body>
        <#-- 注释部分 -->
        <#-- 下面使用插值 -->
        <h1>Welcome ${user}!</h1>
        <p>We have these animals:
        <ul>
            <#-- 使用FTL指令 -->
            <#list animals as being>
            <li>${being.name} for ${being.price} Euros
            </#list>
        </ul>
    </body>
</html>

一、插值规则

FreeMarker的插值有如下两种类型:

  1. 通用插值${expr}
  2. 数字格式化插值:#{expr}#{expr;format}

1. 通用插值

  • 输出 ${book.name}
  • 空值判断:

    • ${book.name?if_exists}
    • ${book.name?default(‘xxx’)} //默认值xxx
    • ${book.name!"xxx"} //默认值xxx
  • 日期格式:${book.date?string('yyyy-MM-dd')}
  • 数字格式:

    • ${book?string.number} //20
    • ${book?string.currency} //$20.00
    • ${book?string.percent} //20%

2. 数字格式化插值

数字格式化插值可采用#{expr;format}形式来格式化数字。

其中format可以是:

  • mX:小数部分最小X位
  • MX:小数部分最大X位

示例:

<#assign x=2.582/>
<#assign y=4/>
#{x; M2}
#{y; M2}
#{x; m2}
#{y; m2}
#{x; m1M2}
#{x; m1M2} 

输出:

2.58
4
2.58
4.00
2.58
2.58

二、FTL指令规则

在FreeMarker中,使用FTL标签来使用指令,FreeMarker有3种FTL标签,这和HTML标签是完全类似的.

  1. 开始标签:<#directivename parameter>
  2. 结束标签:</#directivename>
  3. 空标签:<#directivename parameter/>

1. 遍历List集合

<#list ["星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期天"]  as item>
${item}
</#list>

这里会逐个输出星期几。

此外,迭代集合对象时,还包含两个特殊的循环变量:

  • item_index:当前变量的索引值
  • item_has_next:是否存在下一个对象

也可以使用<#break>指令跳出迭代。

2. 遍历map集合

{"语文":78, "数学":80}

Map对象的key和value都是表达式,但是key必须是字符串

<#list map?keys as key>
    ${key}=${map[key]}<br/>
</#list>

3. 逻辑判断

if 判断

判断条件,当condition的判断结果为false(布尔值)时,在<#if condition></#if>标签之间的内容将会被略过。

<#if animals.python.price < animals.elephant.price>
Pythons are cheaper than elephants today.
<#else>
Pythons are not cheaper than elephants today.
</#if>

需要注意的是比较运算符两边的类型必须相同,不能一个是 数字,一个是字符串,否则将会报错。

Switch 判断

<#switch value>
    <#case refValue1>
        ...
        <#break>
    <#case refValue2>
        ...
        <#break>
    <#case refValueN>
        ...
        <#break>
    <#default>
        ...
</#switch>

4. 空值处理

FreeMarker对空值的处理非常严格,FreeMarker的变量必须有值,没有被赋值的变量就会抛出异常,因为FreeMarker未赋值的变量强制出错可以杜绝很多潜在的错误,如缺失潜在的变量命名,或者其他变量错误。这里所说的空值,实际上也包括那些并不存在的变量,对于一个Java的null值而言,我们认为这个变量是存在的,只是它的值为null,但对于FreeMarker模板而言,它无法理解null值,null值和不存在的变量完全相同。

为了处理缺失变量,FreeMarker提供了两个运算符:

  • !:指定缺失变量的默认值
  • ??:判断某个变量是否存在

指定缺失变量的默认值

!运算符的用法有如下两种:

variable!
variable!defaultValue

第一种用法不给缺失的变量指定默认值,表明默认值是空字符串、长度为0的集合或者长度为0的Map对象。

另外,使用!指定默认值时,并不要求默认值的类型和变量类型相同。

Demo:

<h1>Welcome ${user!"Anonymous"}!</h1>
<#assign user = "Doe Joe">
<h1>Welcome ${user!"Anonymous"}!</h1>

输出:

<h1>Welcome Anonymous!</h1>
<h1>Welcome Doe Joe!</h1>

判断某个变量是否存在

使用??运算符非常简单,它总是返回一个布尔值,用法为:variable??,如果该变量存在,返回true,否则返回false

但是需要注意的是,我们在使用freemarker获取后端回填数据时,必须使用??来判断是否存在,再进行使用。或者通过default来赋予默认值,否则一旦数据不存在,就会出现前端报错完全崩溃的情况。

Demo:

<#if mouse??>
Mouse found.
<#else>
No mouse found.
</#if>

实际使用示例:

<#if !productList??>
<div class="n-result">
    <h3>暂无内容!</h3>
</div>
<#else>
<div class="n-plist">
    <ul class="f-cb" id="plist">
        <#list productList as x>
            <#if !x.isBuy>
                <li id="p-${x.id}">
                    <a href="/show?id=${x.id}" class="link">
                        <div class="img"><img src="${x.image}" alt="${x.title}"></div>
                        <h3>${x.title}</h3>
                        <div class="price"><span class="v-unit">¥</span><span class="v-value">${x.price}</span></div>
                    </a>
                </li>
            </#if>
        </#list>
    </ul>
</div>
</#if>

5. 变量的声明

plain变量

它能从模板中的任何位置来访问,或者从使用include指令引入的模板访问。可以使用assignmacro指令来创建或替换这些变量。

<#assign num=0/>
<#assign x="Hello ${user}!"/>

局部变量

只能在#function#macro定义体中定义&有效,使用local指令创建和替换。

局部变量会隐藏同名的plain变量。

循环变量

只能存在于指令的嵌套内容,由指令(如list)自动创建;宏的参数是局部变量,而不是循环变量。

循环变量会隐藏同名的局部变量和plain变量,且内部循环变量会隐藏外部循环变量。

如果想要跳出作用域,直接使用数据模型中的变量,使用globals关键字即可:

<#assign user = "Joe">
${user}
${.globals.user}

输出:

Joe
Doe

6. 运算符的优先级

FreeMarker中的运算符优先级如下(由高到低排列):

  1. 一元运算符:!
  2. 内建函数:?
  3. 乘除法: *, / , %
  4. 加减法:- , +
  5. 比较: > , < , >= , <=
  6. 相等: == , = , !=
  7. 逻辑与:&&
  8. 逻辑或: ||
  9. 数字范围:..

7. include指令

include 指令的作用类似于 JSP 的包含指令,用于包含指定页。include指令的语法格式如下:

<#include filename [options]>

示例:

<#include "/header.html">
<#include "/footer.ftl">

8. import 指令及命名空间

import指令类似于 java 里的 import,它导入文件,然后就可以在当前文件里使用被导入文件里的宏组件。

我们通过assign和macro创建的变量的集合就是命名空间。命名空间的作用在各种编程语言中都已见识,这里假设我们定义了自己的macro和assign位于lib/mylib.ftl中。

<#macro copyright date>
<p>Copyright (C) ${date} Julia Smith. All rights reserved.</p>
</#macro>
<#assign mail = "jsmith@acme.com">

通过import来引用:

<#import "/libs/mylib.ftl" as my> 
<#-- 被称为"my"的哈希表就会是那个"大门" --> 
<@my.copyright date="1999-2002"/>
${my.mail}

输出:

<p>Copyright (C) ${date} Julia Smith. All rights reserved.</p>
jsmith@acme.com

覆盖变量

引入命名空间后替换变量需要在assign的基础上加上in关键字:

<#import "/lib/mylib.ftl" as my>
${my.mail}
<#assign mail = "jsmith@other.com" in my>
${my.mail}

输出:

jsmith@acme.com
jsmith@other.com

9. noparse指令

noparse 指令指定 FreeMarker 不处理该指定里包含的内容,该指令的语法格式如下:

<#noparse>...</#noparse>

示例:

<#noparse>
<#list animals as animal>
    <li>${animal.name} for ${animal.price} Euros</li>
</#list>
</#noparse>

输出:

<#list animals as animal>
    <li>${animal.name} for ${animal.price} Euros</li>
</#list>

10. setting指令

该指令用于设置FreeMarker的运行环境,该指令的语法格式如下:

<#setting name=value>

在这个格式中,name的取值范围包含如下几个:

  • locale:该选项指定该模板所用的国家/语言选项
  • number_format:指定格式化输出数字的格式
  • boolean_format:指定两个布尔值的语法格式,默认值是true,false
  • date_format,time_format,datetime_format:指定格式化输出日期的格式
  • time_zone:设置格式化输出日期时所使用的时区

三、内建函数

内建函数以?形式提供变量的不同形式或者其他信息。多个内建函数可以通过?连接来使用。

字符串常用的内建函数:

  • html:将字符串中的所有特殊 HTML字符进行转义。例如:<替换成&lt;
  • cap_first:将字符串的首字母大写
  • lower_case:将字符串转换为小写形式
  • upper_case:将字符串转换为大写形式
  • trim:去掉字符串首尾的空格

集合常用的内建函数:

  • size:集合中元素的个数
  • chunk(size):分成几个一组

数字常用的内建函数:

  • int:将数字转换为整数。方式为直接去除小数部分。

示例:

${test?html}
${test?lower_case?html}
${3 * 2 + 2}                <#-- 8 -->
${3 * (2 + 2)}              <#-- 12 -->
${3 * ((2 + 2) * (1 / 2))}  <#-- 6 -->
${"green " + "mouse"?upper_case}    <#-- green MOUSE -->
${("green " + "mouse")?upper_case}  <#-- GREEN MOUSE -->

number_to_datetime

一个转换时间格式的内建函数。

${timestamp?number_to_datetime?string["MM-dd HH:mm"]}

eval

将字符串作为ftl模板输出,就和javascript原生的eval有点类似。

示例:

${(col['name']?eval)}

四、宏

宏是在模板中使用macro指令定义,宏是和某个变量关联的模板片断,以便在模板中通过用户定义指令使用该变量,有人说用freemarker,但没有用到它的宏(macro),就等于没有真正用过freemarker。说的就是宏是freemarker的一大特色。

其基本语法如下:

<#macro name param1 param2 ... paramN>
    ...
    <#nested loopvar1, loopvar2, ..., loopvarN>
    ...
    <#return>
    ...
</#macro>

在上面的格式片段中,包含了如下几个部分:

  • name:name属性指定的是该自定义指令的名字,使用自定义指令时可以传入多个参数
  • paramX:该属性就是指定使用自定义指令时报参数,使用该自定义指令时,必须为这些参数传入值
  • nested指令:nested标签输出使用自定义指令时的中间部分
  • nested指令中的循环变量:这此循环变量将由macro定义部分指定,传给使用标签的模板
  • return指令:该指令可用于随时结束该自定义指令.

需要注意的是:调用宏时,与使用FreeMarker的其他指令类似,只是使用@替代FTL标记中的#

1. macro定义模板,然后调用直接显示
<#macro greet>
<font size="+2">Hello World!</font>
</#macro>

使用:

<@greet/>

<@greet></@greet>

输出:

<font size="+2">Hello World!</font>

<font size="+2">Hello World!</font>
2. 在macro指令中可以在宏变量之后定义参数
<#macro greet person>
<font size="+2">Hello ${person}!</font>
</#macro>

使用:

<@greet person="Doe"></@greet>
<@greet person="Joe"/>
<@greet "Foo"/>

输出:

<font size="+2">Hello Doe!</font>
<font size="+2">Hello Joe!</font>
<font size="+2">Hello Foo!</font>
3. macro 定义多个参数

macro可以有多个参数,参数的次序是无关的(如果使用 Postional style 调用则需要按顺序),在macro指令中只能使用定义的参数,并且必须对所有参数赋值,可以在定义参数时指定缺省值:

<#macro greet person color="black">
<font size="+2" color="${color}">Hello ${person}!</font>
</#macro>

使用:

<#-- Named style -->
<@greet person="Doe"></@greet>
<@greet person="Doe"/>
<@greet person="Doe" color= "green"/>
<#-- Postional style -->
<@greet "Doe"/>
<@greet "Doe", "green"/>

输出:

<font size="+2" color="black">Hello Doe!</font>
<font size="+2" color="black">Hello Doe!</font>
<font size="+2" color="green">Hello Doe!</font>
<font size="+2" color="black">Hello Doe!</font>
<font size="+2" color="green">Hello Doe!</font>
4. 自定义指令嵌套内容 <#nested>
<#macro border>
<table border=4 cellspacing=0 cellpadding=4>
    <tr>
        <td>
            <#nested>
        </td>
    </tr>
</table>
</#macro>

使用:

<@border>The bordered text</@border>

输出:

<table border=4 cellspacing=0 cellpadding=4>
    <tr>
        <td>
            The bordered text
        </td>
    </tr>
</table>

<#nested>就相当于占位符

<#nested>指令可以被多次调用:

<#macro do_thrice>
    <#nested>
    <#nested>
    <#nested>
</#macro>

使用:

<@do_thrice>Anything.</@do_thrice>

输出:

Anything.
Anything.
Anything.
5. 局部变量对嵌套内容不可见
<#macro repeat count>
    <#local y = "test">
    <#list 1..count as x>
    ${y} ${count}/${x}: <#nested>
    </#list>
</#macro>

<@repeat count=3>${y?default("?")} ${x?default("?")} ${count?default("?")}</@repeat>

输出:

test 3/1: ? ? ?
test 3/2: ? ? ?
test 3/3: ? ? ?

其中嵌套内容中的y,x,count都是没定义的,所以取不到值。

6. 宏定义中使用循环变量

nested指令也可以有循环变量(循环变量的含义见下节),调用宏的时候在宏指令的参数后面,分号隔开依次列出循环变量的名字,格式如下:

<@macro_name paramter list; loop variable list[,]>

示例:

<#macro repeat count>
    <#list 1..count as x>
        <#nested x, x/2, x==count>
    </#list>
</#macro>

<@repeat count=4 ; c, halfc, last>
${c}. ${halfc}<#if last> Last!</#if>
</@repeat>

count是宏的参数,c,halfc,last则为循环变量,与宏中<#nested>里定义的x, x/2, x==count一一对应。

输出:

1, 0.5
2, 1
3, 1.5
4, 2 Last!
引用

循环变量和宏标记指定的不同不会有问题,如果调用时少指定了循环变量,那么多余的值不可见。调用时多指定了循环变量,多余的循环变量不会被创建:

<@repeat count=4 ; c, halfc, last>
${c}. ${halfc}<#if last> Last!</#if>
</@repeat>

<@repeat count=4 ; c, halfc>
${c}. ${halfc}
</@repeat>

<@repeat count=4>
Just repeat it...
</@repeat>

转载

内容非原创,转载自:

参考阅读:

Last Modified: January 31, 2023
Archives QR Code Tip
QR Code for this page
Tipping QR Code
Leave a Comment

26 Comments
  1. 真正的菜鸡 真正的菜鸡

    厉害,学习了,快速入门

  2. 预祝你新年大吉吧\#(装大款)

  3. 默默膜拜技术帝

  4. 看来是有一定的研究的。@(吐舌)

  5. 居然有目录树了好酷~#(委屈的吃手手)#

    1. @友人C又挖苦我\#(投降)

    2. @Hran真的好棒啊,我也打算加一个~#(关爱制杖)#