FreeMarker 是一个强大的 Java 模板引擎,广泛用于 Web 开发和代码生成等场景。它支持多种数据结构,包括 Map
、Bean
和 List
,这些结构在模板中被频繁使用。本文将从 核心概念、操作步骤、常见错误、注意事项、使用技巧、最佳实践 与 性能优化 等多个维度,全面讲解如何在 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.key 或 map["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 和模板中的复杂逻辑。