Freemarker

发布时间 2023-03-22 21:17:08作者: 末末随笔

Freemarker

FreeMarker是一款模板引擎: 即一种基于模板和要改变的数据, 并用来生成输出文本(HTML网页、电子邮件配置文件源代码等)的通用工具。 它不是面向最终用户的,而是一个Java类库,是一款程序员可以嵌入他们所开发产品的组件。

FreeMarker模板编写为FreeMarker Template Language(FTL),属于简单、专用的语言。需要准备数据在真实编程语言中来显示,比如数据库查询和业务运算, 之后模板显示已经准备好的数据。在模板中,主要用于如何展现数据, 而在模板之外注意于要展示什么数据 。

发展历史

FreeMarker最初的设计,是被用来在MVC模式的Web开发框架中生成HTML页面的,没有被绑定到ServletHTML或任意Web相关的东西上,它也可以用于非Web应用环境中。

1999年末,FreeMarker的第一个版本出现在SourceForge网站上,它最初是由Benjamin Geer和Mike Bayer编写,他们定义了FreeMarker最基本的语法。FreeMarker 1获得了LGPL(宽通用公共许可证)的许可,其版权归属于Benjamin Geer。此外,Nicholas Cull、Holger Arendt等人对该项目也做出了主要贡献。

在2002年初,Jonathan Revusky用JavaCC重写了FreeMarker的核心代码(语法编译),虽然对FreeMarker 1尽量做到向后兼容,但几乎是完全重写了。Attila Szegedi对FreeMarker 2也有重要影响,除了重构和优化一些核心的API应用程序编程接口),Attila还作为主要编写者实现了FreeMarker对日期、时间的支持,写出的freemarker.ext*包完成对javabeanJythonXML的映射,以及HTTP servletJSP和Ant的集成。Dániel Dékány主要负责文档以及项目的维护(截至2011年,Dániel Dékány仍是该项目的主要维护者)。

2002年3月18日,FreeMarker的第一个发布候选版2.0 RC1发布 [3] ,又经过了2个候选版的BUG修复之后,正式版的Free Marker2.0于2002年4月18日发布 。2002年10月17日,FreeMarker 2.1 发布,该版本并不能与2.0版本兼容,所以使用者如果不是新建工程的话,需要重新审视已有的代码和模版 。

由于项目没有法律实体,FreeMarker的2.0.x和2.1.x的版权仍归属Benjamin Geer。而在2002年12月制作2.2版本时,Benjamin Geer出于对自由开源许可的理解,将代码库版权转给Visigoth Software Society(西班牙的一个非营利性软件协会)和共同创办人Jonathan Revusky 。

2003年3月27日,FreeMarker 2.2 发布,这个版本引入了一些非常重要的新特性,但是有一些功能却不能逆向兼容 。在2.2的版本中,可能最重要的新特性就是namespace支持,这使得FreeMarker成为了合适大规模项目的工具,因为它允许不同页面分享的变量没有任何名称空间冲突。同时,宏也变得更加强大,因为他们可以调用可选目标,并且宏现作为一流的变量,可以传递给其他宏使用。此外2.2中另一个吸引人的特性为,FreeMarker可以利用由第三方所写的JSP标记库 。在此之后,2.3版本之前,共更新了8个版本 。

2004年6月15日,FreeMarker 2.3 发布,此版本对2.2系列进行了质量上的改进,以及引入了大量的新功能。最主要的改进点在于可以定义函数(方法)模版,插入字符串变量,支持宏参数和更为智能的默认对象包装。但2.3并不支持2.2.x的向后兼容,所以仅供新项目使用 。

2005年1月4日的2.3.1版本到10月10日2.3.4版本主要是编写和维护一些新特性,以及BUG错误修复。2006年3月11日发布的2.3.5版本,因为发现严重错误而被撤回 ,在后续的2.3.6版本中修复。2.3.7时出了一个测试版本用于BUG修正和FreemarkerServlet的改进,其正式版中新增substring用于处理空的或缺失的变量 。

模板引擎的执行流程

2006年7月9日发布2.3.8版本,提高了对JSP2.0的兼容性。

2007年1月23日发布2.3.9版本,包含了对JDK 1.5枚举和通过BeansWrapper公共类字段的支持 。

2007年4月20的2.3.10版本到2009年12月10日的2.3.16版本都是一些小性能改进和BUG修复。

2011年5月17日,FreeMarker 2.3.17 发布,该版本主要进行了安全性的修复并扩充了一些内建函数。

2011年5月22日,FreeMarker 2.3.18 发布,修复jar包相关的bug。

2012年2月29日,FreeMarker 2.3.19 发布,该版本修复了两个重要的bug,另外新增对JSON字符串进行处理的方法json_string等小改动 。

2013年6月27日,FreeMarker 2.3.20 发布,主要对于使用IDE工具的修改 。

2014年10月12日,FreeMarker 2.3.21 发布,对Java版本的最低要求从1.2变为1.4。由于旧的BSD风格许可不被OSI所承认,且Visigoth Software Society停滞不前,其许可变更为Apache 2.0版,所有者转为Attila Szegedi、Daniel Dekany和Jonathan Revusky(FreeMarker 2的主要开发者)] 。

