Error handling

Page Contents

可能的 exception

关于 FreeMarker 可能发生的异常可以如下分类:

  • 配置 FreeMarker 时会发生异常:通常,在应用程序初始化时,仅在应用程序中配置一次 FreeMarker。当然,在此期间,可能会发生异常。

  • 加载和解析模板时发生异常:调用Configuration.getTemplate(...)时,FreeMarker 必须将模板加载到内存中并进行解析(除非模板已经在该Configuration对象中为cached)。在此期间,可能会发生以下几种异常:

  • TemplateNotFoundException,因为请求的模板不存在。请注意,这扩展了IOException

    • freemarker.core.ParseException,因为根据 FTL 语言的规则,该模板在语法上不正确。请注意,当您获得Template对象(Configuration.getTemplate(...))时发生此错误,而不是在执行模板(Template.process(...))时发生。 。请注意,这扩展了IOException(旧版)。

    • 任何其他类型的IOException,因为在读取现有模板时发生了错误。例如,您无权读取文件,或者读取模板的连接断开。这些的 Launcher 是TemplateLoader object,它已插入Configuration对象。

  • 执行(处理)模板时(即调用Template.process(...)时)发生异常。可能发生两种异常:

  • IOException,因为尝试写入输出写入器时发生错误。

    • freemarker.template.TemplatException,因为在执行模板时发生了其他问题。例如,经常发生的错误是指数据模型中不存在的变量。默认情况下,发生TemplatException时,FreeMarker 以纯文本格式将 FTL 错误消息和堆栈跟踪信息输出到输出编写器,然后通过重新抛出TemplatException来中止模板的执行,然后您可以在Template.process(...)抛出时捕获它。这种行为可以自定义,实际上应该如此。请参阅推荐的配置here。默认情况下,FreeMarker 也是logs TemplatException -s。

自定义有关 TemplatException -s 的行为

模板处理期间引发的TemplateException -s 由freemarker.template.TemplateExceptionHandler对象处理,该对象通过setTemplateExceptionHandler(...)方法插入到Configuration对象中。这些是 FreeMarker 随附的TemplateExceptionHandler实现:

  • TemplateExceptionHandler.DEBUG_HANDLER:打印堆栈跟踪(包括 FTL 错误消息和 FTL 堆栈跟踪)并重新引发异常。这是默认处理程序,但是,请注意不要在生产环境中使用它,因为它会显示有关系统的技术信息。

  • TemplateExceptionHandler.HTML_DEBUG_HANDLER:与DEBUG_HANDLER相同,但它格式化堆栈跟踪,以便 Web 浏览器可以读取。在生成 HTML 页面时,建议不要在DEBUG_HANDLER上推荐,但仅应将其用于开发,因为它会显示有关系统的技术信息。

  • TemplateExceptionHandler.IGNORE_HANDLER:仅禁止显示所有异常(尽管Configuration.getLogTemplateExceptionstrue时 FreeMarker 仍会记录它们)。它不处理任何事件。它不会重新引发异常。

  • TemplateExceptionHandler.RETHROW_HANDLER:只需重新抛出所有异常即可;它什么也没做。今天应在大多数应用程序中使用它。它不会在错误输出中输出任何有关错误的信息,这使其很安全,并且开发人员仍可以从日志中获取错误详细信息。在模板开发过程中,它不如HTML_DEBUG_HANDLERDEBUG_HANDLER那样方便。有关处理 Web 应用程序请参阅常见问题中的错误的更多信息。

您也可以通过实现包含此方法的接口来编写自定义TemplateExceptionHandler

void handleTemplateException(TemplateException te, Environment env, Writer out)
        throws TemplateException;

每当出现TemplateException时,都会调用此方法。处理的异常在te参数中,模板处理的运行时环境在env参数中,并且处理程序可以使用out参数打印到输出。如果此方法引发异常(通常会重新引发te),则模板处理将被中止,而Template.process(...)将引发相同的异常。如果handleTemplateException没有引发异常,则模板处理将 continue 进行,就好像什么都没有发生一样,但是导致异常的语句将被跳过(请参阅稍后)。当然,处理程序仍可以将错误指示符打印到输出。

让我们通过示例看看在错误处理程序未引发异常时 FreeMarker 如何跳过语句。假设我们正在使用此模板异常处理程序:

class MyTemplateExceptionHandler implements TemplateExceptionHandler {
    public void handleTemplateException(TemplateException te, Environment env, java.io.Writer out)
            throws TemplateException {
        try {
            out.write("[ERROR: " + te.getMessage() + "]");
        } catch (IOException e) {
            throw new TemplateException("Failed to print error message. Cause: " + e, env);
        }
    }
}

...

cfg.setTemplateExceptionHandler(new MyTemplateExceptionHandler());

如果不在 FTL 标签内部(即未包含在<#...><@...>内)的插值中发生错误,则将跳过整个插值。因此,此模板(假设数据模型中缺少badVar):

a${badVar}b

如果我们使用MyTemplateExceptionHandler将打印此内容:

a[ERROR: Expression badVar is undefined on line 1, column 4 in test.ftl.]b

该模板将打印相同的内容(除了列号不同...):

a${"moo" + badVar}b

因为如果内部插值发生错误,整个插值将被跳过。

如果在评估指令调用的参数值时发生错误,或者参数列表存在其他问题,或者在<@exp ...>中评估exp时发生错误,或者exp的值不是用户定义的指令,然后跳过整个指令调用。例如:

a<#if badVar>Foo</#if>b

将打印此:

a[ERROR: Expression badVar is undefined on line 1, column 7 in test.ftlh.]b

请注意,该错误发生在if开始标记(<#if badVar>)中,但是整个指令调用都被跳过。从逻辑上讲,嵌套内容(Foo)被跳过了,因为嵌套内容是由封闭指令(if)处理(打印)的。

输出与此相同(除了列号不同...):

a<#if "foo${badVar}" == "foobar">Foo</#if>b

因为如果在参数评估期间发生任何错误,将跳过整个指令调用。

如果已经开始执行指令后发生错误,则不会跳过指令调用。也就是说,如果嵌套内容中发生错误:

a
<#if true>
  Foo
  ${badVar}
  Bar
</#if>
c

或在宏定义主体中:

a
<@test />
b
<#macro test>
  Foo
  ${badVar}
  Bar
</#macro>

输出将是这样的:

a
  Foo
  [ERROR: Expression badVar is undefined on line 4, column 5 in test.ftlh.]
  Bar
c

TemplateException logging

默认情况下,freemarker.runtime日志类别下的 FreeMarker logs全部TemplateException -s,即使它会从其公共 API 抛出它。由于日志记录已成为 Java 应用程序中的普遍做法,因此现在通常会导致两次 exception 记录,因此建议您在配置 FreeMarker 的cfg.setLogTemplateExceptions(false)(或log_template_exceptions=false)禁用此旧行为。

模板中的显式错误处理

尽管它与 FreeMarker 配置无关(本章的主题),但是出于完整性考虑,这里提到您也可以直接在模板内部处理错误: