<#autoesc>
...
</#autoesc>
<#noautoesc>
...
</#noautoesc>
可以在ftl头中为整个模板禁用auto-escaping。然后用autoesc
指令在某个部分重新启用它。noautoesc
指令可以嵌套在autoesc
指令中使用,以重新禁用auto-escaping功能。?no_esc
也可以在autoesc
指令中使用,不对单个插值进行转义。在auto-escaping(自动转义)被禁用的情况下,只转义单个插值时可以使用${expression?esc}
代替autoesc
指令。
模板fm-directive-autoesc-noautoesc.ftl
如下:
<#ftl output\_format="HTML" auto\_esc=false>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>autoesc、noautoesc</title>
</head>
<body>
<div>
<#autoesc>
<p>启用auto-escaping:${hello}</p>
<#-- noautoesc指令可以嵌套在autoesc指令中使用,以重新禁用auto-escaping功能 -->
<#noautoesc>
<p>重新禁用auto-escaping:${hello}</p>
</#noautoesc>
<p>?no_esc也可以在autoesc指令中使用,不对单个插值进行转义:${hello?no_esc}</p>
</#autoesc>
<#-- 在auto-escaping(自动转义)被禁用的情况下,只转义单个插值时可以使用${expression?esc}代替autoesc指令 -->
<p>只转义单个插值的情况下代替autoesc指令的语法:${hello?esc}</p>
<p>由于禁用了auto-escaping,不使用autoesc指令或?esc则不会转义:${hello}</p>
</div>
</body>
</html>
注意:
autoesc
不能用于当前输出格式为非标记输出格式的情况;而noautoesc
与autoesc
不同,不管当前输出格式是什么,都可以被使用。autoesc
可以用在已经启用了auto-escaping的地方,甚至在另一个autoesc
内。虽然这是多余的,但也是允许的;noautoesc
可以用在已经禁用了auto-escaping的地方,甚至在另一个noautoesc
内,虽然这是多余的,但也是允许的。
FreeMarkerController
中新增以下方法:
@GetMapping("/directive/autoesc-noautoesc")
public String autoescDirective(Model model) {
model.addAttribute("hello", "<h1>Hello FreeMarker</h1>");
return "directive-autoesc-noautoesc";
}
效果:
修改模板:
<#ftl output\_format="HTML">
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>autoesc、noautoesc</title>
</head>
<body>
<div>
<#noautoesc>
<p>禁用auto-escaping:${hello}</p>
<#-- autoesc指令可以嵌套在noautoesc指令中使用,以重新启用auto-escaping功能 -->
<#autoesc>
<p>重新启用auto-escaping:${hello}</p>
</#autoesc>
<p>?esc也可以在noautoesc指令中使用,转义单个插值:${hello?esc}</p>
</#noautoesc>
<#-- 如果只是不想对单个插值进行转义,可以使用${expression?no\_esc}代替noautoesc指令 -->
<p>只是不想对单个插值进行转义情况下代替noautoesc指令的语法:${hello?no_esc}</p>
<p>默认启用了auto-escaping,不使用noautoesc指令或?no_esc则会转义:${hello}</p>
</div>
</body>
</html>
在任何情况下,一个模板的输出格式都可以在ftl头中强制执行。如果在添加上述ftl头之后,转义没有发生,可以将
ftl
标签修改为<#ftl output_format="XML" auto_esc=true>
。
效果:
(6)ftl
<#-- param1、param2等:参数的名称,不是表达式 -->
<#-- value1、value2等:参数的值,必须是一个常量表达式,不能用变量 -->
<#ftl param1=value1 param2=value2 ... paramN=valueN>
告诉FreeMarker和其他工具关于模板的信息,而且帮助程序自动检测一个文本文件是否是FTL文件。该指令如果存在,则必须是模板的第一句。该指令前的任何空白符将被忽略。一些设置(如编码方式,空白剥离等)在这里给定的话就有最高的优先级,也就是说,它们直接作用于模板而不管其他任何FreeMarker的配置设置。
这里总结一下ftl
中的常用参数:
attributes
:关联模板任意属性(名-值对)的哈希表。属性值可以是任意类型。FreeMarker不会去理解属性的含义,它是由封装FreeMarker的应用决定的。auto_esc
:开启或关闭auto-escaping。这取决于FreeMarker配置中的auto_escaping_policy
,但是如果当前的输出格式默认使用auto-escaping,通常auto-escaping会默认为打开。所以主要设置为false,用它来禁用auto-escaping。当当前输出格式为非标记输出格式时,设置为true将导致解析时错误。注意,可以用autoesc
和noautoesc
指令只为模板的一部分打开或关闭auto-escaping。encoding
:在模板文件本身中为模板指定编码方式(字符集)。这是新创建的Template
的encoding
设置, 而且Configuration.getTemplate
中的encoding
参数不能覆盖它。output_format
:指定模板的输出格式。这必须是一个字符串字面量,它指的是输出格式的名称。预定义的输出格式有HTML
、XHTML
、XML
、RTF
等。strict_syntax
:开启或关闭"strict syntax",这是FreeMarker 2.1之后的标准语法。合法值为布尔常量true
和false
(向下兼容字符串"yes"
、"no"
、"true"
、"false"
)。默认值(不使用该参数时)取决于FreeMarker的配置,对新项目应该为true
。strip_text
:开启它,则模板被解析时模板中所有顶级文本被移除。这不会影响macro、指令或插值中的文本。合法值为布尔常量true
和false
(向下兼容字符串"yes"
、"no"
、"true"
、"false"
)。默认值(不使用该参数时)为false
。strip_whitespace
:开启或关闭空白剥离(white-space stripping)。合法值为布尔常量true
和false
(向下兼容字符串"yes"
、"no"
、"true"
、"false"
)。默认值(不使用该参数时)取决于FreeMarker的配置,对新项目应该为true
。
(7)function、return
语法:
<#-- name:方法变量的名称(不是表达式) -->
<#-- param1、param2等:存储参数的值(不是表达式)的局部变量名称,=后面的默认值(是表达式)可选 -->
<#-- paramN:最后一个参数,可以可选的包含一个尾部省略号(...),表示接受可变数量的参数,paramN将是额外参数的序列 -->
<#-- 没有默认值的参数必须在有默认值的参数之前 -->
<#-- return指令可以在function开始标签和function结束标签之间的任意位置使用并且可以使用任意次数 -->
<#function name param1 param2 ... paramN>
...
<#-- returnValue:计算方法调用值的表达式 -->
<#return returnValue>
...
</#function>
再当前命名空间创建一个方法变量,return
指令必须有一个参数来指定方法的返回值,而且视图写入输出的将会被忽略。如果到达 </#function>
(也就是说没有return returnValue
),那么方法的返回值就是未定义变量。
模板fm-directive-function-return.ftl
如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>function、return</title>
</head>
<body>
<#-- 计算两个数的平均值的方法 -->
<#-- func为方法变量的名称,x、y为存储参数的值(不是表达式)的局部变量名称,=后面的默认值(是表达式)可选 -->
<#-- 没有默认值的参数必须在有默认值的参数之前 -->
<#function func x y = 20>
<#return (x + y) / 2>
</#function>
<#-- 计算多个数的平均值的方法 -->
<#-- 最后一个参数可以可选的包含一个尾部省略号(...),表示接受可变数量的参数,nums是额外参数的序列 -->
<#function avg nums...>
<#-- 和assign指令类似,但是它创建或替换局部变量,它只能在macro和function内部定义才起作用 -->
<#local sum = 0>
<#list nums as num>
<#local sum += num>
</#list>
<#if nums?size != 0>
<#return sum / nums?size>
</#if>
</#function>
<#assign reslut = 1>
<#-- 计算一个自然数的阶乘的方法 -->
<#function factorial num>
<#if num lt 0>
<#return "num不能小于0">
</#if>
<#if num gt 1>
<#assign reslut \*= num>
<#-- 递归调用方法计算阶乘 -->
${factorial(num - 1)}
</#if>
<#return reslut>
</#function>
<div>
<p>计算10和20的平均值:${func(10)}</p>
<p>计算20和30的平均值:${func(20, 30)}</p>
<p>计算10、20、30的平均值:${avg(10, 20, 30)}</p>
<p>计算10、20、30、40的平均值:${avg(10, 20, 30, 40)}</p>
<#-- 如果到达</#function>(即没有return返回值),那么方法的返回值就是未定义变量 -->
<p>计算平均值,没有传参数:${avg()!"N/A"}</p>
<p>${num}的阶乘为:${factorial(num)}</p>
</div>
</body>
</html>
FreeMarkerController
中新增以下方法:
@GetMapping("/directive/function-return")
public String functionAndReturnDirective(Model model) {
model.addAttribute("num", 5);
return "directive-function-return";
}
效果:
num
变量分别设置为0和-1时:
(8)global
语法:
<#-- name:变量的名称,它不是表达式,可以写成一个字符串字面量 -->
<#-- =:赋值运算符,也可以是以下简写的运算符之一,++、--、+=、-=、*=、/=、%= -->
<#-- value:存储的值,是表达式 -->
<#global name=value>
或
<#global name1=value1 name2=value2 ... nameN=valueN>
或
<#global name>
capture this
</#global>
该指令和assign
相似,但是被创建的变量在所有命名空间中都可见,但又不在任何命名空间中。准确地说就如同创建(或替换)一个数据模型变量。因此,这个变量是全局的。使用该指令创建的变量会隐藏数据模型中的同名变量。在当前命名空间中一个相同名称的变量存在会隐藏global
指令创建的变量。
模板fm-directive-global.ftl
如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>global</title>
</head>
<body>
<div>
<p>数据模型中的username: ${username}</p>
<#-- assign指令创建的变量会隐藏数据模型中的同名变量 -->
<#assign username = "GuanYu">
<p>assign指令创建的变量username隐藏了数据模型中的username: ${username}</p>
<#-- 使用特殊变量globals可以访问数据模型中的变量 -->
<p>可以使用特殊变量globals访问数据模型中的变量: ${.globals.username}</p>
</div>
</body>
</html>
FreeMarkerController
中新增以下方法:
@GetMapping("/directive/global")
public String globalDirective(Model model) {
model.addAttribute("username", "ZhaoYun");
return "directive-global";
}
效果:
修改模板:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>global</title>
</head>
<body>
<div>
<p>数据模型中的username: ${username}</p>
<#-- global指令创建的变量(全局变量)会隐藏数据模型中的同名变量 -->
<#global username = "MaChao">
<p>global指令创建的变量username会隐藏数据模型中的username: ${username}</p>
<p>此时使用特殊变量globals访问的是全局变量username: ${.globals.username}</p>
<#-- 仍然可以使用特殊变量data\_model访问数据模型中的变量 -->
<p>可以使用特殊变量data_model访问数据模型中的变量username: ${.data_model.username}</p>
<#-- 在当前命名空间,相同名称的变量存在会隐藏global指令创建的变量 -->
<#assign username = "ZhangFei">
<p>assign指令在当前命名空间创建的变量username隐藏了global指令创建的变量username: ${username}</p>
<#-- 使用特殊变量globals不但可以访问数据模型中的变量,还可以访问global指令创建的变量 -->
<p>可以使用特殊变量globals访问全局变量username: ${.globals.username}</p>
</div>
</body>
</html>
效果:
(9)if、else、elseif
语法:
<#-- ondition、condition2等:将被计算成布尔值的表达式 -->
<#-- elseif和else是可选的 -->
<#if condition>
...
<#elseif condition2>
...
<#elseif condition3>
...
...
<#else>
...
</#if>
可以使用if
、elseif
和else
指令通过条件判断是否跳过模板的某个部分。条件必须计算成布尔值,否则会出错终止模板处理。elseif
和else
必须出现在if
内部(即在if
开始标签和结束标签之间)。if
中可以包含任意数量的elseif
(包括0个)并且结束时的else
可选。
模板fm-directive-if.ftl
如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>if、else、elseif</title>
</head>
<body>
<h1>只有if,没有elseif和else</h1>
<div>
<p>
成绩是否是正常数据:
<#-- 注意使用比较运算符>、>=时,FreeMarker解释>时可以把它当做ftl标签的结束符
为了避免该问题,可以用小括号括起来或用lt代替<、lte代替<=、gt代替>、gte代替>= -->
<#if (grade >= 0 && grade <=100)>
<span>成绩数据正常</span>
</#if>
</p>
</div>
<h1>只有if和else,没有elseif</h1>
<div>
<p>
成绩是否及格:
<#if grade gte 60>
<span>及格</span>
<#else>
<span>不及格</span>
</#if>
</p>
</div>
<h1>只有if和elseif,没有else</h1>
<div>
<p>
成绩对应的级别:
<#if (grade < 0 || grade > 100)>
<span>无效数据</span>
<#elseif (grade < 60)>
<span>不及格</span>
<#elseif (grade < 70)>
<span>差</span>
<#elseif (grade < 90)>
<span>良</span>
<#elseif (grade <= 100)>
<span>优</span>
</#if>
</p>
</div>
<h1>if、else、elseif都有</h1>
<div>
<p>
成绩对应的级别:
<#if grade lt 0 || grade gt 100>
<span>无效数据</span>
<#elseif grade gte 90>
<span>优</span>
<#elseif grade gte 70>
<span>良</span>
<#elseif grade gte 60>
<span>差</span>
<#else>
<span>不及格</span>
</#if>
</p>
</div>
<h1>嵌套if指令</h1>
<div>
<p>
成绩对应的级别:
<#if grade gte 0 && grade lte 100>
<#if grade gte 60>
<#if grade gte 70>
<#if grade gte 90>
<span>优</span>
<#else>
<span>良</span>
</#if>
<#else>
<span>差</span>
</#if>
<#else>
<span>不及格</span>
</#if>
<#else>
<span>无效数据</span>
</#if>
</p>
</div>
</body>
</html>
FreeMarkerController
中新增以下方法:
@GetMapping("/directive/if")
public String ifDirective(Model model) {
model.addAttribute("grade", 85);
return "directive-if";
}
效果:
变量grade
设置为-1时:
(10)import
语法:
<#-- path:模板的路径,是一个计算为字符串的表达式 -->
<#-- hash:可以通过它来访问命名空间的hash变量的不带引号的名称。不是一个表达式 -->
<#import path as hash>
import
首先创建一个新的空命名空间,然后在该命名空间内执行带有path参数的模板,因此模板用变量(macro、function等)填充该命名空间。然后,命名空间被分配给用hash参数指定的变量,可以通过它访问其内容。命名空间是一个hash值,因此点运算符起了作用。赋值就像assign
指令一样,也就是说,它将变量设置在当前的名字空间中。除了,如果导入发生在主(最顶层)模板的命名空间中,hash变量也会在全局命名空间中创建。
模板fm-directive-import.ftl
如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>import</title>
</head>
<body>
<div>
<#-- 导入一个库并创建新的命名空间,可用在导入的模板定义使导入模板可用的宏(macro)、函数和其他变量的集合
import指令创建了一个hash变量my来访问它所创建的命名空间
该变量位于导入模板所用的命名空间中,并作为进入导入库的命名空间的窗口 -->
<#import "/libs/common.ftl" as com>
<#-- 在当前命名空间(和标签所在模板关联的命名空间)创建了变量mail -->
<#assign mail="rtx@xxx.com">
<#-- 调用宏 -->
<@com.copyright date = "2020-2021"/>
<hr/>
<#-- 使用hash变量来访问新创建的命名空间 -->
<p>/libs/common.ftl的命名空间的变量mail: ${com.mail}</p>
<#-- 创建或替换指定命名空间的变量,这里替换了用于/libs/common.ftl的命名空间的变量mail -->
<#assign mail = "admin@xxx.com" in com>
<p>/libs/common.ftl的命名空间的变量mail: ${com.mail}</p>
<p>在当前命名空间的变量mail: ${mail}</p>
</div>
<div>
<#-- 用同一path多次import,只在第一次调用import时创建命名空间并运行模板
后面的调用将只是返回模板第一次被导入时创建和初始化的命名空间
然后将相同的命名空间分配给指定的hash变量,通过该变量来访问命名空间 -->
<#import "/libs/common.ftl" as co>
<#import "/libs/common.ftl" as cm>
<#import "/libs/common.ftl" as om>
<#-- 三个hash变量访问的都是同一个命名空间 -->
<p>${com.mail}, ${co.mail}, ${cm.mail}, ${om.mail}</p>
<#assign mail = "root@xxx.com" in co>
<p>${com.mail}, ${co.mail}, ${cm.mail}, ${om.mail}</p>
</div>
</body>
</html>
如果用同一path多次调用
import
,它将创建命名空间,并只在第一次调用import
时运行模板。后面的调用将只是返回模板第一次被导入时创建和初始化的命名空间,而不会执行导入的模板。路径参数可以是相对路径,如
"foo.ftl"
和".../foo.ftl"
,或绝对路径,如"/foo.ftl"
。相对路径是相对于使用import
指令的模板目录而言的。绝对路径是相对于在配置FreeMarker时定义的基础路径(通常称为"模板的根目录")。注意,命名空间是不分层次的,它们之间相互独立;当
import
创建另一个命名空间时,处于哪个命名空间中并不重要。例如,当在命名空间N1中import
命名空间N2时,N2不会在N1中。N1只是得到与在主命名空间中导入N2时一样的N2。
/libs/common.ftl
:
<#macro copyright date>
<#-- 数据模型中的变量在任何位置都是可见的,所以这里依然能访问到user -->
<p>© ${date} ${user.username}. All rights reserved.</p>
<p>email: ${mail}</p>
</#macro>
<#assign mail = "${user.username}@xxx.com">
FreeMarkerController
中新增以下方法:
@GetMapping("/directive/import")
public String importDirective(Model model) {
model.addAttribute("user", new User(1L, "RtxTitanV", "654321"));
return "directive-import";
}
效果:
(11)include
语法:
<#-- path:要包含的文件的路径,一个计算为字符串的表达式 -->
<#include path>
或
<#-- options:选项 -->
<#include path options>
在模板中插入另一个FreeMarker模板文件(由path参数指定),被包含的文件和包含它的模板共享变量。被包含模板的输出格式是在include
标签出现的位置插入的。include
指令还支持以下三个选项:
encoding
:包含的模板的编码方式(字符集)。parse
:如果为true,则包含的文件将被解析为FTL,否则整个文件将被视为简单的文本。缺省默认为true。ignore_missing
:当为true时,模板引用为空时压制错误,而<#include ...>
不会输出任何东西。否则,如果模板不存在,模板处理就会发生错误并停止。缺省默认为false。
模板fm-directive-include.ftl
如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>include</title>
</head>
<body>
<div>
<#assign date = "2020-2021">
<#assign mail = "rtx@xxx.com">
<#-- 在模板中插入另一个FreeMarker模板文件,被包含的文件和包含它的模板共享变量 -->
<#include "/libs/copyright.ftl">
<hr/>
<#include "/libs/common.ftl">
<#-- 如果宏(macro)定义是用include指令插入的,那么在FreeMarker执行include指令之前,它们将无法使用
所以调用include指令插入的宏需在include之后进行 -->
<@copyright date = "2021-2022"/>
</div>
</body>
</html>
/libs/copyright.ftl
:
<p>© ${date} ${user.username}. All rights reserved.</p>
<p>email: ${mail}</p>
FreeMarkerController
中新增以下方法:
@GetMapping("/directive/include")
public String includeDirective(Model model) {
model.addAttribute("user", new User(1L, "RtxTitanV", "654321"));
return "directive-include";
}
效果:
(12)list、else、items、sep、break、continue
语法:
序列的通用形式1:
<#-- sequence:计算为一个序列或要迭代的项的集合的表达式 -->
<#-- item:循环变量的名称(不是表达式) -->
<#list sequence as item>
Part repeated for each item
<#-- else部分是可选的 -->
<#else>
Part executed when there are 0 items
</#list>
序列的通用形式2:
<#list sequence>
Part executed once if we have more than 0 items
<#items as item>
Part repeated for each item
</#items>
Part executed once if we have more than 0 items
<#else>
Part executed when there are 0 items
</#list>
哈希表的通用形式1:
<#list hash as key, value>
Part repeated for each key-value pair
<#else>
Part executed when there are 0 key-value pair
</#list>
哈希表的通用形式2:
<#list hash>
Part executed once if we have more than 0 key-value pair
<#items as key, value>
Part repeated for each key-value pair
</#items>
Part executed once if we have more than 0 key-value pair
<#else>
Part executed when there are 0 key-value pair
</#list>
list
指令对其第一个参数指定的序列(或集合)中的每个值执行list
开始标签和list
结束标签之间的代码。对于每次迭代,循环变量将存储当前项的值。循环变量(list
中指定的)只存在于list
标签体内。而且,从循环内调用的宏(macro)或函数不会看到它。注意,循环变量不存在于else
标签和list
结束标签之间,因为这部分不是循环的一部分。
模板fm-directive-list.ftl
如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>list、else、items、sep、break、continue</title>
</head>
<body>
<div>
<table>
<thead>
<tr>
<th>id</th>
<th>username</th>
<th>password</th>
<th>index</th>
<th>counter</th>
<th>is_even_item</th>
<th>is_odd_item</th>
<th>item_parity</th>
<th>item_parity_cap</th>
<th>item_cycle</th>
<th>is_first</th>
<th>is_last</th>
<th>has_next</th>
</tr>
</thead>
<tbody>
<#-- users为要迭代的序列或集合,user为循环变量 -->
<#list users as user>
<tr>
<td>${user.id}</td>
<td>${user.username}</td>
<td>${user.password}</td>
<#-- 通过循环变量内建函数访问当前迭代状态
注意,这些内建函数只能用于list和items指令的循环变量(已废弃的foreach指令也可以)
list指令没有指定循环变量时,这些内建函数就作用于items指令的循环变量
循环变量内建函数只使用循环变量的名称,这样它们可以识别相关的正在进行的迭代
它们并不读取循环变量的值 -->
<#-- 返回当前迭代基于0的索引 -->
<td>${user?index}</td>
<#-- 返回当前迭代基于1的索引 -->
<td>${user?counter}</td>
<#-- 判断当前迭代项是否有一个基于1的偶数索引
c为布尔值内建函数,将布尔值转换为字符串 -->
<td>${user?is_even_item?c}</td>
<#-- 判断当前迭代项是否有一个基于1的奇数索引 -->
<td>${user?is_odd_item?c}</td>
<#-- 根据迭代当前所在的基于1的索引的奇偶性,返回字符串"odd"和"even" 通常用于表格中行间的颜色变换 -->
<td>${user?item_parity}</td>
<#-- 根据迭代当前所在的基于1的索引的奇偶性,返回字符串"Odd"和"Even" -->
<td>${user?item_parity_cap}</td>
<#-- 这是item\_parity更为通用的版本,可以指定值来代替"odd"和"even",允许多于两个值
参数个数至少为1,无上限,类型任意 -->
<td>${user?item_cycle("□", "△", "○")}</td>
<#-- 判断当前迭代项是否是第一项 -->
<td>${user?is_first?c}</td>
<#-- 判断当前迭代项是否是最后一项 -->
<td>${user?is_last?c}</td>
<#-- 判断当前迭代项是否不是最后一项 -->
<td>${user?has_next?c}</td>
</tr>
<#-- else部分是可选的 -->
<#else>
<p>No users</p>
</#list>
</tbody>
</table>
</div>
</body>
</html>
FreeMarkerController
中新增以下方法:
@GetMapping("/directive/list")
public String listDirective(Model model) {
List<User> users = new ArrayList<>();
users.add(new User(1L, "刘备", "123132"));
users.add(new User(2L, "关羽", "321231"));
users.add(new User(3L, "张飞", "213312"));
users.add(new User(4L, "赵云", "132213"));
users.add(new User(5L, "马超", "312123"));
model.addAttribute("users", users);
return "directive-list";
}
效果:
else
指令用于没有迭代项时,需要输出一些特殊内容而不是什么也不输出的情况。将users
设置为空之后会输出else
中的内容:
如果必须在第一个列表项之前或最后一个列表项之后打印一些内容,只要有至少一项,就可以使用items
指令。当list
指令没有指定循环变量时,如果至少有一项,它的主体就会被执行一次,否则就完全不执行。内嵌的items
指令会执行每一项,因此items
指令来定义循环变量而不是list
指令。
在模板中新增以下代码:
<div>
<#-- 如果必须在第一个列表项之前或最后一个列表项之后打印一些内容,只要有至少一项,就可以使用items指令
当list指令没有指定循环变量时,如果至少有一项,它的主体就会被执行一次,否则就完全不执行
内嵌的items指令会执行每一项,因此items指令来定义循环变量而不是list指令 -->
<#list users>
<p>如果至少有一项,此处会被执行一次,否则就完全不执行</p>
<table>
<thead>
<tr>
<th>id</th>
<th>username</th>
<th>password</th>
<th>index</th>
<th>counter</th>
<th>is_even_item</th>
<th>is_odd_item</th>
<th>item_parity</th>
<th>item_parity_cap</th>
<th>item_cycle</th>
<th>is_first</th>
<th>is_last</th>
<th>has_next</th>
</tr>
</thead>
<tbody>
<#-- 不能将items指令移出到宏或包含的模板中,items指令不能嵌入else指令
list可以有多个items指令,但只有其中一个被允许执行,所以多个items可以用于不同的if-else分支,但不能迭代两次
items指令不能有自己的嵌套else指令,只有包含它的list指令可以有
循环变量user仅存在于items指令体内部 -->
<#items as user>
<tr>
<td>${user.id}</td>
<td>${user.username}</td>
<td>${user.password}</td>
<#-- 通过循环变量内建函数访问当前迭代状态
注意,这些内建函数只能用于list和items指令的循环变量(已废弃的foreach指令也可以)
list指令没有指定循环变量时,这些内建函数就作用于items指令的循环变量
循环变量内建函数只使用循环变量的名称,这样它们可以识别相关的正在进行的迭代
它们并不读取循环变量的值 -->
<#-- 返回当前迭代基于0的索引 -->
<td>${user?index}</td>
<#-- 返回当前迭代基于1的索引 -->
<td>${user?counter}</td>
<#-- 判断当前迭代项是否有一个基于1的偶数索引
c为布尔值内建函数,将布尔值转换为字符串 -->
<td>${user?is_even_item?c}</td>
<#-- 判断当前迭代项是否有一个基于1的奇数索引 -->
<td>${user?is_odd_item?c}</td>
<#-- 根据迭代当前所在的基于1的索引的奇偶性,返回字符串"odd"和"even",通常用于表格中行间的颜色变换 -->
<td>${user?item_parity}</td>
<#-- 根据迭代当前所在的基于1的索引的奇偶性,返回字符串"Odd"和"Even" -->
<td>${user?item_parity_cap}</td>
<#-- 这是item\_parity更为通用的版本,可以指定值来代替"odd"和"even",允许多于两个值
参数个数至少为1,无上限,类型任意 -->
<td>${user?item_cycle("□", "△", "○")}</td>
<#-- 判断当前迭代项是否是第一项 -->
<td>${user?is_first?c}</td>
<#-- 判断当前迭代项是否是最后一项 -->
<td>${user?is_last?c}</td>
<#-- 判断循当前迭代项是否不是最后一项 -->
<td>${user?has_next?c}</td>
</tr>
</#items>
</tbody>
</table>
<p>如果至少有一项,此处会被执行一次,否则就完全不执行</p>
<#-- else部分是可选的 -->
<#else>
<p>No users</p>
</#list>
</div>
效果:
将users
设置为空之后会输出else
中的内容:
通过list
指令还可以迭代哈希表。在模板中新增以下代码:
<div>
<table>
<thead>
<tr>
<th>id</th>
<th>username</th>
<th>password</th>
<th>key</th>
<th>index</th>
<th>counter</th>
<th>is_even_item</th>
<th>is_odd_item</th>
<th>item_parity</th>
<th>item_parity_cap</th>
<th>item_cycle</th>
<th>is_first</th>
<th>is_last</th>
<th>has_next</th>
</tr>
</thead>
<tbody>
<#-- userMap为要迭代的Map,key为键,user为值 -->
<#list userMap as key, user>
<tr>
<td>${user.id}</td>
<td>${user.username}</td>
<td>${user.password}</td>
<td>${key}</td>
<#-- 通过循环变量内建函数访问当前迭代状态
注意,这些内建函数只能用于list和items指令的循环变量(已废弃的foreach指令也可以)
list指令没有指定循环变量时,这些内建函数就作用于items指令的循环变量
循环变量内建函数只使用循环变量的名称,这样它们可以识别相关的正在进行的迭代
它们并不读取循环变量的值 -->
<#-- 返回当前迭代基于0的索引 -->
<td>${user?index}</td>
<#-- 返回当前迭代基于1的索引 -->
<td>${user?counter}</td>
<#-- 判断当前迭代项是否有一个基于1的偶数索引
c为布尔值内建函数,将布尔值转换为字符串 -->
<td>${user?is_even_item?c}</td>
<#-- 判断当前迭代项是否有一个基于1的奇数索引 -->
<td>${user?is_odd_item?c}</td>
<#-- 根据迭代当前所在的基于1的索引的奇偶性,返回字符串"odd"和"even" 通常用于表格中行间的颜色变换 -->
<td>${user?item_parity}</td>
<#-- 根据迭代当前所在的基于1的索引的奇偶性,返回字符串"Odd"和"Even" -->
<td>${user?item_parity_cap}</td>
<#-- 这是item\_parity更为通用的版本,可以指定值来代替"odd"和"even",允许多于两个值
参数个数至少为1,无上限,类型任意 -->
<td>${user?item_cycle("□", "△", "○")}</td>
<#-- 判断当前迭代项是否是第一项 -->
<td>${user?is_first?c}</td>
<#-- 判断当前迭代项是否是最后一项 -->
<td>${user?is_last?c}</td>
<#-- 判断当前迭代项是否不是最后一项 -->
<td>${user?has_next?c}</td>
</tr>
<#-- else部分是可选的 -->
<#else>
<p>No userMap</p>
</#list>
</tbody>
</table>
</div>
将userMap
设置到Model中:
Map<String, Object> map = new HashMap<>(16);
map.put("user1", new User(1L, "刘备", "123132"));
map.put("user2", new User(2L, "关羽", "321231"));
map.put("user3", new User(3L, "张飞", "213312"));
map.put("user4", new User(4L, "赵云", "132213"));
map.put("user5", new User(5L, "马超", "312123"));
model.addAttribute("userMap", map);
效果:
当必须在每一项之间显示一些东西时(但不是在第一项之前或最后一项之后),可以使用sep
指令。
在模板中新增以下代码:
<div>
<p>
username:
<#-- 当必须在每一项之间显示一些东西时(但不是在第一项之前或最后一项之后),可以使用sep指令 -->
<#list users as user>
<#-- 如果将sep指令放到包含的指令关闭的位置,则可以省略sep结束标签 -->
${user.username}<#sep>,
</#list>
</p>
<p>
password:
<#list users as user>
<span>
<#-- 由于sep指令没有放在包含的指令(这里是list指令)关闭的位置,所以这里的sep结束标签不能省略 -->
${user.password}<#sep>、</#sep>
</span>
</#list>
</p>
</div>
sep
是<#if item?has_next>...</#if>
的简便形式。因此,它可以用在任何有list
或items
循环变量的地方,可以出现多次,而且可以有任意嵌套的内容。
效果:
list
指令可以嵌套,break
指令可以在迭代的任意点退出。在模板中新增以下代码,通过list
嵌套结合break
输出99乘法表:
<div>
<table>
<#-- list指令可以嵌套 -->
<#list 1..100 as i>
<#if i gt 9>
<#-- break指令可以退出迭代 -->
<#break>
</#if>
<tr>
<#list 1..100 as j>
<#if j gt i>
<#break>
</#if>
<td>${i} * ${j} = ${i * j}</td>
</#list>
</tr>
</#list>
</table>
</div>
如果
break
在items
里面,它将只从items
退出,而不是从list
退出。一般来说,break
只会从为每个迭代项调用的指令主体中退出,并且只能放在该指令中。例如,不能在list
的else
部分使用break
,除非list
被嵌套到其他可break的指令中。
效果:
使用continue
指令可以跳过迭代体的剩余部分(直到</#list>
或</#items>
标签的部分),然后继续迭代下一项。
在模板中新增以下代码:
<div>
<table>
<#list 1..9 as i>
<tr>
<#if i % 2 == 0>
<#-- continue指令跳过当前迭代的剩余部分,继续下一次迭代 -->
<#continue>
</#if>
<#list 1..9 as j>
<#if j gt i>
<#break>
</#if>
<#if j % 3 == 0>
<#continue>
</#if>
<td>${i} * ${j} = ${i * j}</td>
</#list>
</tr>
</#list>
</table>
</div>
只要
list
指令指定了循环变量(as item
),break
和continue
指令可以放在list
指令内的任何地方,否则它可以放在items
指令内的任何地方。强烈建议把它放在迭代过程中的所有其他事情之前或之后。否则,很容易在输出中出现未封闭的元素,或者使模板更难理解。特别是要避免从自定义指令的嵌套内容中脱离出来(例如<#list ...>...<@foo>...<#break>...</@foo>...</#list>
)。
break
和continue
和else
、items
、sep
一样,只能在指令体内部使用,而不能移出到宏或被包含的模板中。
效果:
break
和continue
在大多数情况下被弃用,因为它与<#sep>
、item?has_next
、item?counter
、item?index
、item?item_parity
等不兼容。
如果需要跳过list
中的某些元素,使用if
指令不是一个好主意,因为这样<#sep>
、item?has_next
、item?counter
、item?index
、item?item_parity
等就不可用了,因为FreeMarker不知道哪些项曾经和将要被显示。相反,应该从要迭代的序列中删除不需要的项,然后再进行迭代。下面通过内建函数take_while
、filter
来代替if
和break
或continue
的组合。
在模板中新增以下代码:
<div>
<table>
<#-- 使用序列的内建函数来代替if和break的组合 -->
<#assign seq = 1..100>
<#-- 内建函数take\_while返回只包含输入序列中第一个不符合参数条件的元素之前的元素的序列 -->
<#list seq?take\_while(i -> i lte 9) as i>
<tr>
<#list seq?take\_while(j -> j lte i) as j>
<td>${i} * ${j} = ${i * j}</td>
</#list>
</tr>
</#list>
</table>
</div>
<hr/>
<div>
<table>
<#-- 使用序列的内建函数来代替if和break或continue的组合 -->
<#assign seq = 1..9>
<#-- 内建函数filter返回只包含参数条件返回真值的元素的序列 -->
<#list seq?filter(i -> i % 2 != 0) as i>
<tr>
<#list seq?take\_while(j -> j lte i)?filter(j -> j % 3 != 0) as j>
<td>${i} * ${j} = ${i * j}</td>
</#list>
</tr>
</#list>
</table>
</div>
<hr/>
<div>
<#assign lines = ["Stop listing", "when a", "certain element", "", "is found"]>
<p>
if、break和sep指令一起使用:
<#list lines as line>
<#if line == ''>
<#break>
</#if>
${line}<#sep>,
</#list>
</p>
<p>
使用内建函数take_while代替if和break的组合:
<#list lines?take\_while(line -> line != '') as line>
${line}<#sep>,
</#list>
</p>
</div>
效果:
局部变量会隐藏同名的普通变量,循环变量也会隐藏同名的局部变量和普通变量。在模板中新增以下代码:
<div>
<#-- 局部变量会隐藏同名的普通变量,循环变量也会隐藏同名的局部变量和普通变量 -->
<#assign x = "plain1">
<p>1: ${x}</p>
<@test/>
<p>6: ${x}</p>
<#list ["loop2"] as x>
<p>7: ${x}</p>
<#assign x = "plain2">
<p>8: ${x}</p>
</#list>
<p>9: ${x}</p>
<#macro test>
<p>2: ${x}</p>
<#local x = "local">
<p>3: ${x}</p>
<#list ["loop1"] as x>
<p>4: ${x}</p>
</#list>
<p>5: ${x}</p>
</#macro>
</div>
效果:
内部循环变量会隐藏外部循环变量。在模板中新增以下代码:
<div>
<#-- 内部循环变量会隐藏外部循环变量 -->
<#list ["loop1"] as x>
<p>1: ${x}</p>
<#list ["loop2"] as x>
<p>2: ${x}</p>
<#list ["loop3"] as x>
<p>3: ${x}</p>
</#list>
<p>4: ${x}</p>
</#list>
<p>5: ${x}</p>
</#list>
</div>
效果:
(13)macro、nested、return
语法:
<#-- name:宏(macro)变量的名称,它不是表达式,和顶层变量语法相同,也可以写成一个字符串字面量 -->
<#-- param1、param2等:存储参数的值(不是表达式)的局部变量名称,=后面的默认值(是表达式)可选,默认值也可以是另外一个参数
参数名称和顶层变量语法相同,所以有相同的特性和限制 -->
<#-- paramN: 最后一个参数,可能会有三个点(...),表示宏接受可变的数量的参数,不匹配其他参数的参数将被收集到最后一个参数中
当用命名参数调用宏时,paramN将是包含所有传递给宏的未声明的键值对的哈希表
当用位置参数调用宏时,paramN将是额外参数值的序列 -->
<#-- 没有默认值的参数必须在有默认值的参数之前 -->
<#-- nested和return指令是可选的,可以在macro开始标签和macro结束标签之间的任意位置使用并且可以使用任意次数 -->
<#macro name param1 param2 ... paramN>
...
<#-- loopvar1、loopvar2等:是nested指令想为嵌套内容创建的循环变量的值,这些是表达式,是可选的 -->
<#nested loopvar1, loopvar2, ..., loopvarN>
...
<#return>
...
</#macro>
macro
指令在当前命名空间中创建一个宏(macro)变量,宏变量存储模板片段,可以被用作自定义指令。该变量还存储了允许用户定义指令的参数名称。当将该变量作为指令时, 必须给所有参数赋值,除了有默认值的参数。默认值当且仅当调用宏而不给参数赋值时起作用。
模板fm-directive-macro.ftl
如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>macro、nested、return</title>
</head>
<body>
<div>
<#-- 变量会在模板开始时被创建,而不管macro指令放置在模板的什么位置,所以可以将调用宏放在宏定义之前 -->
<@copy/>
<#-- 在当前命名空间中创建一个宏(macro)变量,宏变量存储模板片段,可以被用作自定义指令 -->
<#macro copy>
<p>© 2021 RtxTitanV . All rights reserved.</p>
</#macro>
</div>
</body>
</html>
FreeMarkerController
中新增以下方法:
@GetMapping("/directive/macro")
public String macroDirective(Model model) {
model.addAttribute("num", 5);
return "directive-macro";
}
效果:
宏可以带参数,参数可以设置默认值。在模板中新增以下代码:
<div>
<#-- 宏可以带有参数,当将宏变量作为指令时,必须给所有参数赋值,除了有默认值的参数
默认值当且仅当调用宏而不给参数赋值时起作用 -->
<#macro mac x y = 20>
<p>计算${x}和${y}的平均值:${(x + y) / 2}</p>
</#macro>
<@mac 10/>
<@mac x = 20 y = 30/>
</div>
效果:
宏支持可变数量的位置参数,不管它是使用位置参数还是命名参数传递。在模板中新增以下代码:
<div>
<#-- 支持可变数量的位置参数的宏,不管它是使用位置参数还是命名参数传递 -->
<#macro avg nums...>
<#local sum = 0>
<p>
<#if nums?is\_sequence>
<#list nums>
计算
<#items as num>
<#local sum += num>
${num}<#sep>、
</#items>
的平均值:
</#list>
<#if nums?size != 0>
${sum / nums?size}
</#if>
<#else>
<#list nums>
计算
<#items as key, num>
<#local sum += num>
${key}(${num})<#sep>、
</#items>
的平均值:
</#list>
<#if nums?size != 0>
${sum / nums?size}
</#if>
</#if>
</p>
</#macro>
<@avg/>
<@avg 10 20 30/>
<@avg 10 20 30 40/>
<@avg a = 20 b = 30 c = 40/>
<@avg a = 20 b = 30 c = 40 d = 50/>
</div>
效果:
宏支持命名参数和可变数量的参数混合使用,不管它是使用位置参数还是命名参数传递。在模板中新增以下代码:
<div>
<#-- 支持命名参数和可变数量的参数混合使用的宏,不管它是使用位置参数还是命名参数传递 -->
<#macro m a b ext...>
<ul>
<li><p>a = ${a}</p></li>
<li><p>b = ${b}</p></li>
<ul>
<#if ext?is\_sequence>
<#list ext as e>
<li><p>ext[${e?index}] = ${e}</p></li>
</#list>
<#else>
<#list ext as k, v>
<li><p>${k} = ${v}</p></li>
</#list>
</#if>
</ul>
</ul>
</#macro>
<@m 1 3 5 7 9/>
<@m a = 2 b = 4 c = 6 d = 8 e = 10/>
</div>
效果:
宏也可以递归调用。在模板中新增以下代码,通过递归调用宏来计算自然数的阶乘:
<div>
<#assign reslut = 1>
<#-- 用于计算阶乘的宏 -->
<#macro factorial num>
<#if num lt 0>
<#assign reslut = "num不能小于0">
</#if>
<#if num gt 1>
<#assign reslut \*= num>
<#-- 递归调用宏计算阶乘 -->
<@factorial num = num - 1/>
</#if>
</#macro>
<@factorial num/>
<p>${num}的阶乘为:${reslut}</p>
</div>
效果:
num
变量分别设置为0和-1时:
nested
指令执行自定义指令开始和结束标签中间的模板片段。如果没有调用nested
指令,自定义指令开始和结束标签中的部分将会被忽略。nested
指令可以对嵌套内容创建循环变量。
在模板中新增以下代码,使用macro
和nested
输出99乘法表:
<div>
<#macro nested\_test count>
<table>
<#list 1..count as i>
<tr>
<#list 1..count as j>
<#if j lte i>
<#-- nested指令执行自定义指令开始和结束标签中间的模板片段
嵌套的片段可以包含模板中任意合法的内容,在宏调用的地方被执行
如果没有调用nested指令,自定义指令开始和结束标签中的部分将会被忽略
nested指令可以对嵌套内容创建循环变量 -->
<#nested i, j, i \* j>
</#if>
</#list>
</tr>
</#list>
</table>
</#macro>
<@nested\_test 9; i, j>
<td>${i} * ${j} = ${i * j}</td>
</@nested\_test>
</div>
效果:
使用return
指令可以将宏或函数定义体停留在任何位置。在模板中新增以下代码:
<div>
<#macro return\_test>
<p>© 2021 RtxTitanV . All rights reserved.</p>
<#-- return指令可以将宏或函数定义体停留在任何位置 -->
<#return>
<p>email: RtxTitanV@xxx.com</p>
</#macro>
<@return\_test/>
</div>
效果:
(14)switch、case、default、break
语法:
<#-- value、refValue1等:表达式将会计算成相同类型的标量 -->
<#-- break和default是可选的 -->
<#switch value>
<#case refValue1>
...
<#break>
<#case refValue2>
...
<#break>
...
<#case refValueN>
...
<#break>
<#default>
...
</#switch>
switch
指令会选择一个匹配的case
指令并继续处理模板,遇到break
指令会退出switch
。如果没有匹配的case
指令,存在default
指令,那么它会继续处理default
指令,否则就继续处理switch
结束标签后的内容。
switch
指令选择一个case
指令后还会继续处理直到遇到break
指令也就是它在遇到break
指令前遇到另一个case
指令或<#default>
标签时也不会自动退出switch
指令,这就是向下通行。
模板fm-directive-switch.ftl
如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>switch、case、default、break</title>
</head>
<body>
<div>
<p>
段位:
<#-- switch指令会选择一个匹配的case指令并继续处理模板,遇到break指令会退出switch
如果没有匹配的case指令,存在default指令,那么它会继续处理default指令
否则就继续处理switch结束标签后的内容 -->
<#switch rank>
<#case 1>
<span>青铜</span>
<#break>
<#case 2>
<span>白银</span>
<#break>
<#case 3>
<span>黄金</span>
<#break>
<#case 4>
<span>铂金</span>
<#break>
<#case 5>
<span>钻石</span>
<#break>
<#case 6>
<span>王者</span>
<#break>
<#default>
<span>无段位</span>
</#switch>
</p>
</div>
<hr/>
<div>
<p>
<#-- switch指令选择一个case指令后还会继续处理直到遇到break指令
也就是它在遇到break指令前遇到另一个case指令或<#default>标签时也不会自动退出switch指令
这就是向下通行 -->
<#switch rank>
<#case 1>
<span>LV1</span>
<#case 2>
<span>LV2</span>
<#case 3>
<span>LV3</span>
<#case 4>
<span>LV4</span>
<#case 5>
<span>LV5</span>
<#case 6>
<span>LV6</span>
<#default>
<span>LV0</span>
</#switch>
</p>
</div>
</body>
</html>
FreeMarkerController
中新增以下方法:
@GetMapping("/directive/switch")
public String switchDirective(Model model) {
model.addAttribute("rank", 3);
return "directive-switch";
}
效果:
变量rank
设置为0时:
4.表达式
(1)直接指定值
可以直接指定以下类型的值:
- 字符串:通过给文本加上双引号或单引号可以直接指定字符串值。如果文本本身包含用于引号的字符(
"
或'
)或反斜杠,则必须在前面加上反斜杠\
转义。转义允许直接在文本中输入包括换行符在内的任何字符。 - 数字:输入不带引号的数字就直接指定了一个数值。必须使用点作为小数的分隔符而不能是其他的分组分隔符。可以使用
-
或+
来表明符号(+
是多余的)。科学记数法暂不支持,也不能在小数点之前不写0。 - 布尔值:直接写true和false就指定了一个布尔值,无需使用引号。
- 序列:列出由逗号分隔的子变量并将整个列表放入方括号内,就指定了一个字面序列。列表中的每一项都可以是表达式。
- 值域:值域也是序列,但它们由指定包含的数字范围所创建, 而不需指定序列中每一项。值域主要用来迭代一定范围内的数字,以及字符串切分和序列切分。值域表达式的通用形式是(
start
和end
可以是任意的结果为数字表达式):start..end
:包含结尾的值域。例如,1..4
就是[1, 2, 3, 4]
,4..1
就是[4, 3 , 2, 1]
。包含结尾的值域不会是一个空序列,所以0..length-1
是 错误的,因为当长度是0时,序列就成了[0, -1]
。start..<end
或start..!end
:不包含结尾的值域。例如1..<4
就是[1, 2 , 3]
,4..<1
就是[4, 3, 2]
,1..<1
为[]
。start..*length
:限定长度的值域。例如5..*3
就是[5, 6, 7]
,5..*-3
就是[5, 4, 3]
,5..*0
为[]
。start..
:无右边界值域,长度是无限的。例如,1..
就是[1, 2, 3, 4, 5, 6, ... ]
。
- 哈希表:列出用逗号分隔的键值对,键值对中的键和值用冒号分开,并将该列表放入大括号中,就指定了一个哈希表。注意,名称和值都是表达式。键必须是字符串。值可以是任意类型。
模板fm-expression-specify-value.ftl
如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>直接指定值</title>
</head>
<body>
<div>
<ul>
<li><p>字符串:</p></li>
<ul>
<li><p>${"It's double quote: \"\", this is a backslash: \\"}</p></li>
<li><p>${'It\'s double quote: "", this is a backslash: \\'}</p></li>
<#-- 直接在模板中输入本文 -->
<li><p>It's double quote: "", this is a backslash: \</p></li>
<#-- 通过转义的方式打印具有特殊含义的${} -->
<li><p>${"$\{user}"}</p></li>
<#-- 通过原生字符串(引号前加r)打印具有特殊含义的${}以及\,原生字符串是一种特殊的字符串,${}和\在其中没有特殊含义,被视为普通字符串 -->
<li><p>${r"${user}, C:\Windows"}</p></li>
</ul>
<li><p>数字:${1}、${+1}、${001}、${1.00}、${-1}、${0.11}</p></li>
<#-- c为布尔值内建函数,将布尔值转换为字符串 -->
<li><p>布尔值:${true?c}、${false?c}</p></li>
<li>
<p>
序列:[
<#list ["刘备", "关羽", "张飞"] as name>
"${name}"<#sep>,
</#list>
]
</p>
</li>
<li><p>值域:</p></li>
<ul>
<li>
<#-- 1..9: [1, 2, 3, 4, 5, 6, 7, 8, 9] -->
<p>
1..9: [
<#list 1..9 as i>
${i}<#sep>,
</#list>
]
</p>
</li>
<li>
<#-- 1..<9: [1, 2, 3, 4, 5, 6, 7, 8] -->
<p>
1..<9: [
<#list 1..<9 as i>
${i}<#sep>,
</#list>
]
</p>
</li>
<li>
<#-- 5..\*6: [5, 6, 7, 8, 9, 10] -->
<p>
5..*6: [
<#list 5..\*6 as i>
${i}<#sep>,
</#list>
]
</p>
</li>
</ul>
<li>
<p>
哈希表:{
<#list {"id": 1, "username": "admin", "password": "123456"} as k, v>
<#if k == "id">
"${k}": ${v}<#sep>,
<#else>
"${k}": "${v}"<#sep>,
</#if>
</#list>
}
</p>
</li>
</ul>
</div>
</body>
</html>
值域的一些注意事项:
- 值域表达式本身没有方括号。
- 在
..
两侧写算术表达式无需小括号。..
、..<
、..!
和..*
是运算符, 所以它们中间不能有空格。- 值域不存储它们包含的数字。
效果:
(2)检索变量
- 顶层变量:访问顶层变量,可以简单使用变量名。例如
${user}
用表达式user
就可以在根(root)上获取以user为名存储的变量值。然后打印出存储在里面的内容。 - 从哈希表中检索数据:如果有一个表达式的结果是哈希表, 那么可以使用点和子变量的名字得到它的值。 如果有一个模型为
{root: {book: {title: "java核心技术", author: {name: "xxx", info: "java基础"}}, test: "title"}}
。那么可以通过book.title
来读取title
,book.author.name
来读取author
的name
。还可以通过book["title"]
来读取title
,通过book["author"].name
,book.author["name"]
或book["author"]["name"]
来读取author
的name
。 - 从序列中检索数据:和从哈希表中检索是相同的,但是只能使用方括号语法形式来进行, 而且方括号内的表达式最终必须是一个数字而不是字符串。例如
users[0].id
。 - 特殊变量:特殊变量是由FreeMarker引擎本身定义的。可以按照
.variable_name
的语法形式使用它们。例如之前使用的globals
和data_model
就是特殊变量。
注意:在顶层变量的变量名表达式中,变量名只可以包含字母(也可以是非拉丁文),数字(也可以是非拉丁数字),下划线
_
, 美元符号$
,at符号@
。 此外,第一个字符不可以是ASCII码数字(0
-9
)。从FreeMarker 2.3.22版本开始,变量名在任何位置也可以包含负号-
,点.
和冒号:
,但这些必须使用前置的反斜杠\
来转义,否则它们将被解释成操作符。(注意,这些转义仅在标识符中起作用,而不是字符串中。)当使用方括号语法时,因为名称可以是任意表达式的结果,则没有这样的限制。
(3)字符串操作
字符串操作有:
- 插值和连接:可以在字符串字面量中使用
${}
在字符串中插入表达式的值。${}
在字符串中的作用与在文本区相同。通过+
进行字符串连接也可以达到和插值类似的效果。 - 获取字符:给定索引值时可以获取字符串中的单个字符,这和序列类似,如
user[0]
,执行的结果是一个长度为1的字符串,FTL并没有独立的字符类型。和序列中的子变量一样,这个索引也必须是数字,范围是从0到字符串长度,否则模板的执行将会发生错误并终止。 - 字符串切分:类似于序列切分,只是用字符来代替序列的项。不同的是降序值域不允许进行字符串切分;如果变量的值既是字符串又是序列(多类型值),那么切分将会对序列进行,而不是字符串;还有一个遗留bug,值域包含结尾时,结尾小于开始索引并且是非负数时, 会返回空字符串而不是错误,现在这个bug已经向后兼容,但是不应该使用它。
警告:插值只在文本区(如
<h1>Hello ${name}!</h1
>)和字符串字面量(如<#include "/footer/${company}.html">
)中起作用。形如<#if ${big}>...</#if>
这样的写法是一个典型的语法错误,简单写为<#if big>...</#if>
即可,而且<#if "${big}">...</#if>
也是错误的, 因为它将参数值转换为字符串,但是if
指令只接受布尔值,这将导致运行时错误。
模板fm-expression-string-operations.ftl
如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>字符串操作</title>
</head>
<body>
<p>插值和连接</p>
<div>
<ul>
<li>
<#-- 可以在字符串字面量中使用${}在字符串中插入表达式的值 -->
<#assign message = "Hello ${user.username} !">
<p>message: ${message}</p>
</li>
<li>
<#-- 也可以使用+号将表达式的值连接到字符串中,达到类似于插值的效果 -->
<#assign message = "Hello " + user.username + " !">
<p>message: ${message}</p>
</li>
</ul>
</div>
<p>获取字符</p>
<div>
<ul>
<li><p>username索引为0的字符:${user.username[0]}</p></li>
<li><p>username索引为3的字符:${user.username[3]}</p></li>
<li><p>username索引为8的字符:${user.username[8]}</p></li>
</ul>
</div>
<p>字符串切分(子串)</p>
<div>
<ul>
<li><p>username从0到2(包括2)的子串:${user.username[0..2]}</p></li>
<li><p>username从0到2(不包括2)的子串:${user.username[0..<2]}</p></li>
<li><p>username从3开始长度为5的子串:${user.username[3..*5]}</p></li>
<li><p>username从3开始长度为100的子串(切分长度已经超过了从开始索引到被切分字符串末尾的长度,所以这里只截取到字符串末尾):${user.username[3..*100]}</p></li>
<li><p>username从3开始一直到末尾的子串:${user.username[3..]}</p></li>
</ul>
</div>
</body>
</html>
FreeMarkerController
中新增以下方法:
@GetMapping("/expression/string-operations")
public String stringOperations(Model model) {
model.addAttribute("user", new User(1L, "RtxTitanV", "654321"));
return "expression-string-operations";
}
效果:
(4)序列操作
序列操作有:
- 序列连接:可以使用
+
号来连接序列。 - 序列切分:使用
seq[range]
(seq
是序列,range
是值域),可以从序列中切分出一个片段。结果序列将包含原始序列中索引在值域中的项。此外,切片后序列的项将与值域的顺序相同。值域中的数字必须是序列可使用的合法索引,否则模板的处理将会终止并报错。
模板fm-expression-sequence-operations.ftl
如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>序列操作</title>
</head>
<body>
<h1>序列连接</h1>
<div>
<table>
<thead>
<th>id</th>
<th>username</th>
<th>password</th>
</thead>
<tbody>
<#list users1 + users2 as user>
<tr>
<td>${user.id}</td>
<td>${user.username}</td>
<td>${user.password}</td>
</tr>
</#list>
</tbody>
</table>
</div>
<h1>序列切分</h1>
<div>
<p>将序列切分为从索引1到3(包括3)的子序列</p>
<table>
<thead>
<th>id</th>
<th>username</th>
<th>password</th>
</thead>
<tbody>
<#list (users1 + users2)[1..3] as user>
<tr>
<td>${user.id}</td>
<td>${user.username}</td>
<td>${user.password}</td>
</tr>
</#list>
</tbody>
</table>
<p>将序列切分为从索引1到3(不包括3)的子序列</p>
<table>
<thead>
<th>id</th>
<th>username</th>
<th>password</th>
</thead>
<tbody>
<#list (users1 + users2)[1..<3] as user>
<tr>
<td>${user.id}</td>
<td>${user.username}</td>
<td>${user.password}</td>
</tr>
</#list>
</tbody>
</table>
<p>将序列切分为从索引2开始长度为2的子序列</p>
<table>
<thead>
<th>id</th>
<th>username</th>
<th>password</th>
</thead>
<tbody>
<#list (users1 + users2)[2..\*2] as user>
<tr>
<td>${user.id}</td>
<td>${user.username}</td>
<td>${user.password}</td>
</tr>
</#list>
</tbody>
</table>
<p>将序列切分为从索引2开始长度为5的子序列(切分长度已经超过了从开始索引到被切分序列末尾的长度,所以这里只截取到序列末尾)</p>
<table>
<thead>
<th>id</th>
<th>username</th>
<th>password</th>
</thead>
<tbody>
<#list (users1 + users2)[2..\*5] as user>
<tr>
<td>${user.id}</td>
<td>${user.username}</td>
<td>${user.password}</td>
</tr>
</#list>
</tbody>
</table>
<#-- 有限长度切分允许开始索引超过最后项索引一个数(但不能再多了) -->
<p>将序列切分为从索引5开始长度为2的子序列(开始索引已经超出了序列的索引范围,所以这里切分结果为空)</p>
<table>
<thead>
<th>id</th>
<th>username</th>
<th>password</th>
</thead>
<tbody>
<#list (users1 + users2)[5..\*2] as user>
<tr>
<td>${user.id}</td>
<td>${user.username}</td>
<td>${user.password}</td>
</tr>
</#list>
</tbody>
</table>
<p>将序列切分为从索引1开始的子序列</p>
<table>
<thead>
<th>id</th>
<th>username</th>
<th>password</th>
</thead>
<tbody>
<#list (users1 + users2)[1..] as user>
<tr>
<td>${user.id}</td>
<td>${user.username}</td>
<td>${user.password}</td>
</tr>
</#list>
</tbody>
</table>
<#-- 无右边界切分允许开始索引超过最后项索引一个数(但不能再多了) -->
<p>将序列切分为从索引5开始的子序列(开始索引已经超出了序列的索引范围,所以这里切分结果为空)</p>
<table>
<thead>
<th>id</th>
<th>username</th>
<th>password</th>
</thead>
<tbody>
<#list (users1 + users2)[5..] as user>
<tr>
<td>${user.id}</td>
<td>${user.username}</td>
<td>${user.password}</td>
</tr>
</#list>
</tbody>
</table>
</div>
</body>
</html>
FreeMarkerController
中新增以下方法:
@GetMapping("/expression/sequence-operations")
public String sequenceOperations(Model model) {
List<User> users1 = new ArrayList<>();
users1.add(new User(1L, "刘备", "123132"));
users1.add(new User(2L, "关羽", "321231"));
users1.add(new User(3L, "张飞", "213312"));
User[] users2 = {new User(4L, "赵云", "132213"), new User(5L, "马超", "312123")};
model.addAttribute("users1", users1);
model.addAttribute("users2", users2);
return "expression-sequence-operations";
}
效果:
(5)哈希表操作
可以使用+
号来连接哈希表。如果哈希表都包含相同的键,在+
号右侧的哈希值优先。
模板fm-expression-hash-operations.ftl
如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>哈希表操作</title>
</head>
<body>
<h1>哈希表连接</h1>
<div>
<table>
<thead>
<th>id</th>
<th>username</th>
<th>password</th>
<th>key</th>
</thead>
<tbody>
<#list map1 + map2 as k, v>
<tr>
<td>${v.id}</td>
<td>${v.username}</td>
<td>${v.password}</td>
<td>${k}</td>
</tr>
</#list>
</tbody>
</table>
</div>
</body>
</html>
FreeMarkerController
中新增以下方法:
@GetMapping("/expression/hash-operations")
public String hashOperations(Model model) {
Map<String, Object> map1 = new HashMap<>(16);
map1.put("user1", new User(1L, "刘备", "123132"));
map1.put("user2", new User(2L, "关羽", "321231"));
Map<String, Object> map2 = new HashMap<>(16);
map2.put("user1", new User(3L, "张飞", "213312"));
map2.put("user4", new User(4L, "赵云", "132213"));
map2.put("user5", new User(5L, "马超", "312123"));
model.addAttribute("map1", map1);
model.addAttribute("map2", map2);
return "expression-hash-operations";
}
效果:
(6)运算符
运算符主要分为:
- 算术运算符:加法(
+
)、减法(-
)、乘法(*
)、除法(/
)、求模(%
)。 - 比较运算符:等于(
==
)、不等于(!=
)、大于(>
、gt
)、小于(<
、lt
)、大于等于(>=
、gte
)、小于等于(<=
、lte
)。 - 逻辑运算符:逻辑与(
&&
)、逻辑或(||
)、逻辑非(!
)。
模板fm-expression-operation.ftl
如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>运算符</title>
</head>
<body>
<p>x=${x}, y=${y}</p>
<#-- 需要保证算术运算符两边都是结果为数字的表达式
+号例外,如果+一边是数字,另一边是字符串,那么数字会转换成字符串,然后进行字符串连接 -->
<p>算术运算符:+、-、*、/、%</p>
<ul>
<li><p>(y + x % 3) * (y - x / 2) :${(y + x % 3) * (y - x / 2)}</p></li>
</ul>
<#-- ==或!=两边的表达式的结果都必须是标量,而且两个标量都必须是相同类型
由于FreeMarker是精确比较,所以在比较字符串时要注意大小写和空格
<、<=、>=、>不能作用于字符串 -->
<p>比较运算符:==、!=、>(gt)、<(lt)、>=(gte)、<=(lte)</p>
<ul>
<li>
<p>
x == y:
<#if x == y>
x等于y
<#else>
x不等于y
</#if>
</p>
</li>
<li>
<p>
x != y:
<#if x != y>
x不等于y
<#else>
x等于y
</#if>
</p>
</li>
<li>
<p>
x > 5:
<#-- 注意使用比较运算符>、>=时,FreeMarker解释>时可以把它当做ftl标签的结束符
为了避免该问题,可以用小括号括起来或用lt代替<、lte代替<=、gt代替>、gte代替>= -->
<#if (x > 5)>
x大于5
<#else>
x不大于5
</#if>
</p>
</li>
<li>
<p>
y lte 2:
<#if y lte 2>
y小于等于2
<#else>
y大于2
</#if>
</p>
</li>
</ul>
<#-- 逻辑运算符只对布尔值起作用,否则,将出现错误导致模板处理中止 -->
<p>逻辑运算符:&&、||、!</p>
<ul>
<li>
<p>
y lt x && -x gt -y:
<#if y lt x && -x gt -y>
true
<#else>
false
</#if>
</p>
</li>
<li>
<p>
-x <= -y || y >= x:
<#if (-x <= -y || y >= x)>
true
<#else>
false
</#if>
</p>
</li>
<li>
<p>
!(-x < -y || y gte x):
<#if !(-x < -y || y gte x)>
true
<#else>
false
</#if>
</p>
</li>
</ul>
</body>
</html>
FTL忽略表达式中的多余的空格。小括号可以用来给任意表达式分组,注意方法调用使用的小括号与给表达式分组的小括号含义完全不同。
FreeMarkerController
中新增以下方法:
@GetMapping("/expression/operation")
public String operation(Model model) {
model.addAttribute("x", 10);
model.addAttribute("y", 3);
return "expression-operation";
}
效果:
(7)处理不存在的值
当试图访问一个不存在的变量时,就会发生错误并中止模板处理。然而,有两个特殊的操作符可以抑制这个错误,并处理这种问题。被处理的变量可以是顶级变量,也可以是哈希表或者序列的子变量。此外,这些操作符还可以处理方法调用没有返回值的情况。(从Java的角度来看:返回null或者返回类型为void),所以说这些操作符一般处理不存在的值,而不仅仅是不存在的变量更为正确。
这两个特殊的操作符:
- 默认值操作符:允许为可能不存在的变量指定一个默认值。默认值可以是任何类型的表达式。
- 使用形式:
unsafe_expr!default_expr
或unsafe_expr!
或(unsafe_expr)!default_expr
或(unsafe_expr)!
。
- 使用形式:
- 不存在值检测操作符:检测一个值是否存在,结果是true或false。
- 使用形式:
unsafe_expr??
或(unsafe_expr)??
。
- 使用形式:
模板fm-expression-missing.ftl
如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>处理不存在的值</title>
</head>
<body>
<p>默认值操作符</p>
<div>
<ul>
<li><p>name不存在时为默认值:${name!"No name"}</p></li>
<#assign name = "RtxTitanV">
<li><p>name存在时为name的值:${name!"No name"}</p></li>
<#-- 默认值可以是任何类型的表达式 -->
<li><p>num不存在时为默认值:${num!0}</p></li>
<li>
<p>
users不存在时为默认值:[
<#list users!["刘备", "关羽", "张飞"] as user>
"${user}"<#sep>,
</#list>
]
</p>
</li>
<#-- 如果默认值被省略了,那么它将同时是空字符串和空序列以及空哈希表 -->
<li><p>message不存在时为默认值:${message!}</p></li>
<#assign message = "Hello World">
<li><p>message存在时为message的值:${message!}</p></li>
<#-- 对于非顶层变量,默认值有两种使用方式 -->
<li><p>第一种方式,user必须存在,否则会报错,user存在但username不存在才会使用默认值:${user.username!"No username"}</p></li>
<#-- 当用小括号包围时,允许表达式的任意部分可以未定义,而没有小括号时,只允许表达式的最后部分未定义 -->
<li><p>第二种方式,user不存在或user存在但username不存在都会使用默认值而不会报错:${(user.username)!"No username"}</p></li>
<#assign seq = ["A", "B", "C"]>
<#-- 默认值操作符也可以作用于序列子变量,序列索引为负数时会出错 -->
<li><p>默认值操作符也可以作用于序列子变量:[${seq[0]!"?"}, ${seq[1]!"?"}, ${seq[2]!"?"}, ${seq[3]!"?"}, ${seq[4]!"?"}]</p></li>
</ul>
</div>
<p>不存在值检测操作符</p>
<ul>
<li>
<p>
password是否存在:
<#-- 不存在值检测操作符可以检测一个值是否存在,结果为true和false,对于非顶层变量,规则与默认值操作符相同 -->
<#if (user.password)??>
存在
<#else>
不存在
</#if>
</p>
</li>
</ul>
</body>
</html>
FreeMarkerController
中新增以下方法:
@GetMapping("/expression/missing")
public String missing(Model model) {
model.addAttribute("user", new User(1L, null, null));
return "expression-missing";
}
效果:
不设置变量user
到Model中时:
如上图所示,发现${user.username!"No username"}
报错,说明对于非顶层变量的默认值使用,没有用小括号包围时user不存在,模板就会报错,只有user存在但username不存在才会使用默认值。去掉${user.username!"No username"}
这行代码再次访问模板:
根据上面的测试结果,可以说明${(user.username)!"No username"}
,即对于非顶层变量的默认值使用,用小括号包围后在user不存在时或user存在但username不存在时都会使用默认值。
5.常用内建函数
内建函数就像FreeMarker添加到对象中的方法。为了防止与实际的方法和其他子变量的名称冲突,用问号(?
)来代替点(.
),将它们与父对象分开。FreeMarker中有很多内建函数,这里结合实例总结一些常用的内建函数。
(1)字符串内建函数
模板fm-built-ins-strings.ftl
如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>字符串内建函数</title>
</head>
<body>
<div>
<#-- 字符串字符串必须是true或false(大小写敏感),或必须是由boolean\_format设置的特定格式
否则访问该内建函数时,会发生错误终止模板 -->
<p>
字符串转为布尔值(boolean):
<#if "true"?boolean>
true
</#if>
<#if !"false"?boolean>
false
</#if>
</p>
<#-- 可以识别科学记数法,从FreeMarker 2.3.21版本开始,可以识别所有XML Schema数字格式,如NaN、INF、-INF,以及Java本地格式Infinity和-Infinity -->
<p>
字符串转换为数字(number):
${"8"?number}、${"0.88"?number}、${"8.88E8"?number}、
${"NaN"?number}、${"INF"?number}、${"-INF"?number}、${"Infinity"?number}、${"-Infinity"?number}
</p>
<p>字符串中首单词的首字母大写(cap_first):${str1?cap_first}</p>
<p>字符串中首单词的首字母小写(uncap_first):${str2?uncap_first}</p>
<p>字符串转换为小写形式(lower_case):${user.username?lower_case}</p>
<p>字符串转换为大写形式(upper_case):${user.username?upper_case}</p>
<p>
检查字符串是否包含指定子串(contains):
<#if user.username?contains("FreeMarker")>
包含Freemarker
<#else>
不包含FreeMarker
</#if>
</p>
<p>
检查字符串是否以指定子串开头(ends_with):
<#if user.username?starts\_with("SpringBoot")>
字符串以SpringBoot开头
<#else>
字符串没有以SpringBoot开头
</#if>
</p>
<p>
检查字符串是否以指定子串结尾(ends_with):
<#if user.username?ends\_with("Test")>
字符串以Test结尾
<#else>
字符串没有以Test结尾
</#if>
</p>
<p>如果字符串没有以第一个参数指定的子串开头,就会将它加到字符串前面,否则返回原字符串(ensure_starts_with):${user.username?ensure_starts_with("用户名是")}、${user.username?ensure_starts_with("Spring")}</p>
<p>如果字符串没有以第一个参数指定的子串结尾,就会将它加到字符串后面,否则返回原字符串(ensure_ends_with):${user.username?ensure_ends_with("是用户名")}、${user.username?ensure_ends_with("Test")}</p>
<#-- 如果第一个参数作为子串没有在该字符串中出现时(如果使用了第二个参数,那么就从给定的索引开始),返回-1 -->
<p>返回字符串中第一次出现子串时的索引(index_of):${user.username?index_of("in")}</p>
<#-- 第二个参数的数值没有限制,如果是负数,效果和0一样,如果比字符串的长度大,和是字符串长度那个数效果一样 -->
<p>返回字符串中第一次出现子串时的索引,指定开始搜索的索引位置(index_of):${user.username?index_of("in", 5)}</p>
<p>返回字符串中最后一次出现子串时的索引(last_index_of):${user.username?last_index_of("in")}</p>
<p>返回字符串中第一次出现子串时的索引,指定开始搜索的索引位置(last_index_of):${user.username?last_index_of("in", 5)}</p>
<p>字符串中字符数量(length):${user.username?length}</p>
<p>去除字符串首尾空格(trim):${user.password?trim}</p>
<#-- 如果没有找到参数字符串,会返回空串;如果参数是长度为0的字符串,会返回未改变的源字符串 -->
<p>保留字符串中给定子串第一次出现之后的部分(keep_after):${user.username?keep_after("-")}</p>
<p>保留字符串中给定子串最后一次出现之后的部分(keep_after_last):${user.username?keep_after_last("-")}</p>
<p>保留字符串中给定子串第一次出现之前的部分(keep_before):${user.username?keep_before("-")}</p>
<p>保留字符串中给定子串最后一次出现之前的部分(keep_before_last):${user.username?keep_before_last("-")}</p>
<p>从字符串开头移除给定子串,如果字符串不以给定子串开头,会返回源字符串(remove_beginning):${user.username?remove_beginning("Spring")}、${user.username?remove_beginning("spring")}</p>
<p>从字符串结尾移除给定子串,如果字符串不以给定子串结尾,会返回源字符串(remove_ending):${user.username?remove_ending("-Test")}、${user.username?remove_ending("-test")}</p>
<#-- 字符串截断至给定长度并附加一个终止符字符串(默认为[...],可以另外指定),如果字符串的长度不超过给定长度,则原样返回
结束符字符串的长度也算在给定长度内,当给定长度比结束符字符串的长度短时,结果长度也可以比给定长度长,这种情况下,结束符字符串仍然原样返回
还有一个额外的参数可以指定终止符字符串的假定长度,否则使用实际长度 -->
<p>
字符串截断至给定长度并附加一个终止符字符串(truncate):
<ul>
<li>
<#-- 如果截断发生在词的边界,那么在词尾和终止符串之间就有一个空格,否则它们之间就没有空格 -->
<p>倾向于在词的边界处截断,但如果结果长度小于指定长度的75%,还是会在词的中间截断(truncate):${str1?truncate(18)}、${str1?truncate(22)}、${str1?truncate(34)}、${str1?truncate(3)}、${str1?truncate(18, "...")}、${str1?truncate(18, "...", 1)}</p>
</li>
<li>
<p>总是在词的边界处截断(truncate_w):${str1?truncate_w(18)}、${str1?truncate_w(22)}、${str1?truncate_w(18, "...")}、${str1?truncate_w(18, "...", 1)}</p>
</li>
<li>
<p>任何字符处截断(truncate_c):${str1?truncate_c(18)}、${str1?truncate_c(22)}、${str1?truncate_c(18, "...")}、${str1?truncate_c(18, "...", 1)}</p>
</li>
</ul>
</p>
<#-- 字符串替换不处理单词边界,从左向右执行,若第一个参数为空串,那么所有空字符串都将被替换 -->
<p>字符串替换(replace):${user.username?replace("-", "_")}、${"ABABA"?replace("ABA", "BAB")}、${user.username?replace("", "_")}</p>
<p>
沿着另一字符串的出现处将字符串拆分为序列(split):
<#list user.username?split("-")>
[
<#items as s>
${s}<#sep>,
</#items>
]
</#list>
</p>
<p>
包含字符串中所有单词的序列,顺序为出现在字符串中的顺序(word_list):
<#list user.password?word\_list as word>
[${word}]<#sep>,
</#list>
</p>
<#-- 如果只有1个参数,则用空格补齐,直到整个字符串长度达到参数指定的长度
如果有两个参数,则用第二个参数指定的字符串补齐到指定长度,如果参数2字符串长度大于1,则该字符串会周期性插入 -->
<p>字符串从左侧补齐至指定长度(left_pad):</p>
<p>
[${""?left_pad(5)}]、[${"A"?left_pad(5)}]、[${"AB"?left_pad(5)}]、
[${"ABC"?left_pad(5)}]、[${"ABCDEF"?left_pad(5)}]、[${""?left_pad(5, "#")}]、
[${"A"?left_pad(5, "#")}]、[${"AB"?left_pad(5, "#")}]、[${"ABC"?left_pad(5, "#")}]、
[${"ABCDEF"?left_pad(5, "#")}]、[${""?left_pad(10, "^_^")}]、[${"A"?left_pad(10, "^_^")}]、
[${"ABC"?left_pad(10, "^_^")}]、[${"ABCDE"?left_pad(10, "^_^")}]、[${"ABCDEFG"?left_pad(10, "^_^")}]
</p>
<p>字符串从右侧补齐至指定长度(right_pad):</p>
<p>
[${""?right_pad(5)}]、[${"A"?right_pad(5)}]、[${"AB"?right_pad(5)}]、
[${"ABC"?right_pad(5)}]、[${"ABCDEF"?right_pad(5)}]、[${""?right_pad(5, "#")}]、
[${"A"?right_pad(5, "#")}]、[${"AB"?right_pad(5, "#")}]、[${"ABC"?right_pad(5, "#")}]、
[${"ABCDEF"?right_pad(5, "#")}]、[${""?right_pad(10, "^_^")}]、[${"A"?right_pad(10, "^_^")}]、
[${"ABC"?right_pad(10, "^_^")}]、[${"ABCDE"?right_pad(10, "^_^")}]、[${"ABCDEFG"?right_pad(10, "^_^")}]
</p>
</div>
</body>
</html>
FreeMarkerController
中新增以下方法:
@GetMapping("/built-ins/strings")
public String builtInsStrings(Model model) {
model.addAttribute("user",
new User(1L, "SpringBoot-FreeMarker-Strings-Test", " SpringBoot \_Free-Marker . Strings, @Test ? ! "));
model.addAttribute("str1", "springboot freemarker strings test");
model.addAttribute("str2", "SpringBoot FreeMarker Strings Test");
return "built-ins-strings";
}
效果:
(2)数字内建函数
模板fm-built-ins-numbers.ftl
如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>数字内建函数</title>
</head>
<body>
<div>
<p>求给定数字的绝对值(abs):${(-5)?abs}</p>
<p>数字转换为字符串(c):${num?c}、${num2?c}</p>
<#-- 1, 2, 3等转换为字符串"a", "b", "c"等,到达"z"会继续转换为"aa", "ab"等,数字最小为1,无上限 -->
<p>
数字转换为小写字母(lower_abc):
<#list 1..30 as i>
${i?lower_abc}<#sep>,
</#list>
</p>
<p>
数字转换为大写字母(upper_abc):
<#list 1..30 as i>
${i?upper_abc}<#sep>,
</#list>
</p>
<#-- 向正无穷方向进位 -->
<p>返回最近的整数,以.5结尾将进位(round):${0.25?round}、${0.5?round}、${1.75?round}、${(-0.25)?round}、${(-0.5)?round}、${(-1.75)?round}</p>
<#-- 向负无穷方向舍弃 -->
<p>返回舍弃小数后的整数(floor):${0.25?floor}、${0.5?floor}、${1.75?floor}、${(-0.25)?floor}、${(-0.5)?floor}、${(-1.75)?floor}</p>
<#-- 向正无穷方向进位 -->
<p>返回小数进位后的整数(ceiling):${0.25?ceiling}、${0.5?ceiling}、${1.75?ceiling}、${(-0.25)?ceiling}、${(-0.5)?ceiling}、${(-1.75)?ceiling}</p>
<p>
数字转换成字符串(string):
<ul>
<#-- 四种预定义数字格式:number、currency、percent、computer -->
<li><P>${num?string}、${num?string.number}、${num?string.currency}、${num2?string.percent}、${num?string.computer}</P></li>
<li><p>最小整数位为1,保留1位小数:${num2?string["0.0"]}</p></li>
<li><p>最小整数位为3,保留2位小数:${num2?string["000.00"]}</p></li>
<li><p>最小整数位为1,保留4位小数:${num2?string["0.####"]}</p></li>
<li><p>最小整数位为4,保留5位小数:${num2?string["0000.#####"]}</p></li>
<li><p>科学计数法:${num?string["0.##E0"]}</p></li>
</ul>
</p>
</div>
</body>
</html>
FreeMarkerController
中新增以下方法:
@GetMapping("/built-ins/numbers")
public String builtInsNumbers(Model model) {
model.addAttribute("num", 888888);
model.addAttribute("num2", 66.6658932);
return "built-ins-numbers";
}
效果:
(3)日期内建函数
模板fm-built-ins-date.ftl
如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>日期内建函数</title>
</head>
<body>
<div>
<#-- 可以用来指定日期变量的哪部分在使用 -->
<p>日期时间(datetime):${date?datetime}</p>
<p>日期(date):${date?date}</p>
<p>时间(time):${date?time}</p>
<p>以指定的格式将日期类型转换为字符串类型(string):</p>
<p>
<ul>
<#-- 由于FreeMarker不能决定数据模型中的日期变量是日期,时间还是日期-时间,在这种情况下,
${date?string.short}这种写法没有指定要显示的确定字段,FreeMarker不指定如何显示该值,最终模板会中止执行并报错
为了防止这种情况,可以用?date、?time和?datetime来确定要显示的部分 -->
<li><p>short: ${date?datetime?string.short}</p></li>
<li><p>medium: ${date?datetime?string.medium}</p></li>
<li><p>long: ${date?datetime?string.long}</p></li>
<li><p>full: ${date?datetime?string.full}</p></li>
<li><p>iso: ${date?datetime?string.iso}</p></li>
<li><p>iso_m_u: ${date?datetime?string.iso_m_u}</p></li>
<li><p>xs: ${date?datetime?string.xs}</p></li>
<li><p>xs_ms_nz: ${date?datetime?string.xs_ms_nz}</p></li>
<#-- 指定了格式之后可以不用?date、?time和?datetime,格式可以是变量或任意表达式 -->
<li><p>${date?string["yyyy/MM/dd EEEE a hh:mm:ss"]}</p></li>
<li><p>${date?date?string["yyyy"]}年${date?date?string["MM"]}月${date?date?string["dd"]}日</p></li>
<li><p>${date?time?string["hh"]}时${date?time?string["mm"]}分${date?time?string["ss"]}秒</p></li>
</ul>
</p>
</div>
</body>
</html>
FreeMarkerController
中新增以下方法:
@GetMapping("/built-ins/date")
public String builtInsDate(Model model) {
model.addAttribute("date", new Date());
return "built-ins-date";
}
效果:
(4)布尔值内建函数
模板fm-built-ins-booleans.ftl
如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>布尔值内建函数</title>
</head>
<body>
<div>
<#-- 不管boolean\_format设置是什么,结果是"true"或"false" -->
<p>布尔值转换为字符串(c):${(grade > 60)?c}、${(grade <= 60)?c}</p>
</div>
<div>
<p>布尔值转换为字符串(string):</p>
<ul>
<#-- 布尔值为true,则返回第一个参数,否则返回第二个参数,返回值总是字符串 -->
<li><p>成绩是否及格:${(grade > 60)?string("及格", "不及格")}</p></li>
</ul>
</div>
<div>
<#-- booleanExp为true,则返回第一个参数,否则返回第二个参数,参数表达式可以是任意类型,也可以是不同类型 -->
<p>使用booleanExp?then(whenTrue, whenFalse),类似于三元运算符(then):</p>
<ul>
<li>
<p>
成绩对应级别:${(grade gte 0 && grade lte 100)?then((grade gte 60)?then((grade gte 70)?then((grade gte 90)?then("优", "良"), "差"), "不及格"), "无效数据")}
</p>
</li>
</ul>
</div>
</body>
</html>
FreeMarkerController
中新增以下方法:
@GetMapping("/built-ins/booleans")
public String builtInsBooleans(Model model) {
model.addAttribute("grade", 85);
return "built-ins-booleans";
}
效果:
(5)序列内建函数
模板fm-built-ins-sequences.ftl
如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>序列内建函数</title>
</head>
## 最后
## 权威指南-第一本Docker书
引领完成Docker的安装、部署、管理和扩展,让其经历从测试到生产的整个开发生命周期,深入了解Docker适用于什么场景。并且这本Docker的学习权威指南介绍了其组件的基础知识,然后用Docker构建容器和服务来完成各种任务:利用Docker为新项目建立测试环境,演示如何使用持续集成的工作流集成Docker,如何构建应用程序服务和平台,如何使用Docker的API,如何扩展Docker。
总共包含了:简介、安装Docker、Docker入门、使用Docker镜像和仓库、在测试中使用Docker、使用Docker构建服务、使用Fig编配Docke、使用Docker API、获得帮助和对Docker进行改进等9个章节的知识。
![image](https://img-blog.csdnimg.cn/img_convert/6c9ffb62f378e2b946567803b64d294e.webp?x-oss-process=image/format,png)
![image](https://img-blog.csdnimg.cn/img_convert/a6dd5830fd4c0903e0ef19ecc80bb8aa.webp?x-oss-process=image/format,png)
![image](https://img-blog.csdnimg.cn/img_convert/7436619ff8fd8be373b11346a9a94ee1.webp?x-oss-process=image/format,png)
![image](https://img-blog.csdnimg.cn/img_convert/3d2dd2cd9c78d3649353cdfc4f918d45.webp?x-oss-process=image/format,png)
关于阿里内部都在强烈推荐使用的“K8S+Docker学习指南”—《深入浅出Kubernetes:理论+实战》、《权威指南-第一本Docker书》,看完之后两个字形容,爱了爱了!
-->
<li><p>${date?string["yyyy/MM/dd EEEE a hh:mm:ss"]}</p></li>
<li><p>${date?date?string["yyyy"]}年${date?date?string["MM"]}月${date?date?string["dd"]}日</p></li>
<li><p>${date?time?string["hh"]}时${date?time?string["mm"]}分${date?time?string["ss"]}秒</p></li>
</ul>
</p>
</div>
</body>
</html>
FreeMarkerController
中新增以下方法:
@GetMapping("/built-ins/date")
public String builtInsDate(Model model) {
model.addAttribute("date", new Date());
return "built-ins-date";
}
效果:
(4)布尔值内建函数
模板fm-built-ins-booleans.ftl
如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>布尔值内建函数</title>
</head>
<body>
<div>
<#-- 不管boolean\_format设置是什么,结果是"true"或"false" -->
<p>布尔值转换为字符串(c):${(grade > 60)?c}、${(grade <= 60)?c}</p>
</div>
<div>
<p>布尔值转换为字符串(string):</p>
<ul>
<#-- 布尔值为true,则返回第一个参数,否则返回第二个参数,返回值总是字符串 -->
<li><p>成绩是否及格:${(grade > 60)?string("及格", "不及格")}</p></li>
</ul>
</div>
<div>
<#-- booleanExp为true,则返回第一个参数,否则返回第二个参数,参数表达式可以是任意类型,也可以是不同类型 -->
<p>使用booleanExp?then(whenTrue, whenFalse),类似于三元运算符(then):</p>
<ul>
<li>
<p>
成绩对应级别:${(grade gte 0 && grade lte 100)?then((grade gte 60)?then((grade gte 70)?then((grade gte 90)?then("优", "良"), "差"), "不及格"), "无效数据")}
</p>
</li>
</ul>
</div>
</body>
</html>
FreeMarkerController
中新增以下方法:
@GetMapping("/built-ins/booleans")
public String builtInsBooleans(Model model) {
model.addAttribute("grade", 85);
return "built-ins-booleans";
}
效果:
(5)序列内建函数
模板fm-built-ins-sequences.ftl
如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>序列内建函数</title>
</head>
## 最后
## 权威指南-第一本Docker书
引领完成Docker的安装、部署、管理和扩展,让其经历从测试到生产的整个开发生命周期,深入了解Docker适用于什么场景。并且这本Docker的学习权威指南介绍了其组件的基础知识,然后用Docker构建容器和服务来完成各种任务:利用Docker为新项目建立测试环境,演示如何使用持续集成的工作流集成Docker,如何构建应用程序服务和平台,如何使用Docker的API,如何扩展Docker。
总共包含了:简介、安装Docker、Docker入门、使用Docker镜像和仓库、在测试中使用Docker、使用Docker构建服务、使用Fig编配Docke、使用Docker API、获得帮助和对Docker进行改进等9个章节的知识。
[外链图片转存中...(img-rizxlQLG-1718719360951)]
[外链图片转存中...(img-kMFgQ3NX-1718719360951)]
[外链图片转存中...(img-9JD3jfRz-1718719360952)]
[外链图片转存中...(img-ChsWHZnk-1718719360952)]
关于阿里内部都在强烈推荐使用的“K8S+Docker学习指南”—《深入浅出Kubernetes:理论+实战》、《权威指南-第一本Docker书》,看完之后两个字形容,爱了爱了!