本文全面介紹了在 Java 語言中功能十分強大的模版引擎 — FreeMarker,以及對 FreeMarker 的可擴展性進行了全面探索。
FreeMarker 模版引擎簡介
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 的宏功能更加強大,包括允許通過名稱和參數的位置進行參數傳遞;允許設置參數的默認值;支持宏的嵌套;宏可以先使用再聲明;支持命名空間等。
下面我們針對這些功能給出一個簡單但是完整的演示例子,先看看代碼:
<#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 分別指定了默認的值。
再來看看如何調用該宏:
<#include "html.ftl"> <@html title="FreeMarker 宏測試 "> 歡迎使用 FreeMarker 模版引擎 </@html> |
在 FreeMarker 中,用戶自定義的宏必須以 @ 開頭來調用,並傳入頁面標題 title 的參數。而 <@html> 標籤中包含的文本“歡迎使用 FreeMarker 模版引擎”將替換宏定義中的 <#nested> 標籤。因此這個模版將會生成如下的 HTML 信息:
<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 中得以解決。下面是一個簡單的例子:
/** * 將標籤中的代碼全部轉為大寫並輸出 * @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 ,植入該指令擴展,代碼如下:
@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 的函數支持可變個數的參數。例如下面定義了一個求平均值的函數:
<#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 代碼來生成一個隨機的整數,其代碼如下:
/** * 生成一個隨機的整數 * @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()); |
[火星人 ] 全面探索 FreeMarker 模版引擎的擴展性已經有426次圍觀