FreeMarker 是一个强大的 Java 模板引擎,广泛用于 Web 开发和代码生成等场景。它支持多种数据结构,包括 MapBeanList,这些结构在模板中被频繁使用。本文将从 核心概念操作步骤常见错误注意事项使用技巧最佳实践性能优化 等多个维度,全面讲解如何在 FreeMarker 模板中使用这些数据结构。


一、核心概念

1. Map(映射)

  • 定义:键值对集合,用于存储任意类型的数据。
  • Java 对应类型java.util.Map
  • 模板中使用:使用点号 . 或方括号 [] 访问值。

2. Bean(Java Bean)

  • 定义:具有 getter/setter 方法的 Java 类。
  • Java 对应类型:普通 Java 类(POJO)
  • 模板中使用:通过属性名访问其字段或方法。

3. List(列表)

  • 定义:有序集合,可重复元素。
  • Java 对应类型java.util.List
  • 模板中使用:通过索引访问元素,或使用 #list 指令遍历。

二、操作步骤(详细)

1. 准备 Java 数据模型

// 示例数据模型
Map<String, Object> dataModel = new HashMap<>();

// 1. 添加 Map
Map<String, String> userMap = new HashMap<>();
userMap.put("name", "Alice");
userMap.put("email", "alice@example.com");
dataModel.put("user", userMap);

// 2. 添加 Bean
class User {
    private String name;
    private int age;
    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public String getName() { return name; }
    public int getAge() { return age; }
}
dataModel.put("beanUser", new User("Bob", 25));

// 3. 添加 List
List<String> fruits = Arrays.asList("Apple", "Banana", "Orange");
dataModel.put("fruits", fruits);

2. FreeMarker 模板中使用 Map

访问 Map 中的值:

<#-- 使用点号访问 -->
<p>User Name: ${user.name}</p>

<#-- 使用方括号访问 -->
<p>User Email: ${user["email"]}</p>

遍历 Map:

<#list user?keys as key>
  Key: ${key}, Value: ${user[key]}
</#list>

3. FreeMarker 模板中使用 Bean

访问 Bean 属性:

<#-- 自动调用 getter 方法 -->
<p>Bean User Name: ${beanUser.name}</p>
<p>Bean User Age: ${beanUser.age}</p>

注意:Bean 方法调用(需开启方法调用):

<#-- 需要配置 environment.allowMethodExposure = true -->
<p>Bean Method Call: ${beanUser.getName()}</p>

4. FreeMarker 模板中使用 List

访问 List 元素:

<#-- 使用索引访问 -->
<p>Fruit 0: ${fruits[0]}</p>

遍历 List:

<#list fruits as fruit>
  <p>${fruit}</p>
</#list>

获取 List 长度:

<p>Total Fruits: ${fruits?size}</p>

三、常见错误与注意事项

1. 空指针异常(NullPointerException)

  • 原因:未判断对象是否为 null。
  • 解决:使用 ?? 运算符判断是否存在。
${user.name!}  <#-- 如果 user 或 name 为 null,则不输出 -->

2. 类型转换错误

  • 原因:模板期望的数据类型与实际传入的不一致。
  • 解决:确保传入的数据类型匹配,使用 ?string?number 等转换函数。
${user.age?number}

3. 方法调用失败

  • 原因:未启用方法调用支持。
  • 解决:在配置中开启 allow_method_exposure
Configuration cfg = new Configuration(Configuration.VERSION_2_3_31);
cfg.setAllowMethodExposure(true);

4. Map 遍历顺序问题

  • 原因:Java 中的 HashMap 不保证顺序。
  • 解决:使用 LinkedHashMap 保证顺序。

四、使用技巧

1. 嵌套结构访问

<#-- 嵌套 Map -->
${users[0].address.city}

<#-- 嵌套 Bean -->
${user.address.city}

2. 使用 default

${user.nickname!"Default Nickname"}

3. 条件判断

<#if user??>
  <p>User exists.</p>
</#if>

4. 使用 ?keys?values

<#list user?keys as key>
  Key: ${key}, Value: ${user[key]}
</#list>

五、最佳实践

1. 数据模型扁平化

  • 避免深层嵌套结构,提高可读性和性能。
  • 使用 Map 或 DTO 类封装数据。

2. 模板与数据分离

  • 模板只负责展示,数据逻辑在 Java 层处理。

3. 使用强类型 Bean

  • 使用 Java Bean 而非 Map,提高类型安全性与可维护性。

4. 缓存模板

  • FreeMarker 支持模板缓存,避免重复解析。
cfg.setTemplateCacheSize(100);

六、性能优化

1. 启用缓存

  • FreeMarker 默认启用模板缓存,但可配置缓存策略。
cfg.setTemplateUpdateDelay(3600);  // 每小时检查一次模板更新

2. 使用 #assign#local 减少重复计算

<#assign total = items?size>

3. 避免在模板中执行复杂逻辑

  • 模板应专注于展示,复杂逻辑应在 Java 层处理。

4. 避免频繁调用方法

  • 方法调用会降低性能,建议将结果预计算后传入模板。

七、总结

类型 Java 对应 模板访问方式 推荐用途
Map Map map.keymap["key"] 简单键值对数据
Bean Java 类 bean.property 结构化对象数据
List List list[index]#list 有序数据集合

附录:FreeMarker 数据结构对比表

特性 Map Bean List
数据结构 键值对 对象属性 有序集合
是否可遍历 是(通过 ?keys 是(#list
是否支持方法调用 是(需开启)
是否支持嵌套
性能 中等
推荐场景 动态属性、配置数据 强类型业务对象 列表展示

八、参考示例代码(Java + FTL)

Java 代码

Map<String, Object> model = new HashMap<>();

// Map
Map<String, String> userMap = new HashMap<>();
userMap.put("name", "Alice");
model.put("user", userMap);

// Bean
model.put("beanUser", new User("Bob", 25));

// List
model.put("fruits", Arrays.asList("Apple", "Banana", "Orange"));

FTL 模板

<h2>Map 示例</h2>
<p>Name: ${user.name}</p>
<#list user?keys as key>
  <p>${key}: ${user[key]}</p>
</#list>

<h2>Bean 示例</h2>
<p>Name: ${beanUser.name}, Age: ${beanUser.age}</p>

<h2>List 示例</h2>
<#list fruits as fruit>
  <p>${fruit}</p>
</#list>

九、结语

掌握 FreeMarker 中 Map、Bean、List 的使用,是构建高效、可维护模板系统的关键。通过合理使用这些数据结构,并结合最佳实践和性能优化策略,可以显著提升模板渲染效率和代码可读性。在实际开发中,建议优先使用 Bean 和 List,避免过度依赖 Map 和模板中的复杂逻辑。