FreeMarker 是一个强大的 Java 模板引擎,广泛应用于 Web 开发、代码生成和文档处理等领域。方法调用(Method calls)是 FreeMarker 模板语言中实现动态逻辑和数据处理的核心功能之一。本文将详细介绍 FreeMarker 方法调用的各个方面,帮助您快速掌握关键知识并高效实践。


一、核心概念

  1. 方法(Method)

    • 在 FreeMarker 中,方法是指可以接收参数并返回结果的可调用对象。
    • 方法可以是 Java 对象中的方法(通过 TemplateMethodModelEx 接口暴露给模板),也可以是 FreeMarker 内建函数(Built-in functions)。
    • 语法:method_name(parameters)
  2. 参数(Parameters)

    • 方法可以接受零个或多个参数,参数之间用逗号分隔。
    • 参数可以是变量、字符串、数字、布尔值或其他表达式。
  3. 返回值(Return Value)

    • 方法执行后返回一个值,该值可以是任何 FreeMarker 支持的数据类型(如字符串、数字、布尔值、哈希表、序列等)。
    • 返回值可以直接输出或赋值给变量。
  4. 作用域(Scope)

    • 方法调用的作用域取决于其定义的位置。全局方法在整个模板中可用,而局部方法仅在特定范围内有效。
  5. 内建函数 vs 自定义方法

    • 内建函数:FreeMarker 提供的预定义方法,如 ?upper_case?length 等,用于处理字符串、日期、集合等。
    • 自定义方法:开发者通过 Java 代码实现 TemplateMethodModelEx 接口,并将其注册到数据模型中,供模板调用。

二、操作步骤(非常详细)

步骤 1:准备开发环境

  1. 添加依赖

    • 如果使用 Maven,在 pom.xml 中添加 FreeMarker 依赖:
      <dependency>
          <groupId>org.freemarker</groupId>
          <artifactId>freemarker</artifactId>
          <version>2.3.32</version> <!-- 使用最新稳定版本 -->
      </dependency>
      
  2. 创建 FreeMarker 配置对象

    • 初始化 Configuration 对象,设置模板加载路径、编码等。
      Configuration cfg = new Configuration(Configuration.VERSION_2_3_32);
      cfg.setDirectoryForTemplateLoading(new File("/path/to/templates"));
      cfg.setDefaultEncoding("UTF-8");
      cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
      

步骤 2:定义自定义方法(可选)

  1. 实现 TemplateMethodModelEx 接口

    • 创建一个 Java 类,实现 TemplateMethodModelEx 接口。
      import freemarker.template.TemplateMethodModelEx;
      import freemarker.template.TemplateModelException;
      import java.util.List;
      
      public class CustomMethod implements TemplateMethodModelEx {
          @Override
          public Object exec(List arguments) throws TemplateModelException {
              if (arguments.size() != 1) {
                  throw new TemplateModelException("Expected exactly one argument");
              }
              String input = arguments.get(0).toString();
              return input.toUpperCase(); // 示例:将字符串转为大写
          }
      }
      
  2. 注册自定义方法到数据模型

    • 将自定义方法实例添加到 Map 数据模型中。
      Map<String, Object> dataModel = new HashMap<>();
      dataModel.put("toUpper", new CustomMethod()); // 注册方法名为 "toUpper"
      