2015年3月1日,FreeMarker 2.3.22 发布,在FTL模板和Java方面做了一些更改 。

2015年7月1日,FreeMarker经过投票进入了Apache Incubator,其项目授予给Apache软件基金会

2015年7月5日,FreeMarker 2.3.23 发布,在FTL模板和Java上做了大量修改。尤其增加了list中items和else的字指令,使常见遍历任务更简单 。

2015年9月2日,FreeMarker的主代码库从GitHub导入到Apache软件基金会的基础设施中发展 。

2018年3月21日,FreeMarker在Apache Incubator中升级为顶级项目 。

工作原理

假设在一个应用系统中需要一个HTML页面如下:

`<``html``>``    ``<``head``>``        ``<``title``>Welcome!</``title``>``    ``</``head``>``    ``<``body``>``        ``<``h1``>Welcome Big Joe!</``h1``>``        ``<``p``>Our latest product:``        ``<``a` `href``=``"products/greenmouse.html"``>green mouse</``a``>!``    ``</``body``>``</``html``>`

页面中的用户名(即上面的“Big Joe”)是登录这个网页的访问者的名字, 并且最新产品的数据应该来自于数据库才能随时更新。所以,不能直接在HTML页面中输入“Big Joe”、“greenmouse”及链接, 不能使用静态HTML代码。可以使用要求输出的模板来解决,模板和静态页面是相同的,只是它会包含一些FreeMarker将它们变成动态内容的指令:

`<``html``>``    ``<``head``>``        ``<``title``>Welcome!</``title``>``    ``</``head``>``    ``<``body``>``        ``<``h1``>Welcome ${user}!</``h1``>``        ``<``p``>Our latest product:``        ``<``a` `href``=``"${latestProduct.url}"``>${latestProduct.name}</``a``>!``    ``</``body``>``</``html``>`

模板文件存放在Web服务器上,当有人来访问这个页面,FreeMarker就会介入执行,然后动态转换模板,用最新的数据内容替换模板中${**...}**的部分,之后将结果发送到访问者的Web浏览器中。访问者的Web浏览器就会接收到例如第一个HTML示例那样的内容(也就是没有FreeMarker指令的HTML代码),访问者也不会察觉到服务器端使用的FreeMarker。(存储在Web服务器端的模板文件是不会被修改的;替换也仅仅出现在Web服务器的响应中。)

为模板准备的数据整体被称作为数据模型。数据模型是树形结构(就像硬盘上的文件夹和文件),在视觉效果上, 数据模型可以是(这只是一个形象化显示,数据模型不是文本格式,它来自于Java对象):

`(root)``  ``|``  ``+- user = ``"Big Joe"``  ``|``  ``+- latestProduct``      ``|``      ``+- url = ``"products/greenmouse.html"``      ``|``      ``+- name = ``"green mouse"`

早期版本中,可以从数据模型中选取这些值,使用user和latestProduct.name表达式即可。类比于硬盘的树形结构,数据模型就像一个文件系统,“(root)”和latestProduct就对应着目录(文件夹),而user、url和name就是这些目录中的文件。

总体上,模板和数据模型是FreeMarker来生成输出所必须的组成部分:模板 + 数据模型 = 输出

基本语法

  • ${...}:FreeMarker将会输出真实的值来替换大括号内的表达式,这样的表达式被称为interpolation(插值)。

  • 注释:注释和HTML的注释也很相似,但是它们使用<#-- and -->来标识。不像HTML注释那样,FTL注释不会出现在输出中(不出现在访问者的页面中),因为FreeMarker会跳过它们。

  • FTL标签(FreeMarker模板的语言标签):FTL标签和HTML标签有一些相似之处,但是它们是FreeMarker的指令,是不会在输出中打印的。这些标签的名字以#开头。(用户自定义的FTL标签则需要使用@来代替#) [20]

 

指令

`<``#if condition>``    ``...``<``#elseif condition2>``    ``...``<``#elseif condition3>``    ``...``<``#else>``    ``...``</``#if>`

if、elseif和else指令可以用来条件判断是否越过模板的一个部分。condition必须计算成布尔值,否则错误将会中止模板处理。elseif和else必须出现在if内部(也就是在if的开始标签和结束标签之间)。if中可以包含任意数量的elseif(包括0个),而结束时else也是可选的 [21] 。

`假设 ``users` `包含[``'Joe'``, ``'Kate'``, ``'Fred'``] 序列:``<``#list users as user>``    ``<p>${user}``</``#list>` `输出:``    ``<p>Joe``    ``<p>Kate``    ``<p>Fred`

list指令执行在list开始标签和list结束标签(list中间的部分)之间的代码,对于在序列(或集合)中每个值指定为它的第一个参数。对于每次迭代,循环变量将会存储当前项的值。循环变量仅仅存在于list标签体内。而且从循环中调用的/函数不会看到它(就像它只是局部变量一样)。<#list>与<#else>、<#sep>组合是可选的,而且仅从FreeMarker 2.3.23版本开始支持 [22] 。

