轩辕李的博客 轩辕李的博客
首页
  • Java
  • Spring
  • 其他语言
  • 工具
  • HTML&CSS
  • JavaScript
  • 分布式
  • 代码质量管理
  • 基础
  • 操作系统
  • 计算机网络
  • 编程范式
  • 安全
  • 中间件
  • 心得
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

轩辕李

勇猛精进,星辰大海
首页
  • Java
  • Spring
  • 其他语言
  • 工具
  • HTML&CSS
  • JavaScript
  • 分布式
  • 代码质量管理
  • 基础
  • 操作系统
  • 计算机网络
  • 编程范式
  • 安全
  • 中间件
  • 心得
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Java

    • 核心

    • 并发

    • 经验

    • JVM

    • 企业应用

      • Freemarker模板
        • Freemarker模板简介
          • 1. 主要特点与优势
          • 2. 应用场景
        • Freemarker模板的基本语法
          • 1. 模板文件结构
          • 2. 数据模型
          • 3. 注释
          • 4. 变量
        • 表达式
          • 1. 基本表达式
          • a. 变量表达式
          • b. 字符串表达式
          • c. 数字表达式
          • d. 布尔表达式
          • e. 空值(默认值)表达式
          • 2. 算术表达式
          • 3. 比较表达式
          • 4. 逻辑表达式
          • 5. 范围表达式
          • 6. 切片
          • 7. 方法调用
          • 8. 缺省值和缺失值判断
          • 9. 其他
        • Freemarker模板指令
          • 1. 条件指令
          • a. if指令
          • b. elseif和else指令
          • c. switch指令
          • 2. 循环指令
          • a. list指令
          • 3. 包含与宏指令
          • a. include指令
          • b. import指令
          • c. macro指令
          • 4. 变量与函数指令
          • a. assign指令
          • b. local指令
          • c. global指令
          • d. function指令
          • 5. 模板设置指令
          • a. setting指令
          • 6. 输出控制指令
          • a. escape指令
          • b. autoesc指令
          • c. noparse指令
          • d. compress指令
          • e. t/lt/rt指令
          • 7. 调试指令
          • a. stop指令
          • b. attempt指令和recover指令
          • 8. 自定义指令
        • 内置函数
          • 1. 字符串函数
          • 2. 数字函数
          • 3. 日期函数
          • 4. 布尔函数
          • 5. 序列函数
          • 6. 哈希表函数
          • 7. 循环变量函数
          • 8. XML节点函数
          • 9. switch函数
          • 10. 专家函数
        • 特殊变量引用
        • Freemarker模板与Java集成
          • 1. Java环境配置
          • 2. Java代码实现
          • a. 创建Configuration实例
          • b. 设置模板路径
          • c. 生成模板实例
          • d. 准备数据模型
          • e. 渲染模板输出
          • f. 函数与静态方法导入
          • 添加函数
          • 导入工具类的静态方法
          • 3. Spring Boot集成
          • a. 乱码问题
          • b. JSP模板转移到Freemarker模板
        • Freemarker模板最佳实践
          • 1. 模板组织与管理
          • 2. 代码规范与风格
          • 3. 性能优化
          • a. 缓存的使用
          • 4. 调试与错误处理
        • 总结
      • Servlet与JSP指南
      • Java日志系统
      • Java JSON处理
      • Java XML处理
      • Java对象池技术
      • Java程序的单元测试(Junit)
      • Thymeleaf官方文档(中文版)
      • Mockito应用
      • Java中的空安全
  • Spring

  • 其他语言

  • 工具

  • 后端
  • Java
  • 企业应用
轩辕李
2022-09-20
目录

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. 变量表达式

  1. 简单变量
${userName}

在模板中,使用${}包裹变量名以获取变量的值。

  1. 哈希表
${user.address.street}

哈希表中的元素可以通过.操作符访问。

  1. 序列
${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. 算术表达式

  1. 加法
${a + b}
  1. 减法
${a - b}
  1. 乘法
${a * b}
  1. 除法
${a / b}
  1. 取余
${a % b}
  1. 求幂
${a ^ b}

# 3. 比较表达式

  1. 等于
${a == b}
  1. 不等于
${a != b}
  1. 大于
${a > b}
  1. 小于
${a < b}
  1. 大于等于
${a >= b}
  1. 小于等于
${a <= b}

# 4. 逻辑表达式

  1. 与(AND)
${a && b}
  1. 或(OR)
${a || b}
  1. 非(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>

输出:

  &amp;
  ...
  &amp;

# 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参数是一个字符串,可以包含以下字符:

  1. r:要查找的子字符串是一个正则表达式。
  2. i:在匹配时忽略大小写。
  3. m:启用多行模式。^ 和 $ 可以匹配每一行的开头和结尾。
  4. s:启用点号(.)匹配任意字符,包括换行符。
  5. c:允许在正则表达式中使用空格和注释。
  6. 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接口的对象)和工具类的静态方法添加到数据模型中。以下是如何添加函数和静态方法的示例:

# 添加函数
  1. 创建一个实现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;
    }
}
  1. 将函数实例添加到数据模型中。
Map<String, Object> dataModel = new HashMap<>();
dataModel.put("customFunction", new CustomFunction());
  1. 在模板中使用该函数。
<#assign result = customFunction(5, 3)>
Result: ${result}
# 导入工具类的静态方法
  1. 创建一个工具类,包含一个或多个静态方法。
public class Utils {

    public static String toUpperCase(String input) {
        return input.toUpperCase();
    }

