本文全面介绍了在 Java 语言中功能十分强大的模版引擎 — FreeMarker,以及对 FreeMarker 的可扩展性进行了全面探索。
FreeMarker 模版引擎简介
FreeMarker 是一个采用 Java 开发的模版引擎,是一个基于模版生成文本的通用工具。 FreeMarker 被设计用来生成 HTML Web 页面,特别是基于 MVC 模式的应用程序。虽然 FreeMarker 具有一些编程的能力,但通常由 Java 程序准备要显示的数据,由 FreeMarker 生成页面,并通过模板显示准备的数据(如下图)。
图 1. FreeMarker 工作原理
FreeMarker 非常简单,只需要一个 Freemarker.jar 文件(无需任何配置文件)即可包含所有的功能。但 FreeMarker 的功能却是非常的强大,相比较另外一个非常著名的 Java 模版引擎 —— Velocity 来说,FreeMarker 的功能让您惊叹,但其学习的曲线也较 Velocity 要长很多。
本文主要介绍如何利用 FreeMarker 强大的可扩展性来输出各种文本信息,这不是 FreeMarker 的入门学习材料,如果您尚未对 FreeMarker 有所了解,或者还没有使用过 FreeMarker 的话,那不妨先上手后再来阅读本文。
FreeMarker 主要提供了如下几个方面的扩展性功能:
- 自定义宏
- 自定义函数
- 自定义模版文件加载器
- 缓存处理
- 异常处理
FreeMarker 自定义宏
FreeMarker 和 Velocity 都提供可自定义宏的功能,但 FreeMarker 的宏功能更加强大,包括允许通过名称和参数的位置进行参数传递;允许设置参数的默认值;支持宏的嵌套;宏可以先使用再声明;支持命名空间等。
下面我们针对这些功能给出一个简单但是完整的演示例子,先看看代码:
清单 1. 宏定义文件 ( html.ftl )
<#macro html title charset="utf-8" lang="zh-CN"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=${charset}" /> <meta http-equiv="Content-Language" content="${lang}"/> <title>${title}</title> </head> <body> <#nested> </body> </html> </#macro> |
在这个宏定义文件中,我们声明了一个名为 html 的宏,该宏是为了生成一个 HTML 页面的框架。它具有三个参数分别是 title 、charset 和 lang ,其中 charset 和 lang 分别指定了默认的值。
再来看看如何调用该宏:
清单 2. 调用宏
<#include "html.ftl"> <@html title="FreeMarker 宏测试 "> 欢迎使用 FreeMarker 模版引擎 </@html> |
在 FreeMarker 中,用户自定义的宏必须以 @ 开头来调用,并传入页面标题 title 的参数。而 <@html> 标签中包含的文本“欢迎使用 FreeMarker 模版引擎”将替换宏定义中的 <#nested> 标签。因此这个模版将会生成如下的 HTML 信息:
清单 3. 模版生成结果
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta http-equiv="Content-Language" content="zh-CN"/> <title>FreeMarker 宏测试 </title> </head> <body> 欢迎使用 FreeMarker 模版引擎 </body> </html> |
而 Velocity 本身并不提供嵌套模版的功能,它必须依赖 Velocity-Tools 这个项目来实现。另外对于一些需要实现更复杂逻辑的宏,还可以通过 Java 类来进行定义。 FreeMarker 提供了一个 TemplateDirectiveModel 接口,通过实现该接口可以实现自定义宏的功能,这样可以更好的跟应用逻辑进行集成,不过需要注意的是暂不支持通过参数的位置来调用宏,调用时必须指定参数名,该问题将在 FreeMarker 2.4 中得以解决。下面是一个简单的例子:
清单 4. 自定义宏功能的例子
/** * 将标签中的代码全部转为大写并输出 * @author Winter Lau (javayou@gmail.com) * 使用方法: * <@upper>Welcome to http://www.oschina.net</@upper> */ public class UpperDirective implements TemplateDirectiveModel { public void execute(Environment env, Map params, TemplateModel[] loopVars, TemplateDirectiveBody body) throws TemplateException, IOException { // Check if no parameters were given: if (!params.isEmpty()) { throw new TemplateModelException( "This directive doesn't allow parameters."); } if (loopVars.length != 0) { throw new TemplateModelException( "This directive doesn't allow loop variables."); } // If there is non-empty nested content: if (body != null) { // Executes the nested body. Same as <#nested> in FTL, except // that we use our own writer instead of the current output writer. body.render(new UpperCaseFilterWriter(env.getOut())); } else { throw new RuntimeException("missing body"); } } /** * A {@link Writer} that transforms the character stream to upper case * and forwards it to another {@link Writer}. */ private static class UpperCaseFilterWriter extends Writer { private final Writer out; UpperCaseFilterWriter (Writer out) { this.out = out; } public void write(char[] cbuf, int off, int len) throws IOException { char[] transformedCbuf = new char[len]; for (int i = 0; i < len; i++) { transformedCbuf[i] = Character.toUpperCase(cbuf[i + off]); } out.write(transformedCbuf); } public void flush() throws IOException { out.flush(); } public void close() throws IOException { out.close(); } } } |
接下来我们需要重载 FreemarkerServlet ,植入该指令扩展,代码如下:
清单 5. 重载 FreemarkerServlet
@Override protected Configuration createConfiguration() { Configuration cfg = super.createConfiguration(); cfg.setSharedVariable("upper", new UpperDirective()); return cfg; } |
在页面模版中使用<@upper>Welcome to http://www.oschina.net</@upper>试试吧。
FreeMarker 自定义函数
与宏不同,宏一般用来执行某个过程,而函数可以定义返回值,例如对一组数据求和、平均值、最大值、最小值等等运算。 FreeMarker 的函数支持可变个数的参数。例如下面定义了一个求平均值的函数:
清单 6. 求平均值的函数例子
<#function avg nums...> <#local sum = 0> <#list nums as num> <#local sum = sum + num> </#list> <#if nums?size != 0> <#return sum / nums?size> </#if> </#function> |
其中函数名为 avg ,支持可变个数的参数 nums 。可用下面的代码来要调用该函数:
${avg(3,5,100,3453)} |
跟宏相同,FreeMarker 也可以用 Java 来编写自定义函数。例如我们用 Java 代码来生成一个随机的整数,其代码如下:
清单 7. 使用 Java 编写的自定义函数
/** * 生成一个随机的整数 * @author Winter Lau (javayou@gmail.com) * @url http://www.oschina.net */ public class RandomFunction implements TemplateMethodModel { final static Random rnd_seed = new Random(System.currentTimeMillis()); /* (non-Javadoc) * @see freemarker.template.TemplateMethodModel#exec(java.util.List) */ @SuppressWarnings("unchecked") public Object exec(List args) throws TemplateModelException { return rnd_seed.nextInt(Integer.parseInt((String)args.get(0))); } } |
同样的,需要将该函数的定义植入 FreeMarker :
cfg.setSharedVariable("rand",newRandomFunction());
|