`将版权信息单独存放在页面文件 copyright_footer.html 中:``<``hr``>``<``i``>``    ``Copyright (c) 2000 <``a` `href``=``"http://www.baidu.com"``>Baidu Inc</``a``>,``    ``<``br``>``    ``All Rights Reserved.``</``i``>` `当需要用到这个文件时,可以使用 include 指令来插入:``<``html``>``    ``<``head``>``        ``<``title``>Test page</``title``>``    ``</``head``>``    ``<``body``>``        ``<``h1``>Test page</``h1``>``        ``<``p``>Blah blah...``        ``<#include "/copyright_footer.html">``    ``</``body``>``</``html``>`

include可以在模板中插入另外一个FreeMarker模板文件(由路径参数指定)。被包含模板的输出格式是在include标签出现的位置插入的。被包含的文件和包含它的模板共享变量,就像是被复制粘贴进去的一样。include指令不能由被包含文件的内容所替代,它只是当FreeMarker每次在模板处理期间到达include指令时处理被包含的文件。所以对于如果include在list循环之中的例子,可以为每个循环周期内指定不同的文件名 [23] 。

 

内建函数

常用处理

内建函数很像子变量(也像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序列中项目的个数

  • 如果在<#list animals as animal>和对应的标签中:

    • 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转义,和链式使用.(点)一样 [20] 。

 

空变量

数据模型中经常会有可选的变量(有时并不存在)。除了一些人为原因导致失误外,FreeMarker不能引用不存在的变量,除非明确地告诉它当变量不存在时如何处理,如下两种典型的处理方法:

这部分对程序员而言:一个不存在的变量和一个是null值的变量,对于FreeMarker来说是一样的,所以这里所指的“丢失”包含这两种情况。

不论在哪里引用变量,都可以指定一个默认值来避免变量丢失这种情况,通过在变量名后面跟着一个 !(感叹号)和默认值。像下面的这个例子,当user不存在于数据模型时,模板将会将user的值表示为字符串 “visitor”。(当 user 存在时,模板就会表现出 ${user} 的值):

`<``h1``>Welcome ${user!"visitor"}!</``h1``>`

也可以在变量名后面通过放置??来询问一个变量是否存在。将它和if指令合并,那么如果user变量不存在的话将会忽略整个问候的代码段:

`<``#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)??来看 [20] 。

性能特点

模板并没有包含程序逻辑来查找当前的访问者是谁,或者去查询数据库获取最新的产品。显示的数据是在FreeMarker之外准备的,通常是一些“真正的”编程语言(比如Java)所编写的代码。模板作者无需知道这些值是如何计算出的。事实上,这些值的计算方式可以完全被修改,而模板可以保持不变,而且页面的样式也可以完全被修改而无需改动模板。当模板作者(设计师)和程序员不是同一人时,显示逻辑和业务逻辑相分离的做法是非常有用的,即便模板作者和程序员是一个人,这么来做也会帮助管理应用程序的复杂性。保证模板专注于显示问题(视觉设计,布局和格式化)是高效使用模板引擎的关键 [19] 。

MyEclipse工具下的编辑界面

1.通用性

能够生成各种文本:HTMLXMLRTF、Java源代码等等。

易于嵌入到产品中:轻量级;不需要Servlet环境。

插件式模板载入器:可以从任何源载入模板,如本地文件、数据库等等。

可以按所需生成文本:保存到本地文件;作为Email发送;从Web应用程序发送它返回给Web浏览器。

2. 模板语言

所有常用的指令:include、if/elseif/else、循环结构

在模板中创建和改变变量。

几乎在任何地方都可以使用复杂表达式来指定值。

命名的宏,可以具有位置参数和嵌套内容。

名字空间有助于建立和维护可重用的宏库,或者将一个大工程分成模块,而不必担心名字冲突。

输出转换块:在嵌套模板片段生成输出时,转换HTML转义、压缩、语法高亮等等;可以定义自己的转换。

3. 通用数据模型

FreeMarker不是直接反射到Java对象,Java对象通过插件式对象封装,以变量方式在模板中显示。

可以使用抽象(接口)方式表示对象(JavaBeanXML文档、SQL查询结果集等等),告诉模板开发者使用。方法,使其不受技术细节的打扰。

xml配置

\4. 为Web准备

在模板语言中内建处理典型Web相关任务(如HTML转义)的结构。

能够集成到Model2 Web应用框架中作为JSP的替代。

支持JSP标记库。

为MVC模式设计:分离可视化设计和应用程序逻辑;分离页面设计员和程序员。

5. 智能的国际化和本地化

字符集智能化(内部使用UNICODE)。

数字格式本地化敏感。

日期和时间格式本地化敏感。

非US字符集可以用作标识(如变量名)。

多种不同语言的相同模板。

6. XML处理能力

<#recurse> 和<#visit>指令(2.3版本)用于递归遍历XML树。

在模板中清楚和直接的访问XML对象模型 [24] 。