FreeMarker 模板引擎

Apache FreeMarker 是一个使用 Java 语言编写的模板引擎,用于根据 template(模板)和 java objects(数据)来生成文本输出(HTML 网页,电子邮件,配置文件,源代码等)。模板是用 FreeMarker 模板语言(FreeMarker Template Language,FTL)编写的,这是一种简单的专用语言(不像 PHP 这样的完整编程语言)。通常,使用通用编程语言(如 Java)来准备数据(发起数据库查询,进行业务计算等)。然后,Apache FreeMarker 使用模板显示准备好的数据。在模板中,您将专注于如何呈现数据,而在模板之外,您将关注于要呈现的数据。

简单介绍

FreeMarker 原理简介
FreeMarker 原理简介

这种方法通常被称为 MVC(模型 + 视图 + 控制器)模式,并且特别受动态网页的欢迎。它有助于将网页设计者(前端)与 Java 开发人员(后端)分开。前端人员不会在模板中面对复杂的逻辑,并且可以在程序员不必更改或重新编译代码的情况下更改页面的外观。虽然 FreeMarker 最初是为在 MVC Web 应用程序框架中生成 HTML 页面而创建的,但它并不局限于 Servlet 或 HTML 或与 Web 相关的任何内容,它也用于非 Web 应用程序环境,因为 FreeMarker 实际上就是一个普通的 Java 类库,就类似 Apache Commons CLI 一样,是一个非常简单的 Java 类库。所以我们完全在普通 java 程序中使用 freemarker,来动态的生成文本文件,并不局限于 html 文件的生成,因为本质上,freemarker 就是一个文本处理引擎,替换文本中的特殊模式占位符(FTL 语言),仅此而已,其它什么东西 freemarker 根本不关心。

FreeMarker 的一些亮点

  • 强大的模板语言:条件块、迭代、赋值、字符串运算、算术运算,自定义宏和内置函数等等
  • 多用途和轻量级:零依赖性,任何输出格式,可以从任何地方(可插入)加载模板,许多配置项
  • 国际化/本地化感知:区域设置敏感数字和日期/时间格式,本地化模板变体
  • XML处理功能:将 XML DOM 放入数据模型并遍历它们,甚至以声明方式处理它们
  • 多功能数据模型:Java 对象通过可插拔适配器作为变量树暴露给模板,该适配器决定模板如何看待它们

HelloWorld

项目结构

freemarker 模版文件的后缀名一般为:*.ftl*.ftlh(html)、*.ftlx(xml);通常将它放到 classpath 下,在 maven 中就是 resources 目录下。

pom.xml

jdbcproperty.ftl

freemarker 的注释语法为 <#-- comments -->,第一行注释的作用是告诉 idea 后面的 ${property} 来源哪个 java 对象,当然也可以没有这行。

JdbcProperty.java

JdbcPropertyTest.java

执行结果

简要解析

可以感觉到,freemarker 和 jsp 其实非常相似,但两者又有不同,freemarker 只是一个文本处理引擎,而 jsp 只能用于 servlet 容器环境中,脱离这个环境就一无是处了,jsp 本质就是 servlet 的高级抽象,底层依旧会被转换为 servlet,并编译为 class 文件,装载到 jvm 永久代中;但是 freemarker 只会缓存模版文件,并不会生成新的 class 类文件,也不会占用永久代的内存空间。

freemarker 和 jsp 获取数据的语法都是相似的:${property},在上面这个例子中,我们是使用 jdbcProperty bean/pojo 对象作为数据载体的,其中 ${url} 就是调用该对象的 getUrl() 方法获取的,${driver} 调用 getDriver() 获取,${username} 调用 getUsername() 获取,${password} 调用 getPassword() 获取,与 jsp 没什么两样。除了直接使用 bean/pojo 对象作为数据来源之外,也可以使用 java.util.Map 来作为数据来源,本质上它们两者都可以抽象为 object(可以与 javascript 中的 object 类比起来)。

template + dataModel = output

  • template 就是我们所说的模板文件,模板文件中有一些 FTL 语法占位符(比如上面的 ${username}),待解析。
  • dataModel 通常是 java.util.Map<String, Object> 或 bean/pojo 对象(getter、setter 方法),用来填充模板。
  • 当我们调用 template 对象的 process(dataModel, writer) 方法时,freemarker 会自动使用 dataModel 来填充 template。

上面的例子中,我们使用的是 bean/pojo 对象作为 dataModel,我们也可以使用 Map 来替代它:

执行结果:

