Freemarker模板
Freemarker是一个用Java编写的开源模板引擎,适用于动态web页面生成以及各种文本输出。最早发布于2000年,经过多年的发展,Freemarker已成为Java领域广泛使用的模板引擎之一。
# Freemarker模板简介
# 1. 主要特点与优势
- 易学易用:Freemarker的语法简洁明了,学习成本较低。
- 性能高效:Freemarker具有出色的性能表现,模板渲染速度快。
- 功能丰富:Freemarker提供了丰富的指令和内置函数,支持条件判断、循环遍历等复杂逻辑。
- 灵活可扩展:Freemarker可以轻松地与Java代码集成,可以自定义函数和指令。
- 与前端技术良好兼容:Freemarker可以与HTML、CSS、JavaScript等前端技术无缝结合,帮助开发者快速搭建Web应用。
# 2. 应用场景
Freemarker模板引擎广泛应用于各种Java Web应用,如电商平台、企业官网、个人博客、社交网站等。除此之外,Freemarker还可用于生成代码、文档、邮件模板等其他文本输出场景。
# Freemarker模板的基本语法
# 1. 模板文件结构
Freemarker模板文件以.ftl
或.ftlh
(用于HTML)和.ftlx
(用于XML)为扩展名。模板文件可以包含静态文本、表达式、指令等元素,用于生成动态内容。
# 2. 数据模型
数据模型是模板引擎从Java代码中获取的数据,通常采用键值对(key-value)的形式存储。Freemarker可以访问数据模型中的值,以生成动态内容。
# 3. 注释
Freemarker支持单行和多行注释。注释内容不会出现在最终生成的文本中。
<#-- 单行注释 -->
<#--
多行注释
-->
# 4. 变量
Freemarker支持变量,可以从数据模型中获取值并在模板中使用。
${变量名}
# 表达式
# 1. 基本表达式
# a. 变量表达式
- 简单变量
${userName}
在模板中,使用${}
包裹变量名以获取变量的值。
- 哈希表
${user.address.street}
哈希表中的元素可以通过.
操作符访问。
- 序列
${colors[2]}
序列中的元素可以通过中括号[]
和索引访问。
# b. 字符串表达式
"Hello, ${userName}!"
字符串可以使用双引号"
包裹,可以包含变量表达式。
可以使用r""表示原始字符串,原始字符串中的转义字符不会被转义。
<#assign rawString = r"Line 1\nLine 2">
${rawString}
${r"${foo}"}
${r"C:\foo\bar"}
输出:
Line 1\nLine 2
${foo}
C:\foo\bar
# c. 数字表达式
${totalPrice * 1.05}
数字表达式可以直接使用数字和运算符进行运算。
# d. 布尔表达式
${isPremiumMember}
布尔表达式通常表示条件判断的结果,值为true
或false
。
# e. 空值(默认值)表达式
${unknownVar!'N/A'}
使用!
操作符表示变量的默认值。
# 2. 算术表达式
- 加法
${a + b}
- 减法
${a - b}
- 乘法
${a * b}
- 除法
${a / b}
- 取余
${a % b}
- 求幂
${a ^ b}
# 3. 比较表达式
- 等于
${a == b}
- 不等于
${a != b}
- 大于
${a > b}
- 小于
${a < b}
- 大于等于
${a >= b}
- 小于等于
${a <= b}
# 4. 逻辑表达式
- 与(AND)
${a && b}
- 或(OR)
${a || b}
- 非(NOT)
${!a}
# 5. 范围表达式
<#list 1..5 as number>
${number}
</#list>
使用范围迭代可以遍历一个数字范围,两个数字之间用两个点..
表示。
范围迭代的其他用法:
<
: 独占结束。比如说1..<4
给出[1,2, 3]
!
: 同<
号*
: 长度限制范围。比如10..*4
给出[10, 11, 12, 13]
,10..*-4
给出[10,9,8]
- 除了正序之外,还支持倒序。比如
5..1
给出[5,4,3,2,1]
# 6. 切片
字符串和序列都可以配合范围表达式来切片,比如对于一个字符串:
<#assign s = "ABCDEF">
${s[2..3]}
${s[2..<4]}
${s[2..*3]}
${s[2..*100]}
${s[2..]}
将输出:
CD
CD
CDE
CDEF
CDEF
字符串可以看成是字符的序列,对于序列来说,用法和上面一致。
# 7. 方法调用
${repeat("Foo", 3)}
repeat 是一个方法变量
# 8. 缺省值和缺失值判断
使用叹号可以设置变量缺省值:
${mouse!"No mouse."}
<#assign mouse="Jerry">
${mouse!"No mouse."}
输出:
No mouse.
Jerry
判断一个值是否存在,使用两个问号:
<#if mouse??>
Mouse found
<#else>
No mouse found
</#if>
Creating mouse...
<#assign mouse = "Jerry">
<#if mouse??>
Mouse found
<#else>
No mouse found
</#if>
输出:
No mouse found
Creating mouse...
Mouse found
# 9. 其他
- 可以使用括号进行表达式分组
- 表达式中的空格会被忽略
- 算数运算符的优先级和C、Java等同
# Freemarker模板指令
# 1. 条件指令
# a. if指令
if
指令用于条件判断,根据条件的真假决定是否执行某段代码。
<#if condition>
// 条件为真时执行的代码
</#if>
# b. elseif和else指令
elseif
和else
指令可以与if
指令配合使用,实现多条件判断。
<#if condition1>
// condition1为真时执行的代码
<#elseif condition2>
// condition1为假且condition2为真时执行的代码
<#else>
// 所有条件均为假时执行的代码
</#if>
# c. switch指令
switch
指令用于多条件分支判断。
<#switch value>
<#case refValue1>
...
<#break>
<#case refValue2>
...
<#break>
...
<#case refValueN>
...
<#break>
<#default>
...
</#switch>
# 2. 循环指令
# a. list指令
list
指令用于遍历序列或哈希,实现循环输出。
<#list sequence as item>
// 循环体,使用item变量访问当前元素
${item}
<#if x = "spring"><#break></#if>
</#list>
还可以配合seq、break、items、else、continue等指令一起使用。参考:list指令 (opens new window)
# 3. 包含与宏指令
# a. include指令
include
指令用于在当前模板中包含另一个模板的内容。
<#include "path/to/template.ftl">
指令有三个参数:
parse
默认为true,表示解析模板中的指令。如果为false,则模板中的指令不会被解析,而是原样输出。encoding
默认为UTF-8,表示模板的编码格式。ignore_missing
默认为false,表示如果模板不存在则抛出异常。如果为true,则不抛出异常,而是原样输出。
# b. import指令
import
指令用于引入另一个模板的宏(macro)。
<#import "/libs/commons.ftl" as com>
<@com.copyright date="1999-2002"/>
# c. macro指令
macro
指令用于定义宏,宏可以包含一段可重复使用的代码片段。
<#macro macroName param1 param2>
// 宏的内容
</#macro>
调用宏的方法如下:
<@macroName param1=value1 param2=value2/>
示例:
<#macro shout text>
${text?upper_case}!
</#macro>
---
<@shout text="hello"/>
nested
指令允许在宏中包含调用宏时传入的内容,实现更高级的代码复用。
<#macro wrapper>
<div>
<#nested/>
</div>
</#macro>
<@wrapper>
<p>这是嵌套内容</p>
</@>
# 4. 变量与函数指令
# a. assign指令
assign
指令用于在模板中定义变量。
<#assign variableName = value>
# b. local指令
local
指令用于在模板中定义局部变量,和assign指令类似,但是它创建或替换局部变量。这些变量仅在当前指令范围内有效。
<#local variableName = value>
# c. global指令
global
指令用于在模板中定义全局变量,和assign指令类似,但是它创建或替换全局变量。这些变量在整个模板中都有效。
<#global variableName = value>
# d. function指令
定义一个函数。
<#function avg x y>
<#return (x + y) / 2>
</#function>
${avg(10, 20)}
如果没有返回值,则返回null。
<#function avg nums...>
<#local sum = 0>
<#list nums as num>
<#local sum += num>
</#list>
<#if nums?size != 0>
<#return sum / nums?size>
</#if>
</#function>
${avg(10, 20)}
${avg(10, 20, 30, 40)}
${avg()!"N/A"}
# 5. 模板设置指令
# a. setting指令
设置当前模板的一些属性,如编码格式、日期格式、时区等。
<#setting locale="de_DE">
${1.2}
<#setting locale="en_US">
${1.2}
输出:
1,2
1.2
在 Freemarker 中,ftl 和 setting 都是指令,用于控制模板引擎的行为和配置。
setting 指令的属性有:
- locale:设置模板使用的语言环境(Locale)。
- number_format:设置数字格式化的模式。
- time_format:设置时间格式化的模式。
- date_format:设置日期格式化的模式。
- datetime_format:设置日期时间格式化的模式。
- boolean_format:设置布尔值格式化的模式。
- classic_compatible:设置是否启用兼容模式,以支持 Freemarker 1.x 的语法。
- whitespace_stripping:设置是否启用模板中的空白字符剥离。
- strict_syntax:设置是否启用严格的语法检查。
- tag_syntax:设置模板中使用的标签语法风格。
- naming_convention:设置模板中命名约定的风格。
- recognized_environments:设置模板中识别的运行环境。
- auto_import:设置自动导入的命名空间和类。
- recognized_macro_library_files:设置模板中识别的宏库文件。
- boolean_format:true_value/false_value:设置布尔值格式化时的真值和假值显示文本。
- object_wrapper:设置用于包装模板中的对象的对象包装器。
- interpolation_syntax:设置插值语法的风格。
- output_encoding:设置模板的输出编码。
# 6. 输出控制指令
# a. escape指令
escape
指令用于转义特殊字符,防止跨站脚本攻击(XSS)等安全问题。
<#escape x as x?html>
// 转义后的内容,所有特殊字符将被替换为HTML实体
</#escape>
# b. autoesc指令
autoesc 是 Freemarker 中的一个指令,用于自动转义输出值以提供基本的防御机制,防止跨站点脚本攻击(XSS)。
它会将输出值中的特殊字符(如 <, >, ', " 等)替换为对应的HTML实体,以确保在HTML上下文中显示时不会被解释为标签或脚本。
<#autoesc>
${"&"}
...
${"&"}
</#autoesc>
输出:
&
...
&
# c. noparse指令
相当于HTML中pre标签,会原样输出模板中的内容。
<#noparse>
<#list animals as animal>
<tr><td>${animal.name}<td>${animal.price} Euros
</#list>
</#noparse>
输出:
<#list animals as animal>
<tr><td>${animal.name}<td>${animal.price} Euros
</#list>
# d. compress指令
可以移除每一行前后的空白。
<#assign x = " moo \n\n ">
(<#compress>
1 2 3 4 5
${moo}
test only
I said, test only
</#compress>)
输出:
(1 2 3 4 5
moo
test only
I said, test only)
# e. t/lt/rt指令
这些指令指示FreeMarker忽略标记行中的某些空白。
t
忽略标记行中的所有空白。lt
忽略标记行中的左侧空白。rt
忽略标记行中的右侧空白。
--
1 <#t>
2<#t>
3<#lt>
4
5<#rt>
6
--
输出:
--
1 23
4
5 6
--
# 7. 调试指令
# a. stop指令
stop
指令用于立即停止处理当前模板,不再生成任何输出。这在某些特殊情况下可能会用到,例如处理错误或提前终止模板渲染。
<#stop>
# b. attempt指令和recover指令
attempt
指令和recover
指令用于错误处理,它们可以捕获模板渲染过程中出现的异常,并提供友好的错误信息。
<#attempt>
// 可能引发异常的代码
<#recover>
// 异常处理代码
</#attempt>
Freemarker功能非常丰富,还有许多其他的指令和功能。要深入了解所有的指令和功能,请参考Freemarker官方文档-指令集合 (opens new window)。
# 8. 自定义指令
除了Freemarker内置的指令之外,还可以编写自定义指令来扩展功能。
自定义指令需要实现freemarker.template.TemplateDirectiveModel
接口,然后在Java代码中将自定义指令添加到数据模型中。
public class CustomDirective implements TemplateDirectiveModel {
@Override
public void execute(Environment env, Map params, TemplateModel[] loopVars, TemplateDirectiveBody body)
throws TemplateException, IOException {
// 获取参数
String name = ((SimpleScalar) params.get("name")).getAsString();
// 执行指令体
StringWriter writer = new StringWriter();
body.render(writer);
String content = writer.toString();
// 输出结果
String result = String.format("Hello, %s! Your message is: %s", name, content);
env.getOut().write(result);
}
}
在模板中使用自定义指令:
<@hello name="Alice">Hello, world!</@hello>
输出:
Hello, Alice! Your message is: Hello, world!
# 内置函数
Freemarker提供了许多内置函数,用于处理字符串、数字、日期、序列等数据类型。这些内置函数可以对变量进行操作,以生成所需的输出。
内置函数的调用形式是:
${变量名?内置函数}
# 1. 字符串函数
以下是各个字符串函数的参数和功能介绍:
boolean(str)
:将一个字符串转换为布尔类型。参数str
是要转换的字符串。如果字符串是true
、yes
、y
、t
、1
,则返回true
,否则返回false
。cap_first(str)
:将一个字符串的首字母大写。参数str
是要大写首字母的字符串。c(str)
:将一个对象转换成字符串。参数str
是要转换的对象。cn(str)
:将一个对象转换成字符串,并将null
值转换为空字符串。参数str
是要转换的对象。c_lower_case(str)
:将一个字符串的第一个单词转换成小写字母。参数str
是要转换的字符串。c_upper_case(str)
:将一个字符串的第一个单词转换成大写字母。参数str
是要转换的字符串。capitalize(str)
:将一个字符串的所有单词的首字母大写,注意和cap_first的差异。参数str
是要大写首字母的字符串。chop_linebreak(str)
:去除一个字符串末尾的换行符(如果有)。参数str
是要去除换行符的字符串。contains(str, search)
:判断一个字符串是否包含指定的子字符串。参数str
是要判断的字符串,search
是要查找的子字符串。date(date, format)
:将一个日期对象格式化成字符串。参数date
是要格式化的日期对象,format
是日期格式模板。time(time, format)
:将一个时间对象格式化成字符串。参数time
是要格式化的时间对象,format
是时间格式模板。datetime(datetime, format)
:将一个日期时间对象格式化成字符串。参数datetime
是要格式化的日期时间对象,format
是日期时间格式模板。ends_with(str, suffix)
:判断一个字符串是否以指定的后缀结尾。参数str
是要判断的字符串,suffix
是要查找的后缀。ensure_ends_with(str, suffix)
:确保一个字符串以指定的后缀结尾,如果没有则添加。参数str
是要处理的字符串,suffix
是要添加的后缀。ensure_starts_with(str, prefix)
:确保一个字符串以指定的前缀开头,如果没有则添加。参数str
是要处理的字符串,prefix
是要添加的前缀。esc(str)
:对一个字符串进行 HTML 和 XML 转义,将一些特殊字符转换成 HTML 或 XML 实体。参数str
是要转义的字符串。groups(str, pattern)
:在一个字符串中查找指定正则表达式的匹配,并返回匹配的分组。参数str
是要查找的字符串,pattern
是正则表达式。html(str)
:对一个字符串进行 HTML 转义,将一些特殊字符转换成 HTML 实体。参数str
是要转义的字符串。index_of(str, search)
:查找一个字符串中指定子字符串第一次出现的位置。参数str
是要查找的字符串,search
是要查找的子字符串。j_string(str)
:对一个字符串进行 JSON 转义,将一些特殊字符转换成 JSON 字符串中的转义字符。参数str
是要转义的字符串。js_string(str)
:对一个字符串进行 JavaScript 转义,将一些特殊字符转换成 JavaScript 字符串中的转义字符。参数str
是要转义的字符串。json_string(obj)
:将一个对象转换成 JSON 字符串。参数obj
是要转换的对象。keep_after(str, search)
:保留一个字符串中指定子字符串后面的内容。参数str
是要处理的字符串,search
是要保留的子字符串。keep_after_last(str, search)
:保留一个字符串中最后一个指定子字符串后面的内容。参数str
是要处理的字符串,search
是要保留的子字符串。keep_before(str, search)
:保留一个字符串中指定子字符串前面的内容。参数str
是要处理的字符串,search
是要保留的子字符串。keep_before_last(str, search)
:保留一个字符串中最后一个指定子字符串前面的内容。参数str
是要处理的字符串,search
是要保留的子字符串。last_index_of(str, search)
:查找一个字符串中指定子字符串最后一次出现的位置。参数str
是要查找的字符串,search
是要查找的子字符串。left_pad(str, width, pad)
:在一个字符串左侧添加指定的填充字符,使其达到指定的宽度。参数str
是要填充的字符串,width
是要填充到的宽度,pad
是填充字符。length(str)
:获取一个字符串的长度。参数str
是要获取长度的字符串。lower_case(str)
:将一个字符串转换成小写字母。参数str
是要转换的字符串。matches(str, pattern)
:判断一个字符串是否匹配指定的正则表达式。参数str
是要判断的字符串,pattern
是正则表达式。no_esc(str)
:对一个字符串进行无转义输出,不进行任何转义。参数str
是要输出的字符串。number(str)
:将一个字符串转换成数字。参数str
是要转换的字符串。replace(str, search, replace)
:将一个字符串中所有出现的目标字符串替换成另一个字符串。参数str
是原始字符串,search
是要被替换的目标字符串,replace
是替换目标字符串的字符串。right_pad(str, width, pad)
:在一个字符串右侧添加指定的填充字符,使其达到指定的宽度。参数str
是要填充的字符串,width
是要填充到的宽度,pad
是填充字符。remove_beginning(str, remove)
:去除一个字符串开头的指定子字符串。参数str
是要处理的字符串,remove
是要去除的子字符串。remove_ending(str, remove)
:去除一个字符串末尾的指定子字符串。参数str
是要处理的字符串,remove
是要去除的子字符串。rtf(str)
:对一个字符串进行 RTF 转义,将一些特殊字符转换成 RTF 控制字符。参数str
是要转义的字符串。split(str, delimiter)
:将一个字符串按照指定的分隔符拆分成一个字符串列表。参数str
是要拆分的字符串,delimiter
是分隔符。starts_with(str, prefix)
:判断一个字符串是否以指定的前缀开头。参数str
是要判断的字符串,prefix
是要查找的前缀。string(str)
:将一个对象转换成字符串。参数str
是要转换的对象。substring(str, beginIndex, endIndex)
:获取一个字符串的子串。参数str
是要获取子串的字符串,beginIndex
是起始下标(包括该下标的字符),endIndex
是结束下标(不包括该下标的字符)。trim(str)
:去除一个字符串两侧的空格。参数str
是要去除空格的字符串。truncate(str, maxLength)
:将一个字符串截断到指定的最大长度。参数str
是要截断的字符串,maxLength
是最大长度。truncate_auto(str, maxLength, suffix)
:将一个字符串截断到指定的最大长度,并添加指定的后缀。如果字符串本身已经小于等于最大长度,则不进行截断。参数str
是要截断的字符串,maxLength
是最大长度,suffix
是要添加的后缀。truncate_words(str, maxLength, suffix)
:将一个字符串按照空格拆分成单词列表,将单词列表缩略到指定的最大长度,并添加指定的后缀。参数str
是要处理的字符串,maxLength
是最大长度,suffix
是要添加的后缀。uncap_first(str)
:将一个字符串的首字母转换成小写字母。参数str
是要转换的字符串。upper_case(str)
:将一个字符串转换成大写字母。参数str
是要转换的字符串。url(str)
:对一个字符串进行 URL 转义,将一些特殊字符转换成 URL 编码。参数str
是要转义的字符串。url_path(str)
:对一个字符串进行 URL 路径转义,将一些特殊字符转换成 URL 编码。参数str
是要转义的字符串。word_list(str)
:将一个字符串按照空格拆分成单词列表。参数str
是要拆分的字符串。
需要注意,上面说的str一般表示的是表达式的值。
对于replace、split、ensure_starts_with、keep_after、keep_before等函数会用到flags参数,flags参数是一个字符串,可以包含以下字符:
r
:要查找的子字符串是一个正则表达式。i
:在匹配时忽略大小写。m
:启用多行模式。^ 和 $ 可以匹配每一行的开头和结尾。s
:启用点号(.)匹配任意字符,包括换行符。c
:允许在正则表达式中使用空格和注释。f
:仅首次。即,仅替换/查找等第一次出现的内容。
这些标志可以根据匹配需求的不同进行组合使用。例如,使用 i
标志可以实现不区分大小写的匹配,而使用 m
标志可以实现多行模式的匹配。
函数比较多,对于有些不理解的函数,参考string 内置函数 (opens new window),上面有对各个函数介绍和示例。
# 2. 数字函数
abs(num)
:返回一个数的绝对值。参数num
是要求绝对值的数字。c(num)
:将一个对象转换成数值类型。参数num
是要转换的对象。cn(num)
:将一个对象转换成数值类型,并将null
值转换为 0。参数num
是要转换的对象。is_infinite(num)
:判断一个数是否为无穷大。参数num
是要判断的数字。is_nan(num)
:判断一个数是否为 NaN(Not a Number)。参数num
是要判断的数字。lower_abc(num)
:将一个数转换成小写字母表中对应的字母。参数num
是要转换的数字,转换规则为 1 对应a
,2 对应b
,以此类推。round(num, scale, roundingMode)
:对一个数进行四舍五入。参数num
是要四舍五入的数字,scale
是小数点后保留的位数,roundingMode
是取整模式,可以是halfUp
、halfDown
、halfEven
、up
、down
中的一种。floor(num)
:对一个数进行向下取整。参数num
是要取整的数字。ceiling(num)
:对一个数进行向上取整。参数num
是要取整的数字。string(num)
:将一个对象转换成字符串。参数num
是要转换的对象,通常为数值类型。upper_abc(num)
:将一个数转换成大写字母表中对应的字母。参数num
是要转换的数字,转换规则为 1 对应A
,2 对应B
,以此类推。
# 3. 日期函数
date(date)
:从一个日期/时间/日期时间值中提取出日期部分。参数date
是要提取日期部分的日期/时间/日期时间值。time(date)
:从一个日期/时间/日期时间值中提取出时间部分。参数date
是要提取时间部分的日期/时间/日期时间值。datetime(date)
:将一个日期/时间/日期时间值转换成日期时间格式的字符串。参数date
是要转换的日期/时间/日期时间值。date_if_unknown(date, fallback)
:从一个日期/时间/日期时间值中提取出日期部分,如果提取不到,则返回指定的备选值。参数date
是要提取日期部分的日期/时间/日期时间值,fallback
是备选值。time_if_unknown(date, fallback)
:从一个日期/时间/日期时间值中提取出时间部分,如果提取不到,则返回指定的备选值。参数date
是要提取时间部分的日期/时间/日期时间值,fallback
是备选值。datetime_if_unknown(date, fallback)
:将一个日期/时间/日期时间值转换成日期时间格式的字符串,如果转换失败,则返回指定的备选值。参数date
是要转换的日期/时间/日期时间值,fallback
是备选值。iso_...
:ISO 格式化日期/时间/日期时间值的函数。可以有以下变种:iso_date(date)
:将一个日期/时间/日期时间值转换成 ISO 8601 格式的日期字符串,例如2019-12-31
。iso_time(date)
:将一个日期/时间/日期时间值转换成 ISO 8601 格式的时间字符串,例如12:34:56.789
。iso_datetime(date)
:将一个日期/时间/日期时间值转换成 ISO 8601 格式的日期时间字符串,例如2019-12-31T12:34:56.789+08:00
。
string(date)
:将一个日期/时间/日期时间值转换成字符串。参数date
是要转换的日期/时间/日期时间值。
# 4. 布尔函数
c(bool)
:将一个对象转换成布尔类型。参数bool
是要转换的对象。cn(bool)
:将一个对象转换成布尔类型,并将null
值转换为false
。参数bool
是要转换的对象。string(bool)
:将一个对象转换成字符串。参数bool
是要转换的对象,通常为布尔类型。then(condition, thenValue, elseValue)
:根据条件返回指定的值。如果条件为真,则返回thenValue
,否则返回elseValue
。参数condition
是要判断的条件,可以是布尔类型或布尔表达式;thenValue
是条件为真时返回的值;elseValue
是条件为假时返回的值。
# 5. 序列函数
chunk(seq, chunkSize)
:将一个序列按照指定的大小拆分成多个子序列。参数seq
是要拆分的序列,chunkSize
是每个子序列的大小。drop_while(seq, condition)
:从一个序列中删除满足条件的前缀,直到遇到第一个不满足条件的元素。参数seq
是要处理的序列,condition
是一个布尔表达式,表示要删除的元素应该满足的条件。filter(seq, condition)
:从一个序列中筛选出满足条件的元素,返回一个新的序列。参数seq
是要筛选的序列,condition
是一个布尔表达式,表示要筛选的元素应该满足的条件。first(seq)
:返回一个序列的第一个元素。参数seq
是要取第一个元素的序列。join(seq, separator)
:将一个序列中的所有元素用指定的分隔符连接成一个字符串。参数seq
是要连接的序列,separator
是分隔符。last(seq)
:返回一个序列的最后一个元素。参数seq
是要取最后一个元素的序列。map(seq, transform)
:对一个序列中的每个元素应用指定的转换函数,返回一个新的序列。参数seq
是要转换的序列,transform
是转换函数。min(seq)
:返回一个序列中的最小值。参数seq
是要查找最小值的序列。max(seq)
:返回一个序列中的最大值。参数seq
是要查找最大值的序列。reverse(seq)
:将一个序列中的元素顺序颠倒,返回一个新的序列。参数seq
是要翻转的序列。seq_contains(seq, value)
:判断一个序列是否包含指定的值。参数seq
是要查找的序列,value
是要查找的值。seq_index_of(seq, value)
:返回一个序列中第一次出现指定值的下标,如果没有找到则返回 -1。参数seq
是要查找的序列,value
是要查找的值。seq_last_index_of(seq, value)
:返回一个序列中最后一次出现指定值的下标,如果没有找到则返回 -1。参数seq
是要查找的序列,value
是要查找的值。size(seq)
:返回一个序列中元素的个数。参数seq
是要计算大小的序列。sort(seq)
:对一个序列进行升序排序,返回一个新的序列。参数seq
是要排序的序列。sort_by(seq, transform)
:对一个序列中的元素进行转换,然后根据转换结果进行升序排序,返回一个新的序列。参数seq
是要排序的序列,transform
是转换函数,它将序列中的每个元素转换为排序用的值。转换函数可以是一个 lambda 表达式或是一个自定义函数。take_while(seq, condition)
:从一个序列中取出满足条件的前缀,直到遇到第一个不满足条件的元素。参数seq
是要处理的序列,condition
是一个布尔表达式,表示要取出的元素应该满足的条件。
# 6. 哈希表函数
keys(map)
:返回一个哈希表中所有键组成的列表。参数map
是要获取键列表的哈希表。values(map)
:返回一个哈希表中所有值组成的列表。参数map
是要获取值列表的哈希表。
对于list指令,也可以把键值一次性赋值:<#list attrs as key, value>...<#list>
# 7. 循环变量函数
list指令对应着Java的foreach。对于其中的循环变量,有如下函数:
counter
:返回当前循环的计数器值,从 1 开始计数。has_next
:判断当前循环是否有下一个元素,返回一个布尔值。index
:返回当前循环的索引值,从 0 开始计数。is_even_item
:判断当前循环的计数器值是否为偶数,返回一个布尔值。is_first
:判断当前循环是否为第一个元素,返回一个布尔值。is_last
:判断当前循环是否为最后一个元素,返回一个布尔值。is_odd_item
:判断当前循环的计数器值是否为奇数,返回一个布尔值。item_cycle(list)
:按照顺序循环遍历一个列表中的所有元素,并在列表的末尾重新开始。参数list
是要循环遍历的列表。item_parity
:判断当前循环的计数器值是偶数还是奇数,并返回对应的字符串"even"
或"odd"
。item_parity_cap
:判断当前循环的计数器值是偶数还是奇数,并返回对应的字符串"Even"
或"Odd"
,首字母大写。
# 8. XML节点函数
ancestors(node)
:返回一个节点的所有祖先节点。参数node
是要获取祖先节点的节点。children(node)
:返回一个节点的所有子节点。参数node
是要获取子节点的节点。node_name(node)
:返回一个节点的名称。参数node
是要获取名称的节点。next_sibling(node)
:返回一个节点的下一个同级节点。参数node
是要获取下一个同级节点的节点。node_namespace(node)
:返回一个节点的命名空间。参数node
是要获取命名空间的节点。node_type(node)
:返回一个节点的类型。参数node
是要获取类型的节点。parent(node)
:返回一个节点的父节点。参数node
是要获取父节点的节点。previous_sibling(node)
:返回一个节点的上一个同级节点。参数node
是要获取上一个同级节点的节点。root(node)
:返回一个节点所在的文档的根节点。参数node
是要获取根节点的节点。
# 9. switch函数
独立于类型的函数只有switch一个。
switch(case1, result1, case2, result2, ... caseN, resultN, defaultResult)
:按照指定的值或表达式进行匹配,返回匹配到的结果。
用法:
<#list ['r', 'w', 'x', 's'] as flag>
${flag?switch('r', 'readable', 'w' 'writable', 'x', 'executable', 'unknown flag: ' + flag)}
</#list>
# 10. 专家函数
以下是一些通常不该用到的内置函数,但是在调试或者高级宏中可能会用到他们。
absolute_template_name(template)
:返回模板的绝对路径。参数template
是模板名称。api(name)
:在模板中引入 Java API 类。参数name
是 Java 类的名称。has_api(name)
:判断是否存在指定的 Java API 类。参数name
是 Java 类的名称。byte(value)
:将一个值转换为字节类型。double(value)
:将一个值转换为双精度浮点数类型。float(value)
:将一个值转换为单精度浮点数类型。int(value)
:将一个值转换为整型。long(value)
:将一个值转换为长整型。short(value)
:将一个值转换为短整型。eval(expression)
:执行一个表达式,并返回表达式的值。参数expression
是要执行的表达式。eval_json(expression)
:将 JSON 字符串转换为对象,并执行一个表达式。参数expression
是要执行的表达式,它可以包括 JSON 对象的属性和方法调用。has_content(value)
:判断一个值是否为空。如果值为字符串类型,则忽略空格和换行符,只要包含任意字符就返回true
;如果值为集合类型,则只要包含任意元素就返回true
;如果值为null
,则返回false
。interpret(expression)
:将一个字符串解析为模板,并执行解析后的模板。参数expression
是要解析的字符串。is_...(value)
:一组函数,用于判断一个值是否属于某种类型,包括is_boolean
、is_date
、is_hash
、is_list
、is_macro
、is_node
、is_number
、is_sequence
、is_string
等。markup_string(string)
:将一个字符串解析为标记字符串,并返回标记字符串对象。参数string
是要解析的字符串。namespace(prefix)
:返回指定前缀的命名空间。参数prefix
是命名空间前缀。new(className, args)
:创建一个新的对象。参数className
是对象的类名,args
是对象的构造函数参数。number_to_date(value, pattern)
、number_to_time(value, pattern)
、number_to_datetime(value, pattern)
:将一个数字转换为日期、时间或日期时间,并返回格式化后的字符串。参数value
是要转换的数字,pattern
是日期、时间或日期时间的格式化字符串。sequence(start, end)
:创建一个指定范围的整数序列。参数start
是序列的起始值,end
是序列的结束值(不包含)。with_args(arguments, expression)
:执行一个表达式,并将参数传递给表达式。参数arguments
是一个列表,包含参数的名称和值,expression
是要执行的表达式。with_args_last(arguments, expression)
:执行一个表达式,并将参数传递给表达式。与with_args
函数不同之处在于,最后一个参数的值会被作为可变参数传递给表达式。参数arguments
是一个列表,包含参数的名称和值,最后一个参数的名称应为...
,expression
是要执行的表达式。
# 特殊变量引用
特殊变量是FreeMarker引擎定义的变量 本身。要访问它们,请使用.variable_name
语法。例如,你不能简单地写version
;你必须写.version
。
以下是特殊变量列表:
args
: 表示当前模板调用的参数列表。该变量引用的是一个包含所有参数的 Map 对象。在模板中可以使用${args.name}
的方式获取特定参数的值。current_template_name
: 表示当前模板的名称。该变量引用的是一个字符串,表示当前模板的文件名或其他标识符。data_model
: 表示模板使用的数据模型。该变量引用的是一个 Map 对象,其中包含了模板需要的所有数据。在模板中可以使用${data_model.name}
的方式获取特定数据的值。global
: 表示全局数据模型。该变量引用的是一个 Map 对象,其中包含了所有模板都可以使用的全局数据。在模板中可以使用${global.name}
的方式获取特定全局数据的值。locale
: 表示当前模板的区域设置。该变量引用的是一个字符串,表示当前模板的区域设置,例如 "en_US" 或 "zh_CN" 等。main
: 表示主模板。该变量引用的是一个布尔值,表示当前模板是否为主模板。如果是,则返回 true,否则返回 false。namespace
: 表示当前命名空间。该变量引用的是一个字符串,表示当前模板的命名空间。output_encoding
: 表示当前输出编码。该变量引用的是一个字符串,表示当前模板输出的编码格式,例如 "UTF-8" 或 "ISO-8859-1" 等。get_optional_template
: 表示获取可选模板的函数。该变量引用的是一个函数对象,可以使用 `${get_optional_template(name)} 的方式获取特定名称的可选模板。如果指定的模板不存在,则返回 null。incompatible_improvements
: 表示不兼容的改进版本号。该变量引用的是一个字符串,表示当前模板使用的 FreeMarker 不兼容的改进版本号。namespace_prefix
: 表示命名空间前缀。该变量引用的是一个字符串,表示当前模板的命名空间前缀。object_wrapper
: 表示对象包装器。该变量引用的是一个对象包装器,用于将 Java 对象转换为模板可以识别的模型对象。auto_flush
: 表示自动刷新。该变量引用的是一个布尔值,表示当前模板的自动刷新设置。如果设置为 true,则 FreeMarker 会在缓冲区满或模板执行完成后自动刷新输出。output_format
: 表示输出格式。该变量引用的是一个输出格式对象,用于控制模板的输出格式。time_zone
: 表示时区。该变量引用的是一个时区对象,用于控制模板的时间格式和解析行为。url_escaping_charset
: 存储应该用于URL转义的字符集。如果这个变量不存在,这意味着没有人指定应该用于URL编码。在这种情况下,内置使用由output_encoding指定的字符集用于URL编码的特殊变量;自定义机制可能遵循同样的逻辑。number_format
: 表示数字格式。该变量引用的是一个数字格式对象,用于控制模板中数字的格式化方式。boolean_format
: 表示布尔值格式。该变量引用的是一个布尔值格式对象,用于控制模板中布尔值的格式化方式。date_format
: 表示日期格式。该变量引用的是一个日期格式对象,用于控制模板中日期的格式化方式。time_format
: 表示时间格式。该变量引用的是一个时间格式对象,用于控制模板中时间的格式化方式。datetime_format
: 表示日期时间格式。该变量引用的是一个日期时间格式对象,用于控制模板中日期时间的格式化方式。
# Freemarker模板与Java集成
# 1. Java环境配置
要使用Freemarker模板引擎,首先需要将其添加到Java项目中。在Maven项目中,可以将以下依赖添加到pom.xml
文件中:
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.31</version>
</dependency>
对于Gradle项目,将以下依赖添加到build.gradle
文件中:
implementation 'org.freemarker:freemarker:2.3.31'
# 2. Java代码实现
# a. 创建Configuration实例
freemarker.template.Configuration
类是Freemarker引擎的核心配置类。首先,创建一个Configuration
实例:
import freemarker.template.Configuration;
import freemarker.template.Version;
Configuration cfg = new Configuration(new Version("2.3.31"));
// 设置属性
cfg..setDefaultEncoding(StandardCharsets.UTF_8.name());
Configuration
对象可以设置多个属性来定制 FreeMarker 的行为和特性。其中一些常见的设置包括:
version
: 设置 FreeMarker 的版本号。templateLoader
: 设置模板加载器,用于加载模板文件。objectWrapper
: 设置对象包装器,用于将 Java 对象转换为模板可以识别的模型对象。defaultEncoding
: 设置模板的默认编码。outputEncoding
: 设置模板输出的编码格式。locale
: 设置模板的区域设置。numberFormat
: 设置数字格式化器,用于控制模板中数字的格式化方式。dateFormat
: 设置日期格式化器,用于控制模板中日期的格式化方式。timeFormat
: 设置时间格式化器,用于控制模板中时间的格式化方式。dateTimeFormat
: 设置日期时间格式化器,用于控制模板中日期时间的格式化方式。booleanFormat
: 设置布尔值格式化器,用于控制模板中布尔值的格式化方式。templateExceptionHandler
: 设置模板异常处理器,用于处理模板中的异常。autoFlush
: 设置自动刷新,用于控制 FreeMarker 是否在缓冲区满或模板执行完成后自动刷新输出。wrapUncheckedExceptions
: 设置是否包装非检查异常,用于控制 FreeMarker 是否将非检查异常包装成 TemplateException。logTemplateExceptions
: 设置是否记录模板异常,用于控制 FreeMarker 是否将模板异常记录到日志中。numberFormatFactory
: 设置数字格式化器工厂,用于创建数字格式化器。dateFormatFactory
: 设置日期格式化器工厂,用于创建日期格式化器。timeFormatFactory
: 设置时间格式化器工厂,用于创建时间格式化器。dateTimeFormatFactory
: 设置日期时间格式化器工厂,用于创建日期时间格式化器。booleanFormatFactory
: 设置布尔值格式化器工厂,用于创建布尔值格式化器。
除了上述常见的设置之外,还有一些高级的配置选项可以进一步定制 FreeMarker 的行为和特性,例如缓存设置、模板加载器的缓存设置、模板和数据模型的缓存设置等等。
Configuration
类还有一个setSettings
方法,允许设置Properties
,如果用了这个方法,那么这里面的设置将覆盖上面的设置。
# b. 设置模板路径
设置模板文件的加载路径。可以使用setClassForTemplateLoading
方法设置基于类路径的模板加载路径:
cfg.setClassForTemplateLoading(this.getClass(), "/templates");
这样的设置,只适合于模板目录在当前项目中。如果模板目录在引用的jar中,这种方法就不行了,需要自定义TemplateLoader
。以下是一个例子:
import freemarker.cache.URLTemplateLoader;
import org.springframework.util.ClassUtils;
import java.net.URL;
import java.util.Objects;
/**
* 为解决不能读取jar中目录的问题,拓展Freemarker的TemplateLoader
*/
public class ClassloaderTemplateLoader extends URLTemplateLoader {
private final String path;
public ClassloaderTemplateLoader(String path) {
super();
this.path = canonicalizePrefix(path);
}
@Override
protected URL getURL(String name) {
name = path + name;
return Objects.requireNonNull(ClassUtils.getDefaultClassLoader()).getResource(name);
}
}
---
cfg.setTemplateLoader(new ClassloaderTemplateLoader("/templates"));
如果模板来自于字符串,那么需要这么设置:
StringTemplateLoader stringTemplateLoader = new StringTemplateLoader();
cfg.setTemplateLoader(stringTemplateLoader);
# c. 生成模板实例
从配置的模板路径中加载模板文件,创建一个模板实例:
import freemarker.template.Template;
Template template = cfg.getTemplate("example.ftl");
对于字符串模板来说,是这样创建的:
String ftlSource = "模板内容...";
String defaultFtlName = "default_" + ftlSource.hashCode();
stringTemplateLoader.putTemplate(defaultFtlName, ftlSource);
try {
Template template = STRING_TEMPLATE_CONFIGURATION.getTemplate(defaultFtlName);
# d. 准备数据模型
创建一个数据模型,包含需要在模板中使用的所有变量。通常,数据模型是一个Java Map
对象:
import java.util.HashMap;
import java.util.Map;
Map<String, Object> root = new HashMap<>();
root.put("key", "value");
# e. 渲染模板输出
使用模板实例和数据模型渲染输出。将输出写入一个java.io.Writer
对象,例如java.io.StringWriter
或java.io.FileWriter
。
import java.io.StringWriter;
StringWriter out = new StringWriter();
template.process(dataModel, out);
System.out.println(out.toString());
# f. 函数与静态方法导入
在Freemarker的dataModel
中,您可以将函数(实现了TemplateMethodModelEx
接口的对象)和工具类的静态方法添加到数据模型中。以下是如何添加函数和静态方法的示例:
# 添加函数
- 创建一个实现
TemplateMethodModelEx
接口的类。实现exec
方法以定义函数的逻辑。
import freemarker.template.TemplateMethodModelEx;
import freemarker.template.TemplateModelException;
public class CustomFunction implements TemplateMethodModelEx {
@Override
public Object exec(List arguments) throws TemplateModelException {
// 实现函数逻辑,例如计算两个数的和
int a = ((SimpleNumber) arguments.get(0)).getAsNumber().intValue();
int b = ((SimpleNumber) arguments.get(1)).getAsNumber().intValue();
return a + b;
}
}
- 将函数实例添加到数据模型中。
Map<String, Object> dataModel = new HashMap<>();
dataModel.put("customFunction", new CustomFunction());
- 在模板中使用该函数。
<#assign result = customFunction(5, 3)>
Result: ${result}
# 导入工具类的静态方法
- 创建一个工具类,包含一个或多个静态方法。
public class Utils {
public static String toUpperCase(String input) {
return input.toUpperCase();
}
public static String toLowerCase(String input) {
return input.toLowerCase();
}
}
- 使用
freemarker.ext.beans.BeansWrapper
将工具类添加到数据模型中。
import freemarker.ext.beans.BeansWrapper;
BeansWrapper beansWrapper = BeansWrapper.getInstance();
TemplateModel statics = beansWrapper.getStaticModels();
dataModel.put("Utils", statics.get("your.package.name.Utils"));
请注意替换your.package.name
为实际工具类所在的包名。
- 在模板中使用静态方法。
Original: ${text}
Upper case: ${Utils.toUpperCase(text)}
Lower case: ${Utils.toLowerCase(text)}
现在,您已经将函数和工具类的静态方法添加到数据模型中,可以在Freemarker模板中直接调用它们。
# 3. Spring Boot集成
在Spring Boot项目中集成Freemarker,您可以在Web控制器中使用模板,并通过模板引擎自动渲染视图。以下是详细的使用方法:
- 首先,确保您已将
spring-boot-starter-freemarker
依赖添加到项目中。
Maven项目:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
Gradle项目:
implementation 'org.springframework.boot:spring-boot-starter-freemarker'
- 在
src/main/resources
目录下创建一个templates
文件夹,将Freemarker模板文件(.ftl
)放入该文件夹。例如,创建一个名为example.ftl
的模板文件:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>${title}</title>
</head>
<body>
<h1>Welcome, ${name}!</h1>
</body>
</html>
- 在Spring Boot应用中创建一个Web控制器。在控制器方法中,返回一个包含视图名称(即模板文件名,不包括后缀)的
ModelAndView
对象。您还可以将数据添加到Model
对象中,以便在模板中使用。
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.servlet.ModelAndView;
@Controller
public class ExampleController {
@GetMapping("/example")
public ModelAndView example(Model model) {
model.addAttribute("title", "Freemarker Example");
model.addAttribute("name", "John Doe");
return new ModelAndView("example"); // 对应 example.ftl 模板
}
}
- 运行Spring Boot应用并访问
/example
路径,您将看到Freemarker模板渲染的视图。
# a. 乱码问题
JSP中的乱码问题一般处在<@page>
头部,而在FreeMarker中为避免乱码,需要统一下配置:
#application.properties配置文件中添加
spring.freemarker.settings.defaultEncoding=UTF-8
spring.freemarker.charset=UTF-8
spring.freemarker.charset=UTF-8
# b. JSP模板转移到Freemarker模板
在JSP中可以方便的获取request和application,还可以使用JSTL标签。
在Spring Boot中,提供了平稳的过渡。我们看FreeMarkerView
中的方法:
protected SimpleHash buildTemplateModel(Map<String, Object> model, HttpServletRequest request,
HttpServletResponse response) {
AllHttpScopesHashModel fmModel = new AllHttpScopesHashModel(getObjectWrapper(), getServletContext(), request);
fmModel.put(FreemarkerServlet.KEY_JSP_TAGLIBS, this.taglibFactory);
fmModel.put(FreemarkerServlet.KEY_APPLICATION, this.servletContextHashModel);
fmModel.put(FreemarkerServlet.KEY_SESSION, buildSessionModel(request, response));
fmModel.put(FreemarkerServlet.KEY_REQUEST, new HttpRequestHashModel(request, response, getObjectWrapper()));
fmModel.put(FreemarkerServlet.KEY_REQUEST_PARAMETERS, new HttpRequestParametersHashModel(request));
fmModel.putAll(model);
return fmModel;
}
这个方法负责创建一个包含请求和响应相关信息的SimpleHash对象,供Freemarker模板引擎使用。
比如,你可以使用JSP标签库。但是需要注意的是,Freemarker与JSP之间存在差异,因此并非所有的JSP标签都能在Freemarker模板中正常工作。如果可能的话,使用Freemarker的内置指令和宏通常是更好的选择。然而,对于某些特定的JSP标签,您仍然可以在Freemarker模板中使用它们。以下是一个使用JSP标签库的示例:
- 首先,在
src/main/webapp/WEB-INF
目录下创建一个名为freemarker-tags.tld
的文件,用于定义自定义的JSP标签。
<?xml version="1.0" encoding="UTF-8"?>
<taglib xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-jsptaglibrary_2_1.xsd"
version="2.1">
<tlib-version>1.0</tlib-version>
<short-name>custom</short-name>
<uri>http://www.example.com/tags</uri>
<tag>
<name>hello</name>
<tag-class>com.example.tags.HelloTag</tag-class>
<body-content>empty</body-content>
<attribute>
<name>name</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
<type>java.lang.String</type>
</attribute>
</tag>
</taglib>
- 创建一个自定义的JSP标签类,例如
com.example.tags.HelloTag
:
package com.example.tags;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.SimpleTagSupport;
import java.io.IOException;
public class HelloTag extends SimpleTagSupport {
private String name;
public void setName(String name) {
this.name = name;
}
@Override
public void doTag() throws JspException, IOException {
getJspContext().getOut().write("Hello, " + (name != null ? name : "World") + "!");
}
}
- 在Freemarker模板中引入并使用JSP标签库。例如,在
example.ftl
模板文件中:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JSP Taglib Example</title>
</head>
<body>
<#assign custom=JspTaglibs["http://www.example.com/tags"]>
<@custom.hello name="Freemarker"/>
</body>
</html>
在这个示例中,我们引入了自定义的JSP标签库,并使用hello
标签输出一条问候信息。
也可以直接模板中访问请求属性和请求头信息:
- 在控制器方法中添加请求属性和请求头信息:
@GetMapping("/example")
public ModelAndView example(Model model, HttpServletRequest request, HttpServletResponse response) {
// 添加请求属性
request.setAttribute("attribute", "Request Attribute Value");
// 添加请求头信息
response.addHeader("custom-header", "Custom Header Value");
return new ModelAndView("example");
}
- 在
example.ftl
模板文件中访问请求属性和请求头信息:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>HttpRequestHashModel Example</title>
</head>
<body>
<p>Request Attribute: ${Request.attribute["attribute"]}</p>
<p>Custom Header: ${Request.header["custom-header"]}</p>
</body>
</html>
在这个示例中,我们在控制器方法中添加了一个请求属性和一个请求头信息。然后在模板中使用Request.attribute
和Request.header
访问它们。
请注意,HttpRequestHashModel
实例已经通过FreeMarkerView
的buildTemplateModel
方法添加到模型中,因此我们可以直接在模板中访问Request
对象。
如果你还是觉得麻烦,想要直接使用request中的属性,可以设置:
spring.freemarker.expose-request-attributes=true
spring.freemarker.expose-session-attributes=true
这将会把session和request中的属性复制到Freemarker的dataModel中。
如果你的dataModel和request中的取值一致,会出现冲突。可以设置:
spring.freemarker.allow-request-override=true
spring.freemarker.allow-session-override=true
这将允许request中的属性覆盖原有root中的属性。
# Freemarker模板最佳实践
# 1. 模板组织与管理
- 目录结构:将模板文件组织在一个统一的目录下,根据功能模块或页面类型创建子目录进行分类。
- 模板命名:使用有意义的名称,遵循一致的命名规范,如
kebab-case
或snake_case
,便于识别和维护。 - 模块化:将公共部分提取为独立的模板文件,通过
<#include>
或<#import>
指令复用,避免重复代码。 - 宏与函数:使用宏和自定义函数封装复杂的逻辑或重复的代码块,提高代码可读性和可维护性。
# 2. 代码规范与风格
- 缩进:使用统一的缩进风格,推荐使用2或4个空格进行缩进。
- 注释:在复杂的逻辑或不易理解的代码处添加注释,增加可读性。
- 变量命名:使用有意义的变量名,遵循一致的命名规范,如
camelCase
。 - 指令风格:在指令标签内使用空格分隔关键字、变量和表达式,提高可读性。
- 代码长度:控制每行代码的长度,避免过长的代码,推荐每行不超过80个字符。
# 3. 性能优化
- 缓存:为模板配置合适的缓存策略,减少模板解析和渲染的开销。
- 压缩输出:在输出HTML时移除不必要的空格、换行符和注释,减小输出文件的大小,提高传输速度。
- 减少循环和递归:避免过多的循环和递归操作,以减轻服务器的计算负担。
- 预处理数据:尽量在服务器端预处理数据,减少模板中的计算逻辑,提高渲染速度。
- 延迟加载:对于大型的数据集或需要异步获取的数据,使用延迟加载策略,减少页面加载时间。
# a. 缓存的使用
在 FreeMarker 中,可以通过配置缓存策略来减少模板解析和渲染的开销,提高性能和响应速度。
以下是一个示例,演示如何将模板缓存到文件系统中。首先,我们需要创建一个用于存储缓存文件的目录,例如 ./cache
:
mkdir cache
然后,在代码中使用以下代码配置缓存策略:
Configuration cfg = new Configuration(new Version("2.3.31"));
cfg.setCacheStorage(new FileCacheStorage(new File("./cache")));
FileTemplateLoader templateLoader = new FileTemplateLoader(new File("./templates"));
cfg.setTemplateLoader(templateLoader);
FileCacheStorage
是一个文件系统缓存存储器,它可以将缓存存储到指定的目录中。在这个示例中,我们将缓存文件存储在 ./cache
目录中,并通过 setCacheStorage
方法将缓存存储器配置到模板加载器中。这样,模板解析和渲染时就会先从缓存中查找模板文件,如果缓存中没有该模板文件,则从文件系统中加载模板文件,并将解析结果存储到缓存中,以便下次使用。
通过这种方式,可以有效地减少模板解析和渲染的开销,提高应用程序的性能和响应速度。当模板文件发生变化时,缓存会自动更新,以保证缓存的一致性和正确性。
缓存文件的格式通常是二进制格式,包含了模板的解析结果、模板的名称和模板的时间戳等信息。缓存文件的名称通常与模板文件的名称和时间戳相关,以保证缓存文件的唯一性和正确性。
FreeMarker 支持多种缓存存储器,包括 ConcurrentMapCacheStorage
、SoftCacheStorage
、MruCacheStorage
、FreemarkerCacheStorage
等等。
FreeMarker 支持多种缓存存储器,它提供了几种不同的 CacheStorage
实现,以满足不同的缓存需求。以下是一些主要的 CacheStorage
实现:
SoftCacheStorage
:此缓存存储使用软引用 (soft references) 实现,这意味着缓存的对象只有在 JVM 内存不足时才会被回收。这是 FreeMarker 的默认缓存存储实现,适用于大多数情况。MruCacheStorage
:最近最少使用(Most Recently Used)策略的缓存存储实现。当缓存达到最大容量时,将删除最近最少使用的条目。这种缓存存储在内存有限的情况下表现良好,但请注意,这不是线程安全的实现。StrongCacheStorage
:此缓存存储使用强引用 (strong references) 实现,这意味着缓存的对象不会被 JVM 回收,除非显式地从缓存中删除。这种缓存存储在内存充足的情况下可以提供更好的性能,但可能导致内存泄漏,因为缓存的对象不会被自动回收。NullCacheStorage
:此缓存存储实际上不缓存任何对象。每次请求模板时,都会重新解析模板文件。这种缓存存储可能在开发环境中有用,因为它允许立即查看模板文件的更改,而无需清除缓存。然而,在生产环境中,它会导致较差的性能,因为模板解析是一个相对耗时的操作。
如果你不设置,那么默认使用SoftCacheStorage
。
# 4. 调试与错误处理
- 错误提示:为模板配置友好的错误提示,便于定位问题和进行调试。
- 异常处理:在适当的地方捕获和处理异常,避免程序因异常而中断。
- 日志记录:记录关键操作和异常信息,便于分析问题和监控性能。
- 单元测试:编写单元测试用例,确保模板渲染的正确性和稳定性,及时发现和修复问题。
- 调试工具:使用Freemarker提供的调试工具,如
freemarker.ext.debug
包下的DebugBreak
类,来进行模板调试。在模板中插入${DebugBreak()}
,当程序运行到此处的时候会暂停,进入调试模式。
# 总结
在模板引擎领域,Freemarker并不是一枝独秀,他的竞争对手还有Thymeleaf、JSP、Beetl、Envoy等,这些模板引擎也都各具特色。
Freemarker的主要优势在于简单易学、功能强大、良好的扩展性、全面的支持性(比如常见IDE对于Freemarker的支持)。具体选择哪个模板引擎取决于项目需求、性能要求和团队熟悉程度。
不过模板引擎领域已经非常成熟了,相信你掌握了Freemarker之后,对于其他的模板引擎也会快速上手的。
祝你变得更强!