本文是 Freemarker 系列的第一篇,面向模板开发人员,次要介绍 FreeMarker 所应用的 FTL(FreeMarker Template Language) 语法,理解 Freemarker 的基本概念,介绍根本的 FTL 术语 及内置函数,内置指令,不便作为开发手册速查(文中演示所用版本为 2.3.30,理论应用中请依据本人我的项目版本自查官网)。
本文不会列举官网 API,只在必要时演示其语法,代码工程中有课代表整顿的 freemarker api 思维导图,配合此文食用可使功力大增!请到 课代表的 github 自取。
1.FreeMarker 是什么
Freemarker
是一款纯 Java
编写的模板引擎软件,能够用来生成各种文本,包含但不限于:HTML
,E-Mail
以及各种源代码等等。
它的次要工作就是:把模板和数据组装在一起,生成文档,这个过程又叫渲染(Render)。流程如图:
因为大部分模板开发人员都是用它来生成 HTML 页面,所以本文将基于 SpringBoot(2.4.1)+Freemarker(2.3.30)+SpringWeb
演示 HTML 页面的渲染
2. 最简略的模板
假如我想要一个简略页面用来欢送以后用户,模板代码:
<html>
<head>
<title>index</title>
</head>
<body>
<p> 你好,${userName}</p>
</body>
</html>
${userName}
是 FTL 的 插值 语法,他会把 userName
的值替换到生成的 HTML
中,从而依据以后登录者,显示不同的用户名,这个值由后端代码放到 Model 中,对应的 Controlelr 代码:
@Controller
public class HelloWorld {@GetMapping("hello")
public String hello(Model model) {model.addAttribute("userName","Java 课代表");
// 返回模板名称
return "index";
}
}
拜访页面:
数据由后端代码通过数据模型(Model)传递,模板只关怀数据如何展现(View),二者的关联关系由 Controller 来管制,这就是 MVC。
3. 数据模型(data-model)
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 中的罕用语法。
4.FTL 语法
FreeMarker 只认如下三种语法:
- 插值:${…},Freemarker 会将外面的变量替换为理论值
- FTL 标签 (tags):构造上相似 HTML 的标签,都是用
<>
包裹起来,一般标签以<#
结尾,用户自定义标签以<@
结尾,如<#if true>true thing<#/if>
,<@myDirect></@myDirect>
你会看到两种叫法,1:标签(tags),2:指令(directive)。举个例子:
<#if></#if>
叫标签;标签外面的if
是指令,能够类比于 html 中的标签(如:<table></table>
)和元素(如:table
)。不过,把标签和指令认为是 同义词 也没有问题。
- 正文(Comments):FTL 中的正文是这样的:
<#-- 被正文掉的内容 -->
,对于正文,FTL 会主动跳过,所以不会显示在生成的文本中(这点有别于 HTML 的正文)
留神:除以上三种语法之外的所有内容,皆被 FreeMarker 视为一般文本,一般文本会被原样输入
插值就是单纯的替换变量的值,正文更没啥好说的,上面次要介绍几个 最罕用 的 FTL 标签(指令)并联合代码演示其用法。
if 指令
if 能够依据条件跳过模板中的某块代码,以前文为例,当 userName
值为 “Java 课代表 ” 或 zhengxl5566
时,用非凡款式展现,相干模板代码如下:
<p> 你好,<#if userName == "Java 课代表">
<strong>${userName}</strong>
<#elseif userName == "zhengxl5566">
<h1>${userName}</h1>
<#else>
${userName}
</#if>
</p>
list 指令
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 指令
include 指令能够把一个模板的内容插入到另一个模板中(官网倡议应用 import 代替,参见下文的最佳实际)。
假如咱们每个页面都须要一个 footer,能够写一个公共的 footer.ftlh 模板,其余须要 footer 的页面只须要援用 footer.ftlh 模板即可:
<#include "footer.ftlh">
import 指令
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 能够用来创立新的变量并为其赋值,语法如下:
<#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 指令
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"/>
5. 内置函数(Built-ins)
所谓内置函数,就是 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 上,读者能够按需自取。
课代表划重点!这个思维导图是全文精髓,肯定要下载下来看看!
6. 命名空间(Namespaces)
所谓命名空间,就是在同一个模板里,所有应用 assign,macro,function 指令所创立的变量汇合,它的次要作用就是惟一标识一个变量。
有两种形式能够创立命名空间:
1、同一个模板中的变量在同一个命名空间中。
以如下的 index.ftlh
为例,这外面创立的变量都在一个命名空间下,同名变量的值会相符笼罩
<#assign myName = "Java 课代表">
<#assign myName = "课代表">
<#-- 理论输入的是“课代表”-->
${myName}
2、不同模板中的变量能够通过 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}
命名空间的生命周期(The life-cycle of namespaces)
命名空间由 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 和 include 的区别
<#import "lib/example.ftlh" as e>
会创立一个全新的命名空间,并把 lib/example.ftlh
中定义的变量封装到新命名空间中,提供拜访,如:<@e.copyright date="1999-2002"/>
。
<#include "lib/example.ftlh">
只是单纯把 example.ftlh
的内容插入到以后模板,并不会对命名空间产生影响
Freemarker 官网倡议:
所有应用 include 的中央都应该被 import 代替
应用 import 益处如下:
- import 引入的模板只会被执行一次,反复援用屡次并不会反复执行模板。而对于 include 而言,每次 include 都会执行一次模板内容;
- import 能够创立该模板的命名空间,援用变量时能够清晰表白出变量起源,缩小命名抵触概率;
- 如果定义了很多通用办法,能够将
auto-import
配置为懒加载,用到哪个加载哪个。而auto-include
无奈实现懒加载,必须全量加载; - import 指令不会有任何输入,而 include 可能会依据模板内容输入相应字符。
7. 最佳实际
1. 空值解决
变量空值的解决
对于不存在的变量和值为 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);
2. 应用 import 代替 include
前文在介绍到 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/>
3. 隔行变色
数据展现的时候常常遇到应用表格展现数据的状况,为了减少辨识度,个别会让奇数行和偶数行色彩不同以辨别,这就是隔行变色
上面以遍历序列为例:
<#assign mySequence = ["Java 课代表","张三","李四","王五"]>
<#list mySequence as name>
<span style="color: ${name?item_cycle("red","blue")}"> ${name}<br/> </span>
</#list>
这里的 item_cycle 是循环变量的内置函数,至于他的具体用法,再次举荐你去看一下课代表整顿的思维导图。
8. 总结
本文介绍了 Freemarker 的基本概念和根底语法,意在让刚接触的萌新能对 Freemarker 有个全局性意识,理解 Freemarker 的数据模型、内置函数、指令。只有能辨别这几个概念,理论开发中现用现查即可,不要一开始就迷失在海量的 API 中。
总体来说,Freemarker 是一个比较简单,容易上手的模板引擎,只有把握了本文所提及的基本概念,间接上手开发是齐全没问题的。
在本文写作过程中,课代表意识到纯文字表白的局限性,又不能全文列举和翻译 API,于是整顿了一个思维导图,将 Freemarker 的各类指令,内置函数及其官网示例全都翻译整顿了进去。在平时开发过程中极大晋升了开发效率,须要的同学请到 课代表的 github 自取。
???? 关注 Java 课代表,获取最新 Java 干货????