现在的位置: 首页 > 综合 > 正文

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

2013年10月09日 ⁄ 综合 ⁄ 共 4726字 ⁄ 字号 评论关闭

FreeMarker 是一个采用 Java 开发的模版引擎,是一个基于模版生成文本的通用工具。 FreeMarker 被设计用来生成 HTML Web 页面,特别
是基于 MVC 模式的应用程序。虽然 FreeMarker 具有一些编程的能力,但通常由 Java 程序准备要显示的数据,由 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.ainwt.com</@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 = 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.ainwt.com</@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.ainwt.com
*/
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()); 

抱歉!评论已关闭.