错误控制
可能的异常
关于 FreeMarker 发生的异常,可以分为如下几类:
-
当配置 FreeMarker 时发生异常:典型地情况,就是在应用程序初始化时, 仅仅配置了一次 FreeMarker。在这个过程中,异常就会发生, 从 FreeMarker 的API中,我们可以很清楚的看到这一点...
-
当加载和解析模板时发生异常:调用了
Configuration.getTemplate(...)
方法, FreeMarker就要把模板文件加载到内存中然后来解析它 (除非模板已经在Configuration
对象中被 缓存 了)。 在这期间,有两种异常可能发生:-
因模板文件没有找到而发生的
IOException
异常, 或在读取文件时发生其他的I/O问题。比如没有读取文件的权限,或者是磁盘错误。 这些错误的发出者是TemplateLoader
对象,可以将它作为插件设置到Configuration
对象中。(为了正确起见:这里所说的”文件”, 是简化形式。例如,模板也可以存储在关系型数据库的表中。这是TemplateLoader
所要做的事。) -
根据FTL语言的规则,模板文件发生语法错误时会导致
freemarker.core.ParseException
异常。当获得Template
对象 (Configuration.getTemplate(...)
)时, 这种错误就会发生,而不是当执行 (Template.process(...)
)模板的时候。 这种异常是IOException
的一个子类。
-
-
当执行(处理)模板时发生的异常,也就是当调用了
Template.process(...)
方法时会发生的两种异常:-
当试图写入输出对象时发生错误而导致的
IOException
异常。 -
当执行模板时发生的其它问题而导致的
freemarker.template.TemplatException
异常。 比如,一个频繁发生的错误,就是当模板引用一个不存在的变量。 默认情况下,当TemplatException
异常发生时, FreeMarker会用普通文本格式在输出中打印出FTL的错误信息和堆栈跟踪信息, 然后再次抛出TemplatException
异常而中止模板的执行, 就可以捕捉到Template.process(...)
方法抛出的异常了。而这种行为是可以定制的。FreeMarker也会经常写TemplatException
异常的 日志。
-
根据TemplateException来自定义处理方式
TemplateException
异常在模板处理期间的抛出是由
freemarker.template.TemplateExceptionHandler
对象控制的,这个对象可以使用
setTemplateExceptionHandler(...)
方法配置到 Configuration
对象中。
TemplateExceptionHandler
对象只包含一个方法:
void handleTemplateException(TemplateException te, Environment env, Writer out) throws TemplateException;
无论 TemplateException
异常什么时候发生,这个方法都会被调用。
异常处理是传递的 te
参数控制的,
模板处理的运行时(Runtime,译者注)环境可以访问 env
变量,
处理器可以使用 out
变量来打印输出信息。
如果方法抛出异常(通常是重复抛出 te
),那么模板的执行就会中止,
而且 Template.process(...)
方法也会抛出同样的异常。如果 handleTemplateException
对象不抛出异常,那么模板将会继续执行,就好像什么也没有发生过一样,
但是引发异常的语句将会被跳过(后面会详细说)。
当然,控制器仍然可以在输出中打印错误提示信息。
任何一种情况下,当 TemplateExceptionHandler
被调用前,
FreeMarker 将会记录异常日志。
我们用实例来看一下,当错误控制器不抛出异常时, 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.ftl.]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.ftl.] Bar c
FreeMarker 本身带有这些预先编写的错误控制器:
-
TemplateExceptionHandler.DEBUG_HANDLER
: 打印堆栈跟踪信息(包括FTL错误信息和FTL堆栈跟踪信息)和重新抛出的异常。 这是默认的异常控制器(也就是说,在所有新的Configuration
对象中,它是初始化好的)。 -
TemplateExceptionHandler.HTML_DEBUG_HANDLER
: 和DEBUG_HANDLER
相同,但是它可以格式化堆栈跟踪信息, 那么就可以在Web浏览器中来阅读错误信息。 当你在制作HTML页面时,建议使用它而不是DEBUG_HANDLER
。 -
TemplateExceptionHandler.IGNORE_HANDLER
: 简单地压制所有异常(但是要记住,FreeMarker 仍然会写日志)。 它对处理异常没有任何作用,也不会重新抛出异常。 -
TemplateExceptionHandler.RETHROW_HANDLER
: 简单重新抛出所有异常而不会做其它的事情。 这个控制器对Web应用程序(假设你在发生异常之后不想继续执行模板)来说非常好, 因为它在生成的页面发生错误的情况下,给了你很多对Web应用程序的控制权 (因为FreeMarker不向输出中打印任何关于该错误的信息)。 要获得更多在Web应用程序中处理错误的信息,可以 参见FAQ。
在模板中明确地处理错误
尽管它和 FreeMarker 的配置(本章的主题)无关,但是为了说明的完整性, 在这里提及一下,你可以在模板中直接控制错误。 通常这不是一个好习惯(尽量保持模板简单,技术含量不要太高),但有时仍然需要:
-
处理不存在/为空的变量: 模板开发指南/模板/表达式/处理不存在的值
-
在发生障碍的"porlets"中留存下来,还可以扩展参考: 模板语言参考 /指令参考/attempt, recover