FreeMarker 是一个强大的 Java 模板引擎,常用于生成 HTML 网页、电子邮件、配置文件等。字符串拼接是其基础且常用的功能。本指南将帮助您快速掌握 FreeMarker 中的字符串拼接。
核心概念
- 字符串类型 (String):FreeMarker 中的文本数据类型,用单引号
'
或双引号"
包裹。 - 插值 (Interpolation):
${...}
或#{...}
语法,用于在模板中输出变量或表达式的值。 - 连接运算符 (
+
):用于连接两个字符串值。 - 字符串连接 (String Concatenation):将两个或多个字符串组合成一个新字符串的过程。
- 表达式求值:FreeMarker 在渲染模板时会计算表达式(如字符串连接)并输出结果。
操作步骤 (非常详细)
步骤 1: 准备 FreeMarker 环境
- 添加依赖:确保您的项目中包含 FreeMarker 库(如 Maven 项目中添加
freemarker
依赖)。 - 创建 Configuration 对象:这是 FreeMarker 的核心配置类,用于设置模板加载路径、编码等。
Configuration cfg = new Configuration(Configuration.VERSION_2_3_31); // 指定版本 cfg.setDirectoryForTemplateLoading(new File("/path/to/templates")); // 设置模板目录 cfg.setDefaultEncoding("UTF-8"); // 设置默认编码
- 获取 Template 对象:从
Configuration
加载一个具体的模板文件。Template template = cfg.getTemplate("your_template.ftl");
步骤 2: 在模板中定义字符串变量
- 在
.ftl
模板文件中,可以使用assign
指令创建变量。<#assign firstName = "John"> <#assign lastName = "Doe"> <#assign greeting = "Hello"> <#assign separator = " ">
步骤 3: 使用 +
运算符进行字符串拼接
- 基本拼接:使用
+
连接两个字符串变量或字面量。${greeting + ", " + firstName + " " + lastName + "!"} <!-- 输出: Hello, John Doe! -->
- 拼接变量与字面量:
${"The user's name is " + firstName + " " + lastName + "."} <!-- 输出: The user's name is John Doe. -->
- 拼接多个变量:
<#assign part1 = "Welcome to"> <#assign part2 = " our website"> <#assign fullMessage = part1 + part2> ${fullMessage} <!-- 输出: Welcome to our website -->
步骤 4: 在插值中直接拼接
- 可以在
${...}
内部直接进行复杂的拼接操作。<p>User Info: ${"Name: " + user.name + ", Age: " + user.age + ", Email: " + user.email}</p>
步骤 5: 处理非字符串类型
- FreeMarker 通常会自动将非字符串类型(如数字、日期)转换为字符串进行拼接。
<#assign age = 30> <#assign birthYear = 1990> ${"I am " + age + " years old, born in " + birthYear + "."} <!-- 输出: I am 30 years old, born in 1990. -->
- 注意:对于复杂对象,可能需要调用其
toString()
方法或访问特定属性。
步骤 6: 使用内建函数辅助拼接 (可选)
- 虽然
+
是主要方式,但有时结合内建函数更方便。?join
:将序列(如列表)的元素用指定分隔符连接成字符串。<#assign colors = ["red", "green", "blue"]> ${"Colors: " + colors?join(", ")} <!-- 输出: Colors: red, green, blue -->
?c
:强制转换为字符串(常用于数字避免格式化)。<#assign number = 123.45> ${"Raw number: " + number?c} <!-- 避免 locale 格式化 -->
步骤 7: 将拼接结果赋值给新变量
- 使用
assign
将拼接结果存储起来,便于后续多次使用。<#assign fullName = firstName + " " + lastName> <#assign emailSubject = "Welcome, " + fullName> <h1>${emailSubject}</h1> <p>Hello ${fullName}, welcome aboard!</p>
步骤 8: 在 Java 代码中传递数据并渲染
- 创建
Map
或Bean
对象,放入需要的数据。Map<String, Object> dataModel = new HashMap<>(); dataModel.put("firstName", "Alice"); dataModel.put("lastName", "Smith"); dataModel.put("greeting", "Hi"); // ... put other data
- 使用
Template.process()
方法渲染模板。Writer out = new OutputStreamWriter(System.out); template.process(dataModel, out); out.flush();
常见错误
- 忘记引号:将字符串字面量写成
Hello World
而不是"Hello World"
或'Hello World'
,这会被当作变量名,导致Undefined variable
错误。 - 拼写错误:变量名拼写错误,如
${fistName}
(应为firstName
)。 - 类型转换错误:
- 尝试将
null
值与其他字符串拼接,会导致TemplateException
。应先检查或提供默认值:${(maybeNullVar!"") + "suffix"}
。 - 对复杂对象直接拼接,期望其有有意义的
toString()
输出,否则可能得到[Object object]
或报错。
- 尝试将
- 作用域问题:在
assign
之前使用变量,或在宏/函数内部定义的变量在外部不可见。 - 编码问题:模板文件或输出流编码设置不正确,导致中文等非 ASCII 字符乱码。
- 缺少
#
符号:<assign ...>
应为<#assign ...>
。 - 在
+
两侧有空格导致解析问题:虽然通常可以,但在某些复杂表达式中,确保语法清晰。例如,${var + "text"}
是标准的。
注意事项
null
值处理:FreeMarker 对null
值敏感。在拼接前,使用!
操作符提供默认值是良好实践。${(user.middleName!"") + " " + user.lastName} <!-- 如果 middleName 为 null,则用空字符串代替 -->
- 自动类型转换:数字、布尔值等会自动转为字符串,但格式可能受
locale
影响(如数字千分位分隔符)。使用?c
内建函数可获得计算机可读的格式。 - 性能考虑:频繁的
assign
和复杂的表达式可能影响性能。对于非常长的字符串构建,考虑在 Java 层预处理。 - 模板可读性:避免在单个
${}
中进行过长或过于复杂的拼接,影响模板可读性。可拆分成多个assign
语句。 - 转义字符:如果字符串字面量中包含引号,需要使用反斜杠
\
转义,或使用不同类型的引号包裹。${"He said \"Hello\""} <!-- 或 --> ${'He said "Hello"'}
- 空格处理:FreeMarker 通常会保留模板中的空格和换行。使用
<#compress>
指令可以压缩空白。 - 安全性:如果拼接的内容来自用户输入,注意 XSS 攻击风险。在输出 HTML 时,使用
?html
内建函数进行转义:${userInput?html}
。
使用技巧
- 使用
?default
或!
提供安全默认值:${(userName!"Unknown User") + " logged in."}
- 利用
?join
处理列表:比循环拼接更简洁高效。<#assign tags = ["java", "freemarker", "tutorial"]> Tags: ${tags?join(" | ")}
- 使用宏 (Macro) 封装常用拼接逻辑:
<#macro formatFullName first last> ${first?default("") + " " + last?default("")} </#macro> <!-- 使用 --> <@formatFullName first="John" last="Doe"/>
- 结合
?string
内建函数格式化:对日期、数字等进行格式化后再拼接。${"Order date: " + orderDate?string("yyyy-MM-dd")}
- 使用三元运算符简化条件拼接:
${"Status: " + (isActive?then("Active", "Inactive"))}
最佳实践与性能优化
- 在 Java 层预处理复杂逻辑:尽量将复杂的字符串构建、业务逻辑放在 Java 代码中处理,然后将最终结果(或结构化数据)传递给模板。模板应专注于展示逻辑。
- 避免在循环中进行昂贵操作:不要在
<#list>
循环内部进行复杂的字符串拼接或数据库查询(虽然模板中通常不会有查询)。 - 重用
Configuration
:Configuration
对象是线程安全的,应创建一次并重用,而不是每次请求都创建新的。 - 缓存
Template
对象:Configuration
会自动缓存已加载的模板,确保setTemplateUpdateDelay
设置合理(如生产环境设为较大值或-1
禁用检查)。 - 使用
?no_esc
谨慎:?no_esc
可以阻止转义,但仅在确保内容安全时使用,避免 XSS。 - 保持模板简洁:遵循 MVC 模式,让模板保持简单,易于维护。复杂的拼接逻辑应抽象成宏或在数据模型中处理。
- 性能监控:在高并发场景下,监控模板渲染时间。如果字符串拼接成为瓶颈,优先考虑在 Java 层优化。
- 使用正确的引号:选择单引号或双引号以减少转义,提高可读性。
- 利用
#compress
指令:当需要减少输出中的空白字符时使用,但注意它本身也有轻微性能开销。 - 版本兼容性:注意使用的 FreeMarker 版本,不同版本在字符串处理或内建函数上可能有细微差异。
通过遵循以上指南,您可以高效、安全地在 FreeMarker 模板中进行字符串拼接,并构建出健壮、可维护的模板。