杰瑞科技汇

Freemarker如何调用Java方法?

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

Freemarker如何调用Java方法?-图1
(图片来源网络,侵删)
  1. 访问通过数据模型传入的对象 (最常用、最推荐)
  2. 使用内置函数 (Built-ins)
  3. 调用静态方法 (需要配置)
  4. 在模板中定义宏 (类似模板内的函数)

下面我将详细解释每一种方式,并提供代码示例。


核心概念:数据模型

无论哪种调用方式,基础都是数据模型,数据模型是 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 方法)来访问对象的属性。

Freemarker如何调用Java方法?-图2
(图片来源网络,侵删)

语法: ${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

Freemarker如何调用Java方法?-图3
(图片来源网络,侵删)

常用内置函数:

函数 描述 示例
?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 哪些类是“安全的”。

配置步骤:

  1. Configuration 中设置 new_class_resolver: 推荐使用 StaticClassResolver,它会允许调用所有类的静态方法(生产环境不推荐,或使用白名单)。

    // 在创建 Configuration 后进行配置
    cfg.setNewClassResolver(StaticClassResolver.INSTANCE);
  2. 在模板中使用 ?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 组件或简单的逻辑片段。 推荐,用于保持模板的整洁和可维护性。

核心原则:

  1. 数据与视图分离:尽量将复杂的业务逻辑和数据处理放在 Java 代码中完成,只将处理好的、易于展示的数据(如 DTO、VO 对象)通过数据模型传递给模板。
  2. 模板保持简洁:FreeMarker 模板应该专注于“如何展示”(View),而不是“如何计算”(Logic),避免在模板中编写复杂的逻辑判断和循环。
  3. 安全第一:对于用户输入,始终使用 ?html, ?js 等内置函数进行转义,防止 XSS 攻击,对于静态方法调用,务必使用安全的 ClassResolver
分享:
扫描分享到社交APP
上一篇
下一篇