dataModel 相关解析
freemarker 的 dataModel 其实就是一个 key-value 键值对结构,在 java 中,bean/pojo 和 Map<String, Object> 均符合这个要求,Map 比较好理解,key 就是属性名,比如 key 为 "url",那么就可以通过 ${url} 来获取它的值;而 bean/pojo 其实也差不多,getter 方法就是对应的属性名(去除 get,首字母小写),而 getter 方法的返回值就是属性的值,假设 bean/pojo 存在一个 getUrl() 方法,那么就可以通过 ${url} 来获取它的值(调用 getUrl() 来获取)。

所以本质上,这两种类型都可以概括为 Map<String, Object> 类型;key 是字符串类型,作为 freemarker 中的变量名;value 是 Object 类型(任意对象),作为对应变量名的值(调用对象的 toString() 方法来获取对象的字符串表示)。一些比较常用的数据类型:

  • String:字符串
  • Number:数值
  • Boolean:布尔值
  • Date/Time:日期、时间
  • List/Array:列表、built-in 数组
  • Map<String, Object>:关联数组
  • Java Bean/POJO 对象,getter 方法就是属性名

和 jsp 一样,有两种访问对象/Map 属性的方式:object['property']object["property"]object.property。字符串可以使用单引号、双引号来表示,可以使用 + 操作符连接字符串。数值不支持科学记数法,支持 +、- 正负数,整数、小数都属于数值类型,如 103.14-100。布尔值的字面量也有两个:truefalse。List、Array 可以通过索引来获取元素的值,如 list[0]array[1],注意索引值也是从 0 开始。

FTL 语法简介

基本结构

Template:

DataModel:

Output:

DataModel 是一个倒立的树状结构,root 就是所谓的根节点,但其实在 freemarker 中这个节点没什么意义,通常将它看作为一个容器,我们主要是使用容器中的元素,比如第一级元素为:userlatestProduct。注意 latestProduct 元素下面还有两个子元素,user 元素是 String 类型,latestProduct 元素可能是 Map<String, Object> 类型,有两个 key:"url""name",这两个 key 分别对应两个字符串类型;也可能是 java bean/pojo 类型,存在两个方法:String getUrl()String getName()。访问第一级元素就是直接通过元素名来访问,比如访问 user 元素就是使用 ${user},访问 latestProduct 元素就是使用 ${latestProduct},访问 latestProduct 元素的 url 子元素就是使用 ${latestProduct.url},访问 latestProduct 元素的 name 子元素就是使用 ${latestProduct.name}

hash 哈希类型(animals.mouse.price):

list 列表类型(animals[0].namemisc.fruits[1]):

  • data-model:数据模型可以可视化为树。
  • scalars:标量存储单个值。值可以是字符串、数字、布尔值,日期、时间。
  • hashes:哈希是存储其他变量并将其与唯一查找名称相关联的容器(关联数组、Map/Bean)。
  • sequences:序列是以有序序列存储其他变量的容器。存储的变量可以通过它们的数字索引从 0 开始检索。

基本语法

  • 插值 ${...}:FreeMarker 将在输出中将其替换为大括号内表达式的实际值。
  • 内置标签 <#tag statement> ... </#tag>:FTL 标签类似于 HTML 标签,但它们是 FreeMarker 的指令,不会打印到输出。
  • 自定义标签 <@tag statement> ... </@tag>:FreeMarker 允许我们创建自定义标签,与内置标签的区别是使用 @ 而不是 #
  • 注释标签 <#-- comments -->:FTL 注释与 HTML 注释有一点不同,那就是 FTL 注释不会进入输出,但 HTML 注释会,注释会被忽略。

任何不是 FTL 标签、FTL 插值、FTL 注释的东西都被认为是静态文本,FreeMarker 不会解释它们,这些文本只会被按原样打印到输出。

基本指令

这里指的指令就是 FTL 标签(内置标签),如 iflistinclude

<#if condition> ... </#if>

<#list items as item> ... </#list>

<#switch value> ... </#switch>

<#include "/path/to/included.file">

内置函数

一些最常用的内置插件的示例:

  • user?length 获取 user 字符串的长度
  • animals?size 获取 animals 集合的大小
  • user?upper_case 将 user 转换为大写字母形式
  • animal.name?cap_first 将 animal.name 首字母转换为大写
  • <#list animals as animal> 标签中,还可以使用这些方法:
    • animal?index 当前元素的基于 0 的索引值
    • animal?counter 当前元素的基于 1 的索引值
    • animal?item_parity 当前元素的奇偶字符串,"odd""even"
    • animal?item_cycle('lightRow', 'darkRow') item_parity 的通用变体
  • animal.protected?string("Y", "N") 返回字符串“Y”(true)或“N”(false)
  • fruits?join(", ") 使用指定的分隔符连接列表的所有元素,如 "orange, banana"
  • user?starts_with("J") 如果 user 字符串以字符 J 开头,则返回 true,否则返回 false
  • fruits?join(", ")?upper_case 链式调用,先将列表 fruits 转换为字符串,然后将其转换为大写

