FreeMarker 提供了多种方式来调用 Java,主要分为以下几类:

- 访问通过数据模型传入的对象 (最常用、最推荐)
- 使用内置函数 (Built-ins)
- 调用静态方法 (需要配置)
- 在模板中定义宏 (类似模板内的函数)
下面我将详细解释每一种方式,并提供代码示例。
核心概念:数据模型
无论哪种调用方式,基础都是数据模型,数据模型是 Java 对象与 FreeMarker 模板之间的桥梁,它是一个树状结构,根对象通常是一个 Map 或一个自定义的 Java 对象。
示例数据模型:
假设我们有一个 User 对象和一个 List<Product> 对象,我们将它们放入一个 Map 中传给模板。
// User.java
public class User {
private String name;
private int age;
// Getters and Setters
public String getName() { return name; }
public int getAge() { return age; }
}
// Product.java
public class Product {
private String name;
private double price;
// Getters and Setters
public String getName() { return name; }
public double getPrice() { return price; }
}
// Java 代码:设置数据模型
Configuration cfg = new Configuration(Configuration.VERSION_2_3_32);
// ... (加载模板等)
Map<String, Object> dataModel = new HashMap<>();
dataModel.put("user", new User("张三", 30));
dataModel.put("products", Arrays.asList(
new Product("笔记本电脑", 5999.99),
new Product("无线鼠标", 99.00)
));
// 将数据模型和模板合并
Template template = cfg.getTemplate("example.ftl");
template.process(dataModel, writer);
访问通过数据模型传入的对象 (最常用)
这是最基本的方式,FreeMarker 会自动通过 JavaBean 规范(getter 方法)来访问对象的属性。

语法:
${object.property}
${object.method()}
示例模板 (example.ftl):
<h1>欢迎, ${user.name}!</h1>
<p>您的年龄是: ${user.age}</p>
<h2>产品列表:</h2>
<ul>
<#list products as product>
<li>
${product.name} - 价格: ${product.price?string.currency}
(${product.getName()?upper_case})
</li>
</#list>
</ul>
说明:
${user.name}:FreeMarker 会调用user对象的getName()方法来获取值。${user.age}:调用getAge()方法。${product.price?string.currency}:这是一个内置函数,将数字格式化为货币字符串。${product.getName()?upper_case}:显式调用getName()方法,并用?upper_case内置函数将结果转为大写。
使用内置函数
内置函数是附加在变量后面的方法,用于对变量值进行转换或格式化,语法是 variable?functionName。

常用内置函数:
| 函数 | 描述 | 示例 |
|---|---|---|
?string |
格式化数字或日期 | ${123.456?string.number} -> 456${123.456?string.currency} -> ¥123.46${123?string("000")} -> 123 |
?date, ?time, ?datetime |
格式化日期 | ${lastUpdated?date("yyyy-MM-dd")} |
?html, ?xml, ?js, ?json |
转义特殊字符,防止XSS攻击 | ${userInput?html} |
?capitalize, ?upper_case, ?lower_case |
大小写转换 | "hello"?upper_case -> HELLO |
?trim |
去除首尾空格 | " text "?trim -> text |
?size |
获取集合、字符串或Map的大小 | ${products.size} |
?has_content |
检查变量是否存在且不为空 | <#if user.name?has_content>...</#if> |
调用静态方法 (需要配置)
FreeMarker 默认不允许直接在模板中调用任意静态方法,因为这是一个潜在的安全风险,你需要明确地告诉 FreeMarker 哪些类是“安全的”。
配置步骤:
-
在
Configuration中设置new_class_resolver: 推荐使用StaticClassResolver,它会允许调用所有类的静态方法(生产环境不推荐,或使用白名单)。// 在创建 Configuration 后进行配置 cfg.setNewClassResolver(StaticClassResolver.INSTANCE);
-
在模板中使用
?new和 调用:?new:用于创建实例并调用实例方法。- 用于直接调用静态方法。
示例模板:
假设我们有一个工具类 StringUtils.java。
// StringUtils.java
public class StringUtils {
public static String reverse(String input) {
return new StringBuilder(input).reverse().toString();
}
}
模板调用:
<h1>调用静态方法</h1>
<p>原始字符串: "freemarker"</p>
<p>反转后: ${"freemarker"?new("com.example.utils.StringUtils")?reverse}</p>
<p>或者直接调用静态方法 (需要配置允许):</p>
<p>反转后: ${@com.example.utils.StringUtils.reverse("freemarker")}</p>
重要安全提示:
在生产环境中,不要轻易使用 StaticClassResolver.INSTANCE,因为它允许模板访问任何 Java 类,包括可能导致系统崩溃的类(如 java.lang.Runtime),更安全的做法是使用 AllowlistClassResolver,并明确列出允许的类。
// 更安全的配置
cfg.setNewClassResolver(new AllowlistClassResolver("com.example.utils"));
在模板中定义宏
宏是定义在模板内部的、可重用的代码块,它不调用 Java,而是在模板层面实现逻辑复用,类似于函数。
语法:
<#macro macroName param1 param2=default_value>
<!-- 宏内容,可以使用参数 -->
Hello, ${param1}! Your score is ${param2}.
</#macro>
<!-- 调用宏 -->
<@macroName "Alice" />
<@macroName "Bob" 95 />
示例模板:
<#-- 定义一个显示产品信息的宏 -->
<#macro displayProduct product>
<div class="product-card">
<h3>${product.name}</h3>
<p>Price: ${product.price?string.currency}</p>
</div>
</#macro>
<#-- 使用宏循环显示产品 -->
<h2>使用宏展示产品:</h2>
<div class="product-list">
<#list products as product>
<@displayProduct product=product />
</#list>
</div>
总结与最佳实践
| 调用方式 | 语法 | 适用场景 | 备注 |
|---|---|---|---|
| 访问对象属性 | ${object.property} |
访问从后端传入的任何数据对象。 | 最常用,是 FreeMarker 的核心。 |
| 内置函数 | variable?function |
对变量值进行格式化、转换、转义等操作。 | 非常常用,功能强大且安全。 |
| 调用静态方法 | @ClassName.method() 或 var?new("Class")?method() |
需要在模板中执行一些通用的、与业务逻辑无关的工具类方法。 | 需谨慎配置,注意安全风险。 |
| 定义宏 | <#macro>...</#macro> 和 <@macro /> |
在模板内部实现代码复用,封装可重用的 UI 组件或简单的逻辑片段。 | 推荐,用于保持模板的整洁和可维护性。 |
核心原则:
- 数据与视图分离:尽量将复杂的业务逻辑和数据处理放在 Java 代码中完成,只将处理好的、易于展示的数据(如 DTO、VO 对象)通过数据模型传递给模板。
- 模板保持简洁:FreeMarker 模板应该专注于“如何展示”(View),而不是“如何计算”(Logic),避免在模板中编写复杂的逻辑判断和循环。
- 安全第一:对于用户输入,始终使用
?html,?js等内置函数进行转义,防止 XSS 攻击,对于静态方法调用,务必使用安全的ClassResolver。
