一、核心概念

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 层处理异常逻辑,模板层应专注于展示控制。