适用版本:FreeMarker 2.3.x(主流稳定)
目标:快速定位模板语法错误、变量未定义、类型不匹配、渲染异常等问题


一、核心概念

1. FreeMarker 调试的本质

FreeMarker 是一个 模板引擎,其“调试”主要指:

  • 语法解析错误(Parse Time):模板 .ftl 文件语法错误。
  • 执行期异常(Render Time):变量访问、类型转换、逻辑错误。
  • 输出内容不符合预期:变量为空、逻辑分支错误、转义问题。

2. 调试的两个层面

层面 说明
开发期调试 使用 DEBUG_HANDLER、日志、IDE 支持
运行期监控 异常捕获、日志记录、降级处理

3. 关键调试工具

  • TemplateExceptionHandler
  • 日志输出(LogTemplateExceptions
  • IDE 插件(IntelliJ / Eclipse)
  • 模板内置函数(?string?dump
  • #assign 临时变量观察

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

步骤 1:配置 Configuration 启用调试模式

import freemarker.template.Configuration;
import freemarker.template.TemplateExceptionHandler;
import freemarker.template.Version;

Configuration cfg = new Configuration(Version.VERSION_2_3_32);

// 设置模板目录(开发期建议用文件系统,便于热更新)
cfg.setDirectoryForTemplateLoading(new File("/path/to/templates"));

// 设置编码
cfg.setDefaultEncoding("UTF-8");

// 🔥 关键:设置调试异常处理器
cfg.setTemplateExceptionHandler(TemplateExceptionHandler.HTML_DEBUG_HANDLER);
// 其他选项:
// - DEBUG_HANDLER: 控制台输出详细信息
// - HTML_DEBUG_HANDLER: 输出 HTML 格式错误页(推荐 Web 开发)
// - RETHROW_HANDLER: 抛出异常,由上层捕获(生产推荐)
// - IGNORE_HANDLER: 忽略(不推荐)

// 启用异常日志(开发期开启)
cfg.setLogTemplateExceptions(true); // 输出异常上下文

// 关闭缓存,便于热更新
cfg.setTemplateUpdateDelayMilliseconds(0); // 每次都重新加载模板

开发环境必须开启 HTML_DEBUG_HANDLER + logTemplateExceptions=true


步骤 2:在模板中使用 ?string 查看变量类型与值

<!-- 查看变量原始值 -->
变量 user: ${user?string}

<!-- 查看集合大小 -->
用户列表大小: ${users?size?string}

<!-- 查看是否存在 -->
user 是否存在: ${user?? ?string("true", "false")}

<!-- 查看类型 -->
user 类型: ${user?internalGetWrapperModel()?object?class?name}

?string 是最基础的调试工具,避免直接 ${user} 因 null 报错。


步骤 3:使用 ?dump 输出结构化信息(FreeMarker 2.3.30+)

<!-- 输出变量的 JSON 风格结构 -->
<#assign debugData = {
    "user": user,
    "roles": roles,
    "total": items?size
}>

调试数据:
${debugData?dump}

输出示例:

{
  "user": {
    "name": "张三",
    "email": "zhangsan@example.com"
  },
  "roles": ["admin", "user"],
  "total": 5
}

✅ 适合复杂数据模型结构查看。


步骤 4:使用 #assign 创建临时变量观察中间值

<#-- 模拟数据处理过程 -->
<#assign userName = user.name!'未知用户'>
<#assign displayName = userName?cap_first>

<!-- 调试输出 -->
调试: userName=${userName?string}, displayName=${displayName?string}

<p>欢迎你,${displayName}!</p>

✅ 用于验证表达式计算结果。


步骤 5:启用 #ftl 指令显示模板元信息

<#ftl encoding="UTF-8">
<#-- 此指令可声明模板信息,部分 IDE 可识别 -->
<!-- Template: user-profile.ftl -->
<!-- Author: dev -->
<!-- Version: 1.0 -->

步骤 6:使用 IDE 插件辅助调试(推荐)

IntelliJ IDEA

  1. 安装插件:FreeMarker SupportVelocitySupport(兼容)
  2. 功能:
    • 语法高亮
    • 变量自动补全
    • 模板跳转(Ctrl+Click)
    • 错误实时提示(红色波浪线)
    • OGNL 表达式检查

Eclipse

  1. 安装 JBoss ToolsFreemarker Editor
  2. 支持 .ftl 编辑、语法检查

✅ IDE 插件可提前发现 #if 未闭合、变量名拼写错误等问题。


步骤 7:捕获并打印异常堆栈(Java 层)

try {
    Template template = cfg.getTemplate("user.ftl");
    StringWriter out = new StringWriter();
    template.process(dataModel, out);
    return out.toString();
} catch (TemplateException e) {
    System.err.println("❌ FreeMarker 渲染失败:");
    System.err.println("模板: " + e.getTemplateName());
    System.err.println("行: " + e.getLineNumber() + ", 列: " + e.getColumnNumber());
    System.err.println("消息: " + e.getMessage());
    e.printStackTrace(); // 输出完整堆栈
    throw e;
}

✅ 结合 HTML_DEBUG_HANDLER,可精确定位错误位置。


步骤 8:使用 #attempt / #recover 局部捕获异常(FreeMarker 2.3.30+)

<#attempt>
  <p>用户名: ${user.name}</p>
  <p>积分: ${user.profile.score}</p>
<#recover>
  <p style="color:red">⚠️ 用户数据加载失败: ${.error_message}</p>
  <!-- 可继续渲染其他内容 -->
</#attempt>

✅ 用于局部降级,避免整个页面崩溃。


三、常见错误与调试方法

错误现象 调试方法
页面空白 1. 检查是否抛出异常但被忽略
2. 使用 HTML_DEBUG_HANDLER
3. 检查 dataModel 是否为空
${user.name} 报错 1. 使用 ${user.name?string}
2. 检查 user 是否为 null
3. 使用 user.name!'默认值'
循环报错 <#list items as item> 1. 检查 items 是否为 null 或非集合
2. 使用 <#if items??> 判断
输出被转义(如 < 变成 &lt; 1. 使用 ?no_esc
2. 检查是否误用 ${...} 而非 <#outputformat "HTML">
模板未更新 1. 检查 template_update_delay 是否为 0
2. 确认文件已保存
?size on null 使用 items?size?default(0)<#if items??>

四、注意事项

  1. 生产环境禁用 HTML_DEBUG_HANDLER:防止源码、变量泄露。
  2. 避免在模板中写复杂逻辑:难以调试,应移到 Java 层。
  3. null 值优先使用 !??,而非依赖异常机制。
  4. 日志脱敏:避免记录用户敏感数据。
  5. ?string 仅用于调试,上线前应移除。
  6. IDE 插件不能替代运行时测试:确保在真实环境中验证。

五、使用技巧

1. 自定义调试宏

<#-- 调试宏 -->
<#macro debug varName, varValue>
  <div style="font-family:monospace; background:#f0f0f0; padding:5px; margin:5px 0; border:1px solid #ccc;">
    <strong>DEBUG:</strong> ${varName} = ${varValue?string}
  </div>
</#macro>

<!-- 使用 -->
<@debug "user.name" user.name />
<@debug "items.size" items?size />

2. 启用环境变量观察

当前模板: ${.current_template.name}
当前命名空间: ${.current_namespace}
全局变量: ${.globals?keys?join(", ")}
局部变量: ${.local?keys?join(", ")}

3. 使用 ?interpret 动态执行(谨慎!)

<#assign dynamicCode = "<#list 1..3 as i>${i}</#list>">
${dynamicCode?interpret}

⚠️ 仅用于测试,生产禁用,有安全风险。


4. 配置文件分离调试与生产

# struts.properties 或 freemarker.properties
# 开发环境
struts.freemarker.templateExceptionHandler=html_debug
struts.freemarker.templatesCache=false

# 生产环境
# struts.freemarker.templateExceptionHandler=rethrow
# struts.freemarker.templatesCache=true

六、最佳实践

实践 说明
✅ 开发使用 HTML_DEBUG_HANDLER 可视化错误
✅ 模板中使用 ?string 快速查看值 安全且高效
✅ 使用 #attempt 局部容错 提升健壮性
✅ IDE 安装 FreeMarker 插件 提前发现语法错误
✅ 变量访问前使用 ?? 判断 防 null 异常
✅ 使用 ! 设置默认值 ${name!'N/A'}
✅ 关闭生产环境调试输出 防止信息泄露
✅ 异常捕获并记录日志 便于问题追踪

七、性能优化(调试相关)

优化项 说明
❌ 避免在生产模板中保留 ?string?dump 影响性能
✅ 调试完成后移除调试代码 减少渲染开销
✅ 使用 RETHROW_HANDLER 生产环境 快速失败,便于监控
✅ 异步日志记录 避免阻塞渲染线程

八、调试流程图

模板渲染
     ↓
语法解析 → 错误? → HTML_DEBUG 输出位置
     ↓
执行渲染 → 异常? → 
     ├─ 是 → HTML_DEBUG / 日志 / #recover
     └─ 否 → 继续
     ↓
输出内容 → 不符合预期?
     ↓
→ 使用 ?string / ?dump / #assign 查看中间值
→ 检查数据模型是否正确
→ 检查模板逻辑(if/list)
→ 使用 IDE 验证语法

九、总结:调试检查清单 ✅

项目 是否完成
开发环境使用 HTML_DEBUG_HANDLER
启用 logTemplateExceptions=true
模板缓存关闭(开发期)
使用 ?string 查看变量
使用 ?dump 查看结构
使用 #assign 调试中间值
安装 IDE FreeMarker 插件
捕获 TemplateException 并打印堆栈
使用 #attempt 局部捕获
上线前移除调试代码

一句话总结
FreeMarker 调试的核心是 “可视化错误 + 变量观察 + 分层定位” —— 通过 HTML_DEBUG_HANDLER 快速定位语法错误,结合 ?string?dump#assign 观察运行时数据,利用 IDE 提升开发效率,最终实现高效、精准的问题排查。