步骤 3:编写模板文件

  1. 创建模板文件(.ftl

    • 在模板目录下创建一个 .ftl 文件,例如 example.ftl
  2. 调用内建函数

    • 使用 FreeMarker 内建函数处理数据。
      <#assign name = "john doe">
      <p>Original: ${name}</p>
      <p>Uppercase: ${name?upper_case}</p>
      <p>Length: ${name?length}</p>
      
  3. 调用自定义方法

    • 调用之前注册的自定义方法。
      <#assign text = "hello world">
      <p>Custom Method Result: ${toUpper(text)}</p>
      
  4. 传递多个参数

    • 如果自定义方法支持多个参数,可以在模板中传递。
      <#function add a b>
          <#return a + b>
      </#function>
      
      <p>Sum: ${add(5, 3)}</p>
      

步骤 4:处理方法调用结果

  1. 直接输出

    • 方法调用的结果可以直接使用 ${} 输出。
      ${toUpper("hello")}
      
  2. 赋值给变量

    • 使用 <#assign> 将方法调用结果赋值给变量。
      <#assign result = toUpper("hello")>
      <p>Result: ${result}</p>
      
  3. 条件判断

    • <#if> 语句中使用方法调用结果。
      <#if name?length > 5>
          <p>Name is longer than 5 characters.</p>
      </#if>
      

步骤 5:异常处理

  1. 捕获模板异常

    • 在 Java 代码中捕获 TemplateException,处理模板解析和执行错误。
      try {
          Template template = cfg.getTemplate("example.ftl");
          Writer out = new OutputStreamWriter(System.out);
          template.process(dataModel, out);
          out.flush();
      } catch (TemplateException e) {
          e.printStackTrace();
      }
      
  2. 模板中处理错误

    • 使用 <#attempt><#recover> 块处理潜在的运行时错误。
      <#attempt>
          ${undefinedMethod()}
      <#recover>
          <p>Error occurred: ${.error}</p>
      </#attempt>
      

三、常见错误

  1. 方法未定义

    • 错误信息:The following has evaluated to null or missing...
    • 原因:尝试调用未注册或不存在的方法。
    • 解决方案:确保方法已正确注册到数据模型中。
  2. 参数类型不匹配

    • 错误信息:Expected a string, but this has evaluated to a number...
    • 原因:传递给方法的参数类型与期望的类型不符。
    • 解决方案:检查参数类型,必要时进行类型转换。
  3. 参数数量错误

    • 错误信息:Wrong number of arguments...
    • 原因:传递的参数数量与方法定义的参数数量不一致。
    • 解决方案:核对方法签名,确保参数数量正确。
  4. 空值处理不当

    • 错误信息:Attempted to output null or missing value...
    • 原因:方法返回 null 或变量未定义。
    • 解决方案:使用 ?? 操作符检查变量是否存在,或在方法中处理 null 输入。
  5. 递归调用导致栈溢出

    • 原因:方法内部无限递归调用自身。
    • 解决方案:避免无限递归,设置递归深度限制。

四、注意事项

  1. 命名规范

    • 方法名应具有描述性,遵循驼峰命名法或下划线命名法。
    • 避免使用 FreeMarker 关键字作为方法名。
  2. 性能考虑

    • 避免在循环中频繁调用耗时的方法。
    • 对于复杂计算,尽量在 Java 代码中完成,而不是在模板中。
  3. 安全性

    • 不要在模板中暴露敏感的 Java 方法(如 System.exit())。
    • 对用户输入进行验证,防止注入攻击。
  4. 编码一致性

    • 确保模板文件、数据模型和输出流的编码一致(通常为 UTF-8)。
  5. 版本兼容性

    • 注意 FreeMarker 版本之间的差异,特别是内建函数的行为变化。

五、使用技巧

  1. 利用内建函数简化操作

    • 熟悉常用的内建函数,如 ?trim?replace?date?time 等,减少自定义方法的编写。
  2. 组合方法调用

    • 可以将多个方法调用链式组合,提高代码简洁性。
      ${name?trim?upper_case?replace(" ", "_")}
      
  3. 使用宏(Macro)替代复杂方法

    • 对于复杂的逻辑,可以使用 <#macro> 定义可重用的代码块,而不是编写复杂的自定义方法。
  4. 缓存模板

    • 启用模板缓存,避免重复解析模板文件,提高性能。
      cfg.setTemplateLookupStrategy(new TemplateLookupStrategy() {
          // 自定义缓存策略
      });
      
  5. 调试技巧

    • 使用 <#assign debug = true><#if debug>...</#if> 块进行调试。
    • 利用 FreeMarker 的日志功能记录模板执行过程。

六、最佳实践与性能优化

  1. 分离逻辑与展示

    • 尽量将业务逻辑放在 Java 代码中处理,模板仅负责数据展示。避免在模板中进行复杂的计算或数据库查询。
  2. 预编译模板

    • 在应用启动时预加载和编译模板,减少运行时开销。
  3. 合理使用缓存

    • 启用 FreeMarker 的模板缓存和对象包装缓存,减少重复创建对象的开销。
  4. 避免过度使用自定义方法

    • 只在必要时才创建自定义方法,优先使用内建函数和宏。
  5. 监控与调优

    • 监控模板渲染时间和内存使用情况,识别性能瓶颈。
    • 使用性能分析工具(如 JProfiler)定位热点代码。
  6. 代码复用

    • 将常用的自定义方法封装成库,便于在多个项目中复用。
  7. 文档化

    • 为自定义方法编写清晰的文档,说明其用途、参数和返回值。

通过以上详细的介绍,您应该能够快速掌握 FreeMarker 方法调用的核心知识,并在实际项目中高效应用。记住,实践是最好的学习方式,建议多动手编写模板和 Java 代码,逐步提升技能水平。