一、核心概念
FreeMarker 提供了模板级别的错误处理机制,允许开发者在模板中捕获和处理运行时错误。<#attempt>
和 <#recover>
是 FreeMarker 中用于实现模板内异常捕获和恢复的关键指令。
1. <#attempt>
和 <#recover>
的作用
<#attempt>
:开始一个尝试块,包裹可能抛出异常的模板代码。<#recover>
:定义当<#attempt>
块中发生错误时的恢复逻辑(即异常处理逻辑)。
2. 适用场景
- 渲染动态内容时,某些变量可能缺失或类型错误。
- 调用宏或函数时,参数可能不合法。
- 外部数据源(如数据库)返回异常值。
- 模板中进行复杂计算,可能出现除以零等错误。
🛠️ 二、操作步骤(详细)
✅ 步骤 1:使用 <#attempt>
和 <#recover>
基本结构
<#attempt>
<#-- 可能出错的代码 -->
<#assign value = someUndefinedVariable + 1>
<#recover>
<#-- 出错后的处理逻辑 -->
<p>发生错误:变量未定义</p>
</#recover>
说明:如果
someUndefinedVariable
未定义,模板将跳转到<#recover>
块并输出错误提示。
✅ 步骤 2:捕获特定异常类型
你可以使用 ?is_error
和 ?error_type
来判断错误类型,进行更细粒度的处理。
<#attempt>
<#assign value = 1 / 0>
<#recover>
<#if .error?is_error("Arithmetic")>
<p>算术错误:除以零</p>
<#else>
<p>未知错误:${.error}</p>
</#if>
</#recover>
FreeMarker 内置错误类型包括:
Arithmetic
:算术错误(如除以零)InvalidReference
:引用不存在变量MalformedTemplateName
:模板名格式错误TemplateException
:模板解析错误User
:用户自定义错误
✅ 步骤 3:嵌套使用 <#attempt>
块
你可以嵌套多个 <#attempt>
块来处理不同层级的错误。
<#attempt>
<#attempt>
<#assign x = undefinedVar + 1>
<#recover>
<p>内部错误处理</p>
</#recover>
<#assign y = 1 / 0>
<#recover>
<p>外部错误处理</p>
</#recover>
✅ 步骤 4:抛出自定义错误(<#stop>
)
你可以在模板中使用 <#stop>
主动抛出错误,并在 <#recover>
中捕获。
<#attempt>
<#if user == "admin">
<p>欢迎管理员</p>
<#else>
<#stop "非法用户访问">
</#if>
<#recover>
<p>错误:${.error}</p>
</#recover>
⚠️ 三、常见错误与注意事项
❌ 错误 1:未启用 <#attempt>
就使用 <#recover>
<#recover>
<p>错误处理</p>
</#recover>
解决方法: <#recover>
必须紧跟在 <#attempt>
块之后。
❌ 错误 2:错误处理逻辑中再次抛出错误
<#attempt>
<#assign x = undefinedVar + 1>
<#recover>
<#assign y = undefinedVar2 + 1> <#-- 再次出错 -->
</#recover>
后果: 会触发外部错误处理(如果有嵌套)或模板渲染失败。
解决方法: 在 <#recover>
块中尽量避免再次触发错误。
❌ 错误 3:忽略错误类型判断,导致误处理
<#attempt>
<#assign x = 1 / 0>
<#recover>
<#-- 未判断错误类型 -->
<p>发生错误</p>
</#recover>
建议: 使用 .error?is_error("Arithmetic")
等方式判断错误类型,进行针对性处理。
🧠 四、使用技巧
1. 使用 <#attempt>
包裹宏调用
<#macro renderUser user>
<p>用户名:${user.name}</p>
</#macro>
<#attempt>
<@renderUser user=currentUser />
<#recover>
<p>无法渲染用户信息</p>
</#recover>
2. 在 <#recover>
中输出原始错误信息
<#recover>
<p>错误信息:${.error}</p>
</#recover>
3. 使用 <#attempt>
处理可选内容
<#attempt>
<#include "optionalContent.ftl">
<#recover>
<p>可选内容未找到,已忽略</p>
</#recover>
4. 动态控制是否抛出错误
<#if debugMode>
<#stop "调试模式下强制停止">
</#if>
🏆 五、最佳实践
实践项 | 建议 |
---|---|
统一错误处理模板 | 将错误处理逻辑抽象为宏或公共模板片段 |
避免在 <#recover> 中再次抛错 |
保持恢复逻辑稳定 |
使用 <#attempt> 包裹外部数据依赖 |
如数据库查询结果、用户输入等 |
记录错误日志(Java 层) | FreeMarker 不支持日志记录,需在 Java 层拦截异常 |
优先使用 Java 层异常处理 | 模板层错误处理是最后防线,优先在 Java 逻辑中做验证 |
合理使用 <#stop> 抛出自定义错误 |
增强模板调试和逻辑控制能力 |
🔧 六、性能优化
优化项 | 说明 |
---|---|
避免频繁使用 <#attempt> |
异常处理机制有性能开销,仅用于关键逻辑 |
减少 <#attempt> 块嵌套 |
多层嵌套影响可读性和执行效率 |
避免在 <#recover> 中执行复杂逻辑 |
保持恢复逻辑轻量 |
预先判断变量是否存在 | 如 <#if variable??> 可避免进入 <#attempt> |
模板异常应作为异常情况处理 | 不应将 <#attempt> 用于常规流程控制 |
📚 七、总结
维度 | 内容 |
---|---|
核心指令 | <#attempt> 、<#recover> 、<#stop> |
错误类型 | Arithmetic、InvalidReference、TemplateException、User 等 |
使用场景 | 变量缺失、宏调用、复杂计算、可选内容引入 |
注意事项 | 不能单独使用 <#recover> 、避免在恢复块中再次出错 |
最佳实践 | 统一错误处理、合理使用、避免滥用 |
性能优化 | 避免频繁使用、减少嵌套、避免复杂恢复逻辑 |
📌 附录:常见错误类型示例
错误类型 | 触发条件 | 示例 |
---|---|---|
Arithmetic |
算术错误 | 除以零、非法操作 |
InvalidReference |
变量未定义 | ${undefinedVar} |
MalformedTemplateName |
模板路径错误 | <#include "invalid/path"> |
TemplateException |
模板语法错误 | <#if> 未闭合 |
User |
手动抛出错误 | <#stop "自定义错误"> |
📚 推荐阅读
📌 总结一句话:
<#attempt>
和 <#recover>
是 FreeMarker 中用于模板级异常处理的核心机制,通过合理使用可以增强模板的健壮性与容错能力。但应避免滥用,优先在 Java 层处理异常逻辑,模板层应专注于展示控制。