缺失变量

默认情况下,访问不存在的变量或者访问值为 null 的变量,FreeMarker 将会抛出异常。FreeMarker 对于缺失变量(不存在的变量或值为 null 的变量统称为“缺失变量”)是不能容忍的,这与 JSP 不相同。之所以不能容忍缺失变量,是因为缺失变量没实际意义,正常且健壮的程序不应该出现 null 值,更不应该访问不存在的变量。FreeMarker 提供两种常用的解决方式来处理“变量缺失问题”:

  • 使用 variable!default 语法来提供默认值,如 ${user!"visitor"};注意,对于链式访问的表达式,需要加上圆括号,否则 ! 操作符实际上只会对最后一个属性起到保护作用,即 (animals.python.price)!0 而不是 animals.python.price!0,对于后者,如果 animals 或 python 不存在或为 null 值,也会导致异常的抛出。
  • 使用 <#if user??>Welcome ${user}!</#if> 语法来判断变量是否缺失,实际上就是调用 ? 内置方法来判断变量是否缺失,如果变量未缺失,则正常执行内部的条件语句。同样的,对于链式访问的表达式,也是要加上圆括号的,如 (animals.python.price)??。实际上我们也可以使用 user?exists 来替代 user??,它们是等价的;其它一些处理方法:
    • ${user.name},抛出异常
    • ${user.name!},显示空字符串
    • ${user.name!'vakin'},显示 'vakin'
    • ${user.name?default('vakin')},同上,显示 'vakin'
    • ${user.name???string(user.name,'vakin')},同上,显示 'vakin'

字符转义

对于 HTML 和 XML 文档,因为存在一些特殊字符,所以如果要在正常文本中输出这些特殊字符(如 <>),我们必须进行转义,否则会破坏文档的语法结构。FreeMarker 支持 HTML 文档和 XML 文档的自动转义功能,我们知道,FTL 模板文件的通用后缀名为 .ftl,如果使用通用后缀名则不会自动进行 HTML 或 XML 转义,如果模板文件后缀名为 .ftlh 则会自动激活 HTML 的转义功能,如果模板文件的后缀名为 .ftlx 则会自动激活 XML 的转义功能;当然,除了通过设置模板文件的后缀名来自动激活转义功能之外,我们也可以在模板文件中通过 ftl 指令的 output_format 配置来告诉 FreeMarker 当前模板文件的格式为 HTML 还是 XML,如 <#ftl output_format="HTML"> 为 HTML 转义形式,注意需要添加到模板文件的首行。如果当前模板文件为 HTML 语法或 XML 语法,那么 FTL 指令、插值的输出就会被自动进行转义,但如果你确实需要输出原字符串,也就是不进行转义,也可以通过调用 FTL 的 ?no_esc 内置函数来取消自动转义,而输出原字符串值。

FTL 标签

FTL 标签的 start-tag 和 end-tag 语法:

  • Start-tag:<#directivename parameters>
  • End-tag:</#directivename>

当然,有些标签没有关闭标签,如 <#ftl output_format="HTML">。FTL 标签之间不可以进行嵌套,FreeMarker 会自动忽略标签内部的空白符(标签内部的内容允许换行),内置标签使用 <#tag param> ... </#tag> 表示,用户自定义标签使用 <@tag param> ... </@tag> 表示。对于自关闭 FTL 标签,有两种形式 <#tag param><#tag param/>,FreeMarker 对此没有什么要求,一般为了方便,大家都不会去写 / 正斜杠。

创建变量

常用运算符

  • +-*/%:加、减、乘、除、取余
  • &&||!():逻辑与、逻辑或、逻辑非、括号(分组、改变优先级等)
  • ==!=<(lt)、<=(lte)、>=(gte)、>(gt):等于、不等于、小于、小于等于、大于等于、大于
  • =+=-=*=/=%=++--:赋值、加赋值、减赋值、乘赋值、除赋值、取余赋值、自增、自减
  • ["foo", "bar", 123.45] 定义列表、{"name":"green mouse", "price":150} 定义关联数组、0..9 范围序列

FTL 语法示例

stringFreeMarker.ftl

otherFreeMarker.ftl

输出结果:

整合 SpringMVC

项目结构

这里依旧以“员工管理系统”为例,SpringMVC + MyBatis + FreeMarker

pom.xml

logback.xml

jdbc.properties

WebConfig.java

MvcConfig.java

Employee.java

EmployeeMapper.java

EmployeeMapper.xml

EmployeeService.java

EmployeeServiceImpl.java

EmployeeController.java

index.jsp

employee-list.ftl

employee-edit.ftl