我对 Velocity 在 Hibernate Tools 中使用的模板中忽略甚至隐藏错误和异常的能力感到不满。
这篇博客讲述了为什么以及如何 FreeMarker 成为我的新兴趣。如果你只想看结果,请前往 TOOLS_FREEMARKER 分支获取代码...继续阅读以获取完整故事。
Velocity 的问题
我开始看到越来越多的论坛帖子和技术问题报告,这些问题是由用户模板中的拼写错误或更糟糕的是 Hibernate Tools 中的错误引起的。如果 Velocity 实际上能够告诉错误发生在模板的哪个位置,许多这些问题都可以在几秒钟内解决;如果暴露了底层异常,单元测试也会失败;但 Velocity 没有做到。
我已经为 Velocity 错误处理添加了我能想到的所有安全预防措施。我创建了自己的 UberSpect 和 EventHandler 实现,不允许调用不存在的方法,并调整了日志设置以使其更具信息性;但它(几乎)没有解决所有可能出现的问题。
即使在 WARN 和 INFO 级别,Velocity 的日志记录也过于频繁,其中一个可能的原因是开发人员知道 Velocity 忽略了它应该失败的情况,因此由于在 Velocity 中没有其他简单的方法实现,他们将它们放入日志中,希望用户意外发现!
最初的选择是 Velocity,因为它是当时最大的玩家,我天真地认为,由于这么多人使用它,如果存在问题,它很快就会被修复。
随着时间的推移,我了解到情况绝对不是这样。
FreeMarker 的优点
上周我决定寻找替代方案,我找到的唯一真正的替代方案是FreeMarker;其他所有方案要么太简单,要么过于复杂,不适合Hibernate Tools的需求。现在,我已经花费了1.5天将现有的Velocity模板转换为FreeMarker,我非常高兴自己做出了这个决定。
以下是FreeMarker之美的几个例子
假设我们有一个以下这样的bean
public class Table { String getName(); }
该bean可以通过以下代码中的table
获取
${table.namee}
在Velocity中,这个错误会被默认忽略,通过自定义EventHandler可以说服它抛出异常,异常显示如下
Caused by: java.lang.IllegalArgumentException: $table.namee is not a valid reference. at org.hibernate.tool.hbm2x.HibernateEventHandler.referenceInsert([=>HibernateEventHandler.java:11]) at org.apache.velocity.app.event.EventCartridge.referenceInsert([=>EventCartridge.java:131]) ... 19 more
没有关于哪个模板以及模板中出错位置的信息。
在FreeMarker中,我得到了以下结果,无需特殊配置和自定义代码
Expression table.namee is undefined on line 15, column 14 in doc/tables/table.ftl. The problematic instruction: ---------- ==> ${table.namee} [on line 15, column 12 in doc/tables/table.ftl] ---------- Java backtrace for programmers: ---------- freemarker.core.InvalidReferenceException: Expression table.namee is undefined on line 15, column 14 in doc/tables/table.ftl. at freemarker.core.TemplateObject.assertNonNull([=>TemplateObject.java:124]) at freemarker.core.Expression.getStringValue([=>Expression.java:118]) at freemarker.core.Expression.getStringValue([=>Expression.java:93]) ...
太棒了!而且更好,第15行,...
在例如Eclipse Console视图中就像一个链接。点击它将带你到table.ftl文件中错误的位置。
如果你引用了不存在的函数,你也会得到类似且精确的错误信息。太棒了!最好的是,如果我真的想让FreeMarker忽略这个错误,我可以通过安装不同的异常处理器来实现。但这是我的选择,不是难以更改的行为。
FreeMarker的内置原语也非常出色,例如<#assign>,它允许我将任何生成的输出存储在变量中以供以后使用。
${pojo.getPackageDeclaration()} // Generated ${date} by Hibernate Tools ${version} <#assign classbody> <#include "PojoTypeDeclaration.ftl"/> { ..more template code.. } </#assign> ${pojo.generateImports()} ${classbody}
这允许我去除需要使用Velocity的神奇第二次遍历的需求。在优秀的FreeMarker 文档中还有更多这样的宝石。
FreeMarker的另一个优点是配置API。让我们比较一下,以下是我们的Velocity设置
engine = new VelocityEngine(); context = new VelocityContext(); EventCartridge ec = new EventCartridge(); ec.addEventHandler(new HibernateEventHandler()); // stricter evaluation ec.attachToContext( context ); Properties p = new Properties(); p.setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM_CLASS, "org.apache.velocity.tools.generic.log.CommonsLogLogSystem"); p.setProperty(CommonsLogLogSystem.LOGSYSTEM_COMMONS_LOG_NAME, "org.hibernate.tool.hbm2x.template"); p.setProperty( RuntimeConstants.UBERSPECT_CLASSNAME, HibernateUberspect.class.getName() ); // stricter evaluation p.setProperty("velocimacro.library",""); // make it shut up about VM_global_library blah p.setProperty("resource.loader", "file, class"); p.setProperty("file.resource.loader.path", directory ); p.setProperty("class.resource.loader.class", ClasspathResourceLoader.class.getName() ); engine.init(p);
以下是相应的FreeMarker配置
engine = new Configuration(); context = new SimpleHash(ObjectWrapper.BEANS_WRAPPER); //Logger.setCategoryPrefix("org.hibernate.tool.hbm2x.template"); // Not really needed since the logging is much more sensible. freeMarkerEngine.setTemplateLoader(new MultiTemplateLoader( new FileTemplateLoader(directory), new ClassTemplateLoader(this.getClass(),"/"));
注意区别?FreeMarker有良好的默认实践,并实际上允许我使用Java代码来配置它;这是一个很酷的概念。
我在FreeMarker中发现的两件“不好”的事情是它的语法基于<#..>在尝试在XML编辑器中显示时效果不佳。这个“问题”在最新版本中被“解决”,因为它也允许[#...]语法。
另一个更大的问题是${}和#{}是不可逃逸的。这种语法在生成ant构建和jsp文件的模板中会冲突。
在Velocity中,它们只是被忽略(唯一有用的是忽略它们的地方)。FreeMarker会抱怨因为这些值未定义。不幸的是,没有简单易行的方法来转义这些字符。以下显示了我找到的允许我输出${..}的方法
${r"${build.dir}"} ${'$í}{build.dir} <#noescape>${build.dir}</noescape>
尽管如此,出色的异常处理、强大的模板语言和配置API使得FreeMarker成为Hibernate Tools的一个更好的选择。
那么现在呢?
Velocity为我服务得很好,可能为许多项目服务得很好;但它并不适合Hibernate Tools。今天,我相信,如果我从一开始就决定使用FreeMarker,我就能为自己和Hibernate世界节省很多麻烦。
请前往TOOLS_FREEMARKER分支看看。那里的代码将在不久的将来合并到主开发中,除非有人提出非常好的理由不这样做;)
说公平点,我必须告诉你,Velocity 1.5目前正在开发中,它似乎解决了这些问题中的某些问题,但并不是完全解决,并且Velocity有一些外部依赖,我不想添加到工具项目中。