FreeMarker 是一个强大的 Java 模板引擎,常用于生成 HTML 网页、电子邮件、配置文件等。字符串拼接是其基础且常用的功能。本指南将帮助您快速掌握 FreeMarker 中的字符串拼接。


核心概念

  1. 字符串类型 (String):FreeMarker 中的文本数据类型,用单引号 ' 或双引号 " 包裹。
  2. 插值 (Interpolation)${...}#{...} 语法,用于在模板中输出变量或表达式的值。
  3. 连接运算符 (+):用于连接两个字符串值。
  4. 字符串连接 (String Concatenation):将两个或多个字符串组合成一个新字符串的过程。
  5. 表达式求值: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 代码中传递数据并渲染

  • 创建 MapBean 对象,放入需要的数据。
    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();
    

常见错误

  1. 忘记引号:将字符串字面量写成 Hello World 而不是 "Hello World"'Hello World',这会被当作变量名,导致 Undefined variable 错误。
  2. 拼写错误:变量名拼写错误,如 ${fistName} (应为 firstName)。
  3. 类型转换错误
    • 尝试将 null 值与其他字符串拼接,会导致 TemplateException。应先检查或提供默认值:${(maybeNullVar!"") + "suffix"}
    • 对复杂对象直接拼接,期望其有有意义的 toString() 输出,否则可能得到 [Object object] 或报错。
  4. 作用域问题:在 assign 之前使用变量,或在宏/函数内部定义的变量在外部不可见。
  5. 编码问题:模板文件或输出流编码设置不正确,导致中文等非 ASCII 字符乱码。
  6. 缺少 # 符号<assign ...> 应为 <#assign ...>
  7. + 两侧有空格导致解析问题:虽然通常可以,但在某些复杂表达式中,确保语法清晰。例如,${var + "text"} 是标准的。

注意事项

  1. null 值处理:FreeMarker 对 null 值敏感。在拼接前,使用 ! 操作符提供默认值是良好实践。
    ${(user.middleName!"") + " " + user.lastName} <!-- 如果 middleName 为 null,则用空字符串代替 -->
    
  2. 自动类型转换:数字、布尔值等会自动转为字符串,但格式可能受 locale 影响(如数字千分位分隔符)。使用 ?c 内建函数可获得计算机可读的格式。
  3. 性能考虑:频繁的 assign 和复杂的表达式可能影响性能。对于非常长的字符串构建,考虑在 Java 层预处理。
  4. 模板可读性:避免在单个 ${} 中进行过长或过于复杂的拼接,影响模板可读性。可拆分成多个 assign 语句。
  5. 转义字符:如果字符串字面量中包含引号,需要使用反斜杠 \ 转义,或使用不同类型的引号包裹。
    ${"He said \"Hello\""} <!-- 或 -->
    ${'He said "Hello"'}
    
  6. 空格处理:FreeMarker 通常会保留模板中的空格和换行。使用 <#compress> 指令可以压缩空白。
  7. 安全性:如果拼接的内容来自用户输入,注意 XSS 攻击风险。在输出 HTML 时,使用 ?html 内建函数进行转义:${userInput?html}

使用技巧

  1. 使用 ?default! 提供安全默认值
    ${(userName!"Unknown User") + " logged in."}
    
  2. 利用 ?join 处理列表:比循环拼接更简洁高效。
    <#assign tags = ["java", "freemarker", "tutorial"]>
    Tags: ${tags?join(" | ")}
    
  3. 使用宏 (Macro) 封装常用拼接逻辑
    <#macro formatFullName first last>
      ${first?default("") + " " + last?default("")}
    </#macro>
    <!-- 使用 -->
    <@formatFullName first="John" last="Doe"/>
    
  4. 结合 ?string 内建函数格式化:对日期、数字等进行格式化后再拼接。
    ${"Order date: " + orderDate?string("yyyy-MM-dd")}
    
  5. 使用三元运算符简化条件拼接
    ${"Status: " + (isActive?then("Active", "Inactive"))}
    

最佳实践与性能优化

  1. 在 Java 层预处理复杂逻辑:尽量将复杂的字符串构建、业务逻辑放在 Java 代码中处理,然后将最终结果(或结构化数据)传递给模板。模板应专注于展示逻辑。
  2. 避免在循环中进行昂贵操作:不要在 <#list> 循环内部进行复杂的字符串拼接或数据库查询(虽然模板中通常不会有查询)。
  3. 重用 ConfigurationConfiguration 对象是线程安全的,应创建一次并重用,而不是每次请求都创建新的。
  4. 缓存 Template 对象Configuration 会自动缓存已加载的模板,确保 setTemplateUpdateDelay 设置合理(如生产环境设为较大值或 -1 禁用检查)。
  5. 使用 ?no_esc 谨慎?no_esc 可以阻止转义,但仅在确保内容安全时使用,避免 XSS。
  6. 保持模板简洁:遵循 MVC 模式,让模板保持简单,易于维护。复杂的拼接逻辑应抽象成宏或在数据模型中处理。
  7. 性能监控:在高并发场景下,监控模板渲染时间。如果字符串拼接成为瓶颈,优先考虑在 Java 层优化。
  8. 使用正确的引号:选择单引号或双引号以减少转义,提高可读性。
  9. 利用 #compress 指令:当需要减少输出中的空白字符时使用,但注意它本身也有轻微性能开销。
  10. 版本兼容性:注意使用的 FreeMarker 版本,不同版本在字符串处理或内建函数上可能有细微差异。

通过遵循以上指南,您可以高效、安全地在 FreeMarker 模板中进行字符串拼接,并构建出健壮、可维护的模板。