全面探索 FreeMarker 模版引擎的扩展性

来源:developerWorks 中国 作者:刘 柄成
  
本文全面介绍了在 Java 语言中功能十分强大的模版引擎 — FreeMarker,以及对 FreeMarker 的可扩展性进行了全面探索。

FreeMarker 模版引擎简介

FreeMarker 是一个采用 Java 开发的模版引擎,是一个基于模版生成文本的通用工具。 FreeMarker 被设计用来生成 HTML Web 页面,特别是基于 MVC 模式的应用程序。虽然 FreeMarker 具有一些编程的能力,但通常由 Java 程序准备要显示的数据,由 FreeMarker 生成页面,并通过模板显示准备的数据(如下图)。


图 1. FreeMarker 工作原理
FreeMarker 工作原理

FreeMarker 非常简单,只需要一个 Freemarker.jar 文件(无需任何配置文件)即可包含所有的功能。但 FreeMarker 的功能却是非常的强大,相比较另外一个非常著名的 Java 模版引擎 —— Velocity 来说,FreeMarker 的功能让您惊叹,但其学习的曲线也较 Velocity 要长很多。

本文主要介绍如何利用 FreeMarker 强大的可扩展性来输出各种文本信息,这不是 FreeMarker 的入门学习材料,如果您尚未对 FreeMarker 有所了解,或者还没有使用过 FreeMarker 的话,那不妨先上手后再来阅读本文。

FreeMarker 主要提供了如下几个方面的扩展性功能:

  1. 自定义宏
  2. 自定义函数
  3. 自定义模版文件加载器
  4. 缓存处理
  5. 异常处理

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());

时间:2009-06-19 17:07 来源:developerWorks 中国 作者:刘 柄成 原文链接

好文,顶一下
(4)
50%
文章真差,踩一下
(4)
50%
------分隔线----------------------------


把开源带在你的身边-精美linux小纪念品
无觅相关文章插件,快速提升流量