适用版本: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
- 安装插件:FreeMarker Support 或 VelocitySupport(兼容)
- 功能:
- 语法高亮
- 变量自动补全
- 模板跳转(Ctrl+Click)
- 错误实时提示(红色波浪线)
- OGNL 表达式检查
Eclipse
- 安装 JBoss Tools 或 Freemarker Editor
- 支持
.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 是否为 null3. 使用 user.name!'默认值' |
循环报错 <#list items as item> |
1. 检查 items 是否为 null 或非集合2. 使用 <#if items??> 判断 |
输出被转义(如 < 变成 < ) |
1. 使用 ?no_esc 2. 检查是否误用 ${...} 而非 <#outputformat "HTML"> |
模板未更新 | 1. 检查 template_update_delay 是否为 02. 确认文件已保存 |
?size on null |
使用 items?size?default(0) 或 <#if items??> |
四、注意事项
- 生产环境禁用
HTML_DEBUG_HANDLER
:防止源码、变量泄露。 - 避免在模板中写复杂逻辑:难以调试,应移到 Java 层。
null
值优先使用!
和??
,而非依赖异常机制。- 日志脱敏:避免记录用户敏感数据。
?string
仅用于调试,上线前应移除。- 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 提升开发效率,最终实现高效、精准的问题排查。