    public static String toLowerCase(String input) {
        return input.toLowerCase();
    }
}
  1. 使用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为实际工具类所在的包名。

  1. 在模板中使用静态方法。
Original: ${text}
Upper case: ${Utils.toUpperCase(text)}
Lower case: ${Utils.toLowerCase(text)}

现在,您已经将函数和工具类的静态方法添加到数据模型中,可以在Freemarker模板中直接调用它们。

# 3. Spring Boot集成

在Spring Boot项目中集成Freemarker,您可以在Web控制器中使用模板,并通过模板引擎自动渲染视图。以下是详细的使用方法:

  1. 首先,确保您已将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'
  1. 在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>
  1. 在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 模板
    }
}
  1. 运行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标签库的示例:

  1. 首先,在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>
  1. 创建一个自定义的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") + "!");
    }
}
  1. 在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标签输出一条问候信息。


也可以直接模板中访问请求属性和请求头信息:

  1. 在控制器方法中添加请求属性和请求头信息:
@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");
}
  1. 在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. 模板组织与管理

  1. 目录结构:将模板文件组织在一个统一的目录下,根据功能模块或页面类型创建子目录进行分类。
  2. 模板命名:使用有意义的名称,遵循一致的命名规范,如kebab-case或snake_case,便于识别和维护。
  3. 模块化:将公共部分提取为独立的模板文件,通过<#include>或<#import>指令复用,避免重复代码。
  4. 宏与函数:使用宏和自定义函数封装复杂的逻辑或重复的代码块,提高代码可读性和可维护性。

# 2. 代码规范与风格

  1. 缩进:使用统一的缩进风格,推荐使用2或4个空格进行缩进。
  2. 注释:在复杂的逻辑或不易理解的代码处添加注释,增加可读性。
  3. 变量命名:使用有意义的变量名,遵循一致的命名规范,如camelCase。
  4. 指令风格:在指令标签内使用空格分隔关键字、变量和表达式,提高可读性。
  5. 代码长度:控制每行代码的长度,避免过长的代码,推荐每行不超过80个字符。

# 3. 性能优化

  1. 缓存:为模板配置合适的缓存策略,减少模板解析和渲染的开销。
  2. 压缩输出:在输出HTML时移除不必要的空格、换行符和注释,减小输出文件的大小,提高传输速度。
  3. 减少循环和递归:避免过多的循环和递归操作,以减轻服务器的计算负担。
  4. 预处理数据:尽量在服务器端预处理数据,减少模板中的计算逻辑,提高渲染速度。
  5. 延迟加载:对于大型的数据集或需要异步获取的数据,使用延迟加载策略,减少页面加载时间。

# 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 实现:

  1. SoftCacheStorage:此缓存存储使用软引用 (soft references) 实现,这意味着缓存的对象只有在 JVM 内存不足时才会被回收。这是 FreeMarker 的默认缓存存储实现,适用于大多数情况。

  2. MruCacheStorage:最近最少使用(Most Recently Used)策略的缓存存储实现。当缓存达到最大容量时,将删除最近最少使用的条目。这种缓存存储在内存有限的情况下表现良好,但请注意,这不是线程安全的实现。

  3. StrongCacheStorage:此缓存存储使用强引用 (strong references) 实现,这意味着缓存的对象不会被 JVM 回收,除非显式地从缓存中删除。这种缓存存储在内存充足的情况下可以提供更好的性能,但可能导致内存泄漏,因为缓存的对象不会被自动回收。

  4. NullCacheStorage:此缓存存储实际上不缓存任何对象。每次请求模板时,都会重新解析模板文件。这种缓存存储可能在开发环境中有用,因为它允许立即查看模板文件的更改,而无需清除缓存。然而,在生产环境中,它会导致较差的性能,因为模板解析是一个相对耗时的操作。

如果你不设置,那么默认使用SoftCacheStorage。

# 4. 调试与错误处理

  1. 错误提示:为模板配置友好的错误提示,便于定位问题和进行调试。
  2. 异常处理:在适当的地方捕获和处理异常,避免程序因异常而中断。
  3. 日志记录:记录关键操作和异常信息,便于分析问题和监控性能。
  4. 单元测试:编写单元测试用例,确保模板渲染的正确性和稳定性,及时发现和修复问题。
  5. 调试工具:使用Freemarker提供的调试工具,如freemarker.ext.debug包下的DebugBreak类,来进行模板调试。在模板中插入${DebugBreak()},当程序运行到此处的时候会暂停,进入调试模式。

# 总结

在模板引擎领域,Freemarker并不是一枝独秀,他的竞争对手还有Thymeleaf、JSP、Beetl、Envoy等,这些模板引擎也都各具特色。

Freemarker的主要优势在于简单易学、功能强大、良好的扩展性、全面的支持性(比如常见IDE对于Freemarker的支持)。具体选择哪个模板引擎取决于项目需求、性能要求和团队熟悉程度。

不过模板引擎领域已经非常成熟了,相信你掌握了Freemarker之后,对于其他的模板引擎也会快速上手的。

祝你变得更强!

编辑 (opens new window)
#Freemarker
上次更新: 2023/06/14
从Hotspot到GraalVM
Servlet与JSP指南

← 从Hotspot到GraalVM Servlet与JSP指南→

最近更新
01
Spring Boot版本新特性
09-15
02
Spring框架版本新特性
09-01
03
Spring Boot开发初体验
08-15
更多文章>
Theme by Vdoing | Copyright © 2018-2025 京ICP备2021021832号-2 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式