本文是Freemarker系列的第一篇,面向模板开发人员,主要介绍 FreeMarker 所使用的 FTL(FreeMarker Template Language) 语法,了解 Freemarker 的基本概念,介绍基本的 FTL 术语 及内置函数,内置指令,方便做为开发手册速查(文中演示所用版本为 2.3.30,实际使用中请根据本身项目版本自查官网)。html
本文不会罗列官网API,只在必要时演示其语法,代码工程中有课表明整理的 freemarker api 思惟导图,配合此文食用可以使功力大增!请到 课表明的 github自取。java
Freemarker
是一款纯 Java
编写的模板引擎软件,能够用来生成各类文本,包括但不限于:HTML
,E-Mail
以及各类源代码等等。python
它的主要任务就是:把模板和数据组装在一块儿,生成文档,这个过程又叫渲染(Render)。流程如图:git
因为大部分模板开发人员都是用它来生成HTML页面,因此本文将基于 SpringBoot(2.4.1)+Freemarker(2.3.30)+SpringWeb
演示 HTML 页面的渲染github
假设我想要一个简单页面用来欢迎当前用户,模板代码:spring
<html> <head> <title>index</title> </head> <body> <p>你好,${userName}</p> </body> </html>
${userName}
是 FTL 的插值语法,他会把userName
的值替换到生成的 HTML
中,从而根据当前登陆者,显示不一样的用户名,这个值由后端代码放到Model中,对应的 Controlelr 代码:apache
@Controller public class HelloWorld { @GetMapping("hello") public String hello(Model model) { model.addAttribute("userName","Java 课表明"); // 返回模板名称 return "index"; } }
访问页面:json
数据由后端代码经过数据模型(Model)传递,模板只关心数据如何展现(View),两者的关联关系由 Controller 来控制,这就是 MVC。segmentfault
Controller中添加到 model 中的数据是如何组织的呢?这就须要了解一下FTL的数据模型(data-model)。后端
FTL 的数据模型在结构上是一个树形:
(root) | +- animals | | | +- mouse | | | | | +- size = "small" | | | | | +- price = 50 | | | +- elephant | | | | | +- size = "large" | | | | | +- price = 5000 | | | +- python | | | +- size = "medium" | | | +- price = 4999 | +- message = "It is a test" | +- misc | +- foo = "Something"
其中的root
能够理解为 Controller 中的 model ,经过 model.addAttribute("userName","Java 课表明");
就能够往数据模型中添加数据。
数据模型中能够像目录同样展开的变量,如:root, animals, mouse, elephant, python, misc
称之为哈希(hash)。哈希的 key 就是变量名,value 就是变量存储的值,经过.
分隔的路径能够访问变量值,好比访问 mouse 的 price :animals.mouse.price
.
像animals.mouse.price
这样存储单个值的变量叫作标量(scalar),标量有四种具体类型:string,boolean,date-like,number;
还有一种变量叫作序列(sequence),能够类比为 Java 中的数组,序列中的每一个项没有名字,能够经过遍历,或者下标的方式访问(后面会演示序列的访问),它的数据结构看起来是这样的:
(root) | +- animals | | | +- (1st) | | | | | +- name = "mouse" | | | | | +- size = "small" | | | | | +- price = 50 | | | +- (2nd) | | | | | +- name = "elephant" | | | | | +- size = "large" | | | | | +- price = 5000 | | | +- (3rd) | | | +- name = "python" | | | +- size = "medium" | | | +- price = 4999 | +- misc | +- fruits | +- (1st) = "orange" | +- (2nd) = "banana"
FTL 里经常使用的数据类型就这三类:哈希(hashe),标量(scalar),序列(sequence)。
有了数据,还要有语法来组织这些数据,下面介绍 FTL 中的经常使用语法。
FreeMarker 只认以下三种语法:
<>
包裹起来,普通标签以<#
开头,用户自定义标签以<@
开头,如<#if true>true thing<#/if>
,<@myDirect></@myDirect>
你会看到两种叫法,1:标签(tags),2:指令(directive)。举个例子:<#if></#if>
叫标签; 标签里面的if
是指令,能够类比于html中的标签(如:<table></table>
)和元素(如:table
)。不过,把标签和指令认为是 同义词也没有问题。
<#-- 被注释掉的内容 -->
,对于注释,FTL会自动跳过,因此不会显示在生成的文本中(这点有别于 HTML 的注释)注意:除以上三种语法以外的全部内容,皆被 FreeMarker 视为普通文本,普通文本会被原样输出
插值就是单纯的替换变量的值,注释更没啥好说的,下面主要介绍几个最经常使用的 FTL 标签(指令)并结合代码演示其用法。
if 能够根据条件跳过模板中的某块代码,之前文为例,当 userName
值为 "Java课表明" 或zhengxl5566
时,用特殊样式展现,相关模板代码以下:
<p>你好, <#if userName == "Java 课表明"> <strong>${userName}</strong> <#elseif userName == "zhengxl5566"> <h1>${userName}</h1> <#else> ${userName} </#if> </p>
list用来遍历序列,其语法为:
<#list sequence as loopVariable> repeatThis </#list>
好比后台往 model 里放入一个 allUsers 的集合
model.addAttribute("allUsers",userService.getAllUser());
能够直接使用下标访问集合中的某个元素:${allUsers[0].name}
也能够在模板中直接遍历展现:
<ol> <#list allUsers as user> <li> 姓名:${user.name},年龄:${user.age} </li> </#list> </ol>
实际渲染出来的 HTML:
<ol> <li> 姓名:zxl,年龄:18 </li> <li> 姓名:ls,年龄:19 </li> <li> 姓名:zs,年龄:16 </li> </ol>
注意:假设 allUsers 是空的,渲染出来的页面会是
<ol></ol>
,若是须要规避这个状况,可使用 items 标签
<#list allUsers> <ol> <#items as user> <li> 姓名:${user.name},年龄:${user.age} </li> </#items> </ol> </#list>
此时,假设 allUsers是空的,list 标签中的 html 内容就不会被渲染出来。
include 指令能够把一个模板的内容插入到另外一个模板中(官方建议使用 import 代替,参见下文的最佳实践)。
假设咱们每一个页面都须要一个 footer,能够写一个公共的footer.ftlh模板,其他须要footer的页面只须要引用footer.ftlh模板便可:
<#include "footer.ftlh">
import 能够将模板中定义的变量引入当前模板,并在当前模板中使用。它和 include 的主要区别就是 import 能够将变量封装到新的命名空间中(后文会介绍 import 和 include 的对比)。
例如:模板 /libs/commons.ftl 里面写了不少公共方法,想在其余模板里引用,只须要在其余模板的开头写上:
<#import "/libs/commons.ftl" as com>
后续想使用/libs/commons.ftl 中的 copyright 方法,能够直接使用:
<@com.copyright date="1999-2002"/>
assign 能够用来建立新的变量并为其赋值,语法以下:
<#assign name1=value1 name2=value2 ... nameN=valueN> or <#assign name1=value1 name2=value2 ... nameN=valueN in namespacehash> or <#assign name> capture this </#assign> or <#assign name in namespacehash> capture this </#assign>
举例:
<#--建立字符串--> <#assign myStr = "Java 课表明"> <#--使用插值语法显示字符串--> myStr:${myStr}
macro 用来从模板上建立用户自定义指令(Java后端能够经过实现TemplateDirectiveModel
接口自定义指令,将在下一篇:《Freemarker 教程(二)-后端开发指南》中介绍)
macro 建立的也是变量,该变量能够作为用户自定义指令使用,好比下面的模板定义了 greet
指令:
<#macro greet> <h1>hello 课表明</h1> </#macro>
使用 greet 指令
<@greet></@greet> 或者 <@greet/>
指令还能够附带参数:
<#macro greet person> <h1>hello ${person}</h1> </#macro>
使用时传入 person 变量:
<@greet person="Java课表明"/> and <@greet person="zhengxl5566"/>
所谓内置函数,就是 FreeMarker 针对不一样数 据类型为咱们提供的一些内置方法,有了这些方法,可让数据在模板中的展现更加方便。使用内置函数时,只须要在变量后面使用?
加相应函数名便可。篇幅有限,这里不打算罗列全部内置函数,只挑几个简单例子展现其语法。
<#--建立字符串--> <#assign myStr = "Java 课表明"> <#--首字母小写--> ${myStr?uncap_first} <#--保留指定字符后面的字符串--> ${myStr?keep_after("Java")} <#--替换指定字符--> ${myStr?replace("Java","Freemarker")}
<#--获取当前时间(若是是后端将时间传入data-model,只须要传Date类型便可)--> <#assign currentDateTime = .now> <#--展现日期部分--> ${currentDateTime?date}<br> <#--展现时间部分--> ${currentDateTime?time}<br> <#--展现日期和时间部分--> ${currentDateTime?datetime}<br> <#--按指定格式展现时间日期--> ${currentDateTime?string("yyyy-MM-dd HH:mm a")}<br>
<#--序列类型内置函数样例--> <#assign mySequence = ["Java 课表明","张三","李四","王五"]> <#--将全部元素以指定分隔符分割,输出字符串--> ${mySequence?join(",")}<br> <#--取序列中的第一个元素--> ${mySequence?first}<br> <#--将序列排序后使用逗号分割输出为字符串--> ${mySequence?sort?join(",")}<br>
经过以上三个例子的简单演示,相信你已经能掌握内置函数的使用技巧了,就是在变量后面用 ?
加变量数据类型所支持的函数。FTL 的内置函数极其丰富,官网按数据类型详细罗列了各自支持的内置函数及其用法,可自行查看官网的内置函数参考。
为了方便你们快速查阅相关内置函数(built-ins)和指令(directives),课表明从官网翻译,并使用 xmind 作了个思惟导图,每一个函数(指令)均可以点进去查看功能描述和样例,能够极大提升模板开发效率:
原始 xmind 文件放在 课表明的github上,读者能够按需自取。
课表明划重点!这个思惟导图是全文精华,必定要下载下来看看!
所谓命名空间,就是在同一个模板里,全部使用 assign,macro,function 指令所建立的变量集合,它的主要做用就是惟一标识一个变量。
有两种方式能够建立命名空间:
一、同一个模板中的变量在同一个命名空间中。
以以下的index.ftlh
为例,这里面建立的变量都在一个命名空间下,同名变量的值会相符覆盖
<#assign myName = "Java 课表明"> <#assign myName = "课表明"> <#--实际输出的是“课表明”--> ${myName}
二、不一样模板中的变量能够经过 import 指令来区分不一样的命名空间变量
模板A中想使用模板B中的变量,可使用 import 指令,给引入的模板定义一个新的命名空间,经过 as
后面指定的 key 访问该新命名空间。
好比模板 lib/example.ftlh
中定义了copyright
:
<#macro copyright date> <p>Copyright (C) ${date} Someone. All rights reserved.</p> </#macro> <#assign mail = "user@example.com">
在另外一个模板index.ftlh
中使用copyright
:
<#import "lib/example.ftlh" as e> <@e.copyright date="1999-2002"/> ${e.mail}
命名空间由 import 指令中的 path 肯定(绝对路径),若是相同的 path 引入了屡次,只有第一次调用 import 的时候才会触发相应命名空间的建立。后面对于相同模板路径的 import,指代的都是同一个命名空间,举例:
<#import "/lib/example.ftl" as e> <#import "/lib/example.ftl" as e2> <#import "/lib/example.ftl" as e3> ${e.mail}, ${e2.mail}, ${e3.mail} <#assign mail="other@example.com" in e> ${e.mail}, ${e2.mail}, ${e3.mail}
将/lib/example.ftl
import
后赋给 e,e2,e3
三个命名空间,修改e
中的 mail
, 对e2,e3
一样生效
输出:
user@example.com, user@example.com, user@example.com other@example.com, other@example.com, other@example.com
<#import "lib/example.ftlh" as e>
会建立一个全新的命名空间,并把lib/example.ftlh
中定义的变量封装到新命名空间中,提供访问,如:<@e.copyright date="1999-2002"/>
。
<#include "lib/example.ftlh">
只是单纯把example.ftlh
的内容插入到当前模板,并不会对命名空间产生影响
Freemarker官方建议:全部使用 include 的地方都应该被 import 替代
使用 import 好处以下:
auto-import
配置为懒加载,用到哪一个加载哪一个。而auto-include
没法实现懒加载,必须全量加载;变量空值的处理
对于不存在的变量和值为 null 的变量,freemarker 统一认为是不存在的值,对其调用将会报错。为了不这种状况,能够设置默认值,示例:<h1>Welcome ${user!"visitor"}!</h1>
,当user不存在时,将显示visitor
字符串。
另外一种方式是在变量后面使用 ??
表达式,如:user??
若是user存在,则返回 true,不然返回 false,示例:<#if user??><h1>Welcome ${user}!</h1></#if>
,当user
不存在时,就不展现欢迎标语了。
list中的空值处理
遍历序列的时候,假设序列中有空值,freemarker并不会直接报错或显示空值,而是像更上一级做用域去搜索同名变量值,这种行为可能会致使错误的输出,举例:
<#assign x = 20> <#list xs as x> ${x!'Missing'} </#list>
本意是当遇到序列 xs 中的空元素是显示“missing” 字符串,但因为 freemarker 向上查找的特性, 这里的空值将会显示为 20。要关闭这个特性,能够在服务端配置:configuration.setFallbackOnNullLoopVariable(false);
前文在介绍到 include 的时候提到过,官方建议应该将全部用到 include 的地方都用 import 实现
咱们平时用到 include 指令,主要就是用来把一段内容插入到当前模板,那如何用 import 实现 include 的功能呢?
很简单,把须要插入的内容封装成 自定义指令就行了。
好比咱们从common.ftlh 里定义一个自定义指令 myFooter
<#macro myFooter> <hr> <p>这里是 footer</p> </#macro>
在须要使用的地方,引入 common.ftlh 并调用 myFooter 指令
<#import "lib/common.ftlh" as common> <#--将include使用import代替--> <#--<#include "footer.ftlh">--> <@common.myFooter/>
数据展现的时候常常遇到使用表格展现数据的状况,为了增长辨识度,通常会让奇数行和偶数行颜色不一样以区分,这就是隔行变色
下面以遍历序列为例:
<#assign mySequence = ["Java 课表明","张三","李四","王五"]> <#list mySequence as name> <span style="color: ${name?item_cycle("red","blue")}"> ${name}<br/> </span> </#list>
这里的 item_cycle 是循环变量的内置函数,至于他的详细用法,再次推荐你去看一下课表明整理的思惟导图。
本文介绍了 Freemarker 的基本概念和基础语法,意在让刚接触的萌新能对 Freemarker 有个全局性认识,了解Freemarker的数据模型、内置函数、指令。只要能区分这几个概念,实际开发中现用现查便可,不要一开始就迷失在海量的API中。
整体来讲,Freemarker 是一个比较简单,容易上手的模板引擎,只要掌握了本文所说起的基本概念,直接上手开发是彻底没问题的。
在本文写做过程当中,课表明意识到纯文字表达的局限性,又不能全文罗列和翻译API,因而整理了一个思惟导图,将Freemarker 的各种指令,内置函数及其官网示例全都翻译整理了进去。在平时开发过程当中极大提高了开发效率,须要的同窗请到 课表明的 github自取。
👇关注 Java课表明,获取最新 Java 干货👇