FreeMarker
https://freemarker.apache.org/
https://search.bilibili.com/all?keyword=freemarker
FreeMarker概述
FreeMarker 是一款 模板引擎: 即一种基于模板和要改变的数据, 并用来生成输出文本(HTML网页,电子邮件,配置文件,源代码等)的通用工具。 它不是面向最终用户的,而是一个Java类库,是一款程序员可以嵌入他们所开发产品的组件。
模板编写为FreeMarker Template Language (FTL)。它是简单的,专用的语言, 不是 像PHP那样成熟的编程语言。 那就意味着要准备数据在真实编程语言中来显示,比如数据库查询和业务运算, 之后模板显示已经准备好的数据。在模板中,你可以专注于如何展现数据, 而在模板之外可以专注于要展示什么数据。
这种方式通常被称为 MVC (模型 视图 控制器) 模式,对于动态网页来说,是一种特别流行的模式。 它帮助从开发人员(Java 程序员)中分离出网页设计师(HTML设计师)。设计师无需面对模板中的复杂逻辑, 在没有程序员来修改或重新编译代码时,也可以修改页面的样式。
而FreeMarker最初的设计,是被用来在MVC模式的Web开发框架中生成HTML页面的,它没有被绑定到 Servlet或HTML或任意Web相关的东西上。它也可以用于非Web应用环境中。
FreeMarker 是 免费的, 基于Apache许可证2.0版本发布。
FreeMarker特性
通用目标
强大的模板语言
通用数据模型
为web准备
智能的国际化和本地化
强大的XML处理能力
入门
模板+数据模型=输出
模板:UI视图的基本结构,FTL模板。
数据模型:数据在面向对象设计中的职称。
输入:HTML。
数据模型一览
1 | (root) |
模板一览
最简单的模板通常是普通的HTML文件(或者是其他任何文本文件; FreeMarker本身不属于HTML)。
**${…}**: FreeMarker将会输出真实的值来替换大括号内的表达式,这样的表达式被称为 interpolation(插值,译者注)。
FTL 标签(FreeMarker模板的语言标签): FTL标签和HTML标签有一些相似之处,但是它们是FreeMarker的指令,是不会在输出中打印的。 这些标签的名字以 # 开头。**(用户自定义的FTL标签则需要使用 @ 来代替 #)**
注释注释和HTML的注释也很相似, 但是它们使用 <#– and –> 来标识。
其他任何不是FTL标签,插值或注释的内容将被视为静态文本, 这些东西不会被FreeMarker所解析;会被按照原样输出出来。
FTL标签也被称为 指令。 这些指令在HTML的标签 (比如:
) 和HTML元素 (比如: table 元素) 中的关系是相同的。(如果现在还没有感觉到它们的不同, 那么把“FTL标签”和“指令”看做是同义词即可。)
指令
http://freemarker.foofun.cn/ref_directives.html
assign自定义变量指令
使用assign指令你可以创建一个新的变量,或者替换一个已经存在的变量。
1 | <#-- |
if指令
if条件渲染,<#if condition> 和 </#if> ,condition
如果为true,渲染,反之,不渲染。
1 | <html> |
<#else>标签:如同java中if{...}else{...}
1 | <#if condition> |
<#elseif>标签:如同java中if{...}else if{...}else{...}
1 | <#if condition1> |
list指令
当需要列表显示内容时,list指令是必须的。
1 | <p>We have these animals: |
那么输出结果将会是这样的:
1 | <p>We have these animals: |
list 指令的一般格式为: <#list sequence as loopVariable>repeatThis</#list>
。repeatThis 部分将会在给定的 sequence 遍历时在每一项中重复, 从第一项开始,一个接着一个。在所有的重复中, loopVariable 将持有当前遍历项的值。 这个变量仅存在于 <#list …> 和 </#list> 标签内。sequence 可以是任意表达式, 比如我们可以列表显示示例数据模型中的水果,就像这样:
1 | <ul> |
上面示例中的一个问题是如果我们有0个水果,它仍然会输出一个空的 ,而不是什么都没有。 要避免这样的情况,可以这么来使用 list:
1 | <#list misc.fruits> |
此时, list 指令将列表视为一个整体, 在 items 指令中的部分才会为每个水果重复。 如果我们有0个水果,那么在 list 中的所有东西都被略过了, 因此就不会有 ul 标签了。
<#sep>
标签:
列表相关的常见任务是:使用一些分隔符来列出水果,比如逗号:
1 | <p>Fruits: <#list misc.fruits as fruit>${fruit}<#sep>, </#list> |
输出:
1 | <p>Fruits: orange, banana |
被 sep 覆盖的部分(我们也可以这么来写: …<#sep>, </#sep></#list>) 只有当还有下一项时才会被执行。 因此最后一个水果后面不会有逗号。
再次回到这个话题,如果我们有0个水果,会怎么样?只是打印 “Fruits:” 也没有什么不方便。 list 指令,也像 if 指令那样,可以有 else 部分,如果列表中有0个元素时就会被执行:
1 | <p>Fruits: <#list misc.fruits as fruit>${fruit}<#sep>, <#else>None</#list> |
事实上,这个过于简单的示例可以这么来写, 但是它使用了本主题中没有介绍的语言特性:
<p>Fruits: ${fruits?join(", ", "None")}
所有的这些指令(list
, items
, sep
, else
)可以联合起来使用:
1 | <#list misc.fruits> |
macro自定义指令(宏)
可以使用macro指令来定义一些自定义指令
1 | <#-- |
nested占位(嵌套)指令
nested指令执行自定义指令开始和结束标签中间的模板片段。嵌套的片段可以包含模板中任意合法的内容。
1 | <#-- |
import导入指令
import指令可以导入一个库。也就是说,它创建一个新的命名空间,然后在那个命名空间中执行给定路径的模板。可以使用引入的空间中的指令。
commons.ftl
1 | <#macro macroName> |
在其他ftl页面中通过import导入commons.ftl的命名空间,使用该命名空间中的指令。
test.ftl
1 | <#-- 导入命名空间--> |
include 指令
使用 include 指令, 我们可以在模板中插入其他文件的内容。
假设要在一些页面中显示版权声明的信息。那么可以创建一个文件来单独包含这些版权声明, 之后在需要它的地方插入即可。比方说,我们可以将版权信息单独存放在页面文件 copyright_footer.html 中:
1 | <hr> |
当需要用到这个文件时,可以使用 include 指令来插入:
1 | <html> |
此时,输出的内容为:
1 | <html> |
当修改了 copyright_footer.html 文件, 那么访问者在所有页面都会看到版权声明的新内容。
使用内建函数
内建函数很像子变量(如果了解Java术语的话,也可以说像方法), 它们并不是数据模型中的东西,是 FreeMarker 在数值上添加的。 为了清晰子变量是哪部分,使用 ?(问号)代替 .(点)来访问它们。常用内建函数的示例:
user?html 给出 user 的HTML转义版本, 比如 & 会由 & 来代替。
user?upper_case 给出 user 值的大写版本 (比如 “JOHN DOE” 来替代 “John Doe”)
animal.name?cap_first 给出 animal.name 的首字母大写版本(比如 “Mouse” 来替代 “mouse”)
user?length 给出 user 值中 字符的数量(对于 “John Doe” 来说就是8)
animals?size 给出 animals 序列中 项目 的个数(我们示例数据模型中是3个)
如果在 <#list animals as animal> 和对应的 </#list> 标签中:
animal?index 给出了在 animals 中基于0开始的 animal的索引值
animal?counter 也像 index, 但是给出的是基于1的索引值
animal?item_parity 基于当前计数的奇偶性,给出字符串 “odd” 或 “even”。在给不同行着色时非常有用,比如在 中。
一些内建函数需要参数来指定行为,比如:
animal.protected?string(“Y”, “N”) 基于 animal.protected 的布尔值来返回字符串 “Y” 或 “N”。
animal?item_cycle(‘lightRow’,’darkRow’) 是之前介绍的 item_parity 更为常用的变体形式。
fruits?join(“, “) 通过连接所有项,将列表转换为字符串, 在每个项之间插入参数分隔符(比如 “orange,banana”)
user?starts_with(“J”) 根据 user 的首字母是否是 “J” 返回布尔值true或false。
内建函数应用可以链式操作,比如user?upper_case?html 会先转换用户名到大写形式,之后再进行HTML转义。(这就像可以链式使用 .(点)一样)
可以阅读 全部内建函数参考。
处理不存在的变量
数据模型中经常会有可选的变量(也就是说有时并不存在)。 除了一些典型的人为原因导致失误外,FreeMarker 绝不能容忍引用不存在的变量, 除非明确地告诉它当变量不存在时如何处理。这里来介绍两种典型的处理方法。
这部分对程序员而言: 一个不存在的变量和一个是 null 值的变量, 对于FreeMarker来说是一样的,所以这里所指的”丢失”包含这两种情况。
不论在哪里引用变量,都可以指定一个默认值来避免变量丢失这种情况, 通过在变量名后面跟着一个 !(叹号,译者注)和默认值。 就像下面的这个例子,当 user 不存在于数据模型时, 模板将会将 user 的值表示为字符串 “visitor”。(当 user 存在时, 模板就会表现出 ${user} 的值):
1 | <h1>Welcome ${user!"visitor"}!</h1> |
也可以在变量名后面通过放置 ?? 来询问一个变量是否存在。将它和 if 指令合并, 那么如果 user 变量不存在的话将会忽略整个问候的代码段:
1 | <#if user??><h1>Welcome ${user}!</h1></#if> |
关于多级访问的变量,比如 animals.python.price, 书写代码:animals.python.price!0 当且仅当 animals.python 永远存在, 而仅仅最后一个子变量 price 可能不存在时是正确的 (这种情况下我们假设价格是 0)。 如果 animals 或 python 不存在, 那么模板处理过程将会以”未定义的变量”错误而停止。为了防止这种情况的发生, 可以如下这样来编写代码 (animals.python.price)!0。 这种情况就是说 animals 或 python 不存在时, 表达式的结果是 0。对于 ?? 也是同样用来的处理这种逻辑的; 将 animals.python.price?? 对比 (animals.python.price)??来看。
数据类型
布尔型:等价于java的Boolean类型,不同的是不能直接输出,可转换为字符串输出。
日期型:等价于java的Date类型,不同的是不能直接输出,可转换为字符串输出。
数值型:等价于Java中的int,float,double等数值类型。
有三种显示形式:数值型(默认)、货币型、百分比型。
字符型:等价于Java中的字符串,有很多内置函数。
sequence(序列)类型:等价于Java中的数组,list,set等集合类型。
hash类型:等价于java中Map类型。
布尔类型
变量flag是布尔类型,在freemarker页面中不能直接输出,需要转换为字符串输出。
方式一:?c。
方式二:?string或?string(‘1’,’2’)。
1 | ${flag} //这是错的 |
日期类型
变量myDate是日期类型。在freemarker中日期类型不能直接输出;如果输出要先转成日期型或字符串。
年月日 ?date
时分秒 ?time
年月日时分秒 ?datetime
指定格式 ?string(“自定义格式”)
- y:年 M:月 d:日
- H:时 m:分 s:秒
1 | ${myDate?date} |
数值类型
数值类型:
在freemarker中数值类型可以直接输出:
转字符串
- 普通字符串 ?c
- 货币型字符串 ?string.currency
- 百分比型字符串 ?string.percent
保留浮点型数值指定小数位(##表示小数位)
- ?string[“0.##”]
字符串类型
在freemarker中字符串类型可以直接输出:
- 截取字符串(左闭右开)?substring(start,end)
- 首字母小写输出 ?uncap_first
- 首字母大写输出 ?cap_first
- 字母转小写输出 ?lower_case
- 获取字符串长度 ?length
- 是否以指定字符开头(boolean类型)?starts_with(“xx”)?string
- 是否以指定字符结尾(boolean类型)?ends_with(“xx”)?string
- 获取指定字符的索引 ?index_of(“xx”)
- 去除字符串前后空格 ?trim
- 替换指定字符串 ?replace(“xx”,”xx”)
sequence类型
- 通过list指令输出序列
<#list 序列名 as 元素名>
${元素名}
</#list>
- 获取序列的长度 ${序列名?size}
- 获取序列元素的下标 ${元素名?index}
- 获取第一个元素 ${序列名?first}
- 获取最后一个元素 ${序列名?last}
倒序输出 序列名?reverse
升序输出 序列名?sort
降序输出 序列名?sort?reverse
指定字段名排序 序列名?sort_by(“字段名”)
hash类型
key遍历输出
<#list hashName?keys as key>
${key} – ${hash[key]}
</#list>
value遍历输出
<#list hash?values as value>
${value}
</#list>
空值情况处理
!
:指定缺失变量的默认值- ${value!}:如果value值为空,则默认值是空字符串。
- ${value?”默认值”}:如果value值为空,则默认值是字符串“默认值”
??
:判断变量是否存在- 如果变量存在,返回true。否则返回false。
${(value??)?string}
- 如果变量存在,返回true。否则返回false。
FreeMarker页面静态化
通过上述介绍可知 Freemarker是一种基于模板的、用来生成输出文本的通用工具,所以我们必须要定制符合自己业务的模板,然后生成自己的html页面。Freemarker是通过freemarker.template. Configuration这个对象对模板进行加载的(它也处理创建和缓存预解析模板的工作),然后我们通过 getTemplate方法获得你想要的模板,有一点要记住
freemarker.template.Configuration在你整个应用必须保证唯一实例。
对不需要频繁改变的数据进行静态化处理,减少服务器/数据库的访问压力。
FreeMarker运算符
算术运算符
1 | <!-- |
逻辑运算符
1 | <#-- |
比较运算符
1 | <#-- |
空值运算符
1 | 空值运算符 |