全面探索 FreeMarker 模版引擎的扩展性
FreeMarker 模版引擎簡介
FreeMarker 是一個采用 Java 開發(fā)的模版引擎,是一個基于模版生成文本的通用工具。 FreeMarker 被設計用來生成 HTML Web 頁面,特別是基于 MVC 模式的應用程序。雖然 FreeMarker 具有一些編程的能力,但通常由 Java 程序準備要顯示的數(shù)據(jù),由 FreeMarker 生成頁面,并通過模板顯示準備的數(shù)據(jù)(如下圖)。
圖 1. FreeMarker 工作原理
點擊查看大圖
FreeMarker 非常簡單,只需要一個 Freemarker.jar 文件(無需任何配置文件)即可包含所有的功能。但 FreeMarker 的功能卻是非常的強大,相比較另外一個非常著名的 Java 模版引擎 —— Velocity 來說,FreeMarker 的功能讓您驚嘆,但其學習的曲線也較 Velocity 要長很多。
本文主要介紹如何利用 FreeMarker 強大的可擴展性來輸出各種文本信息,這不是 FreeMarker 的入門學習材料,如果您尚未對 FreeMarker 有所了解,或者還沒有使用過 FreeMarker 的話,那不妨先上手后再來閱讀本文。
FreeMarker 主要提供了如下幾個方面的擴展性功能:
FreeMarker 自定義宏
FreeMarker 和 Velocity 都提供可自定義宏的功能,但 FreeMarker 的宏功能更加強大,包括允許通過名稱和參數(shù)的位置進行參數(shù)傳遞;允許設置參數(shù)的默認值;支持宏的嵌套;宏可以先使用再聲明;支持命名空間等。
下面我們針對這些功能給出一個簡單但是完整的演示例子,先看看代碼:
清單 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 頁面的框架。它具有三個參數(shù)分別是 title 、charset 和 lang ,其中 charset 和 lang 分別指定了默認的值。
再來看看如何調用該宏:
清單 2. 調用宏
<#include "html.ftl"> <@html title="FreeMarker 宏測試 "> 歡迎使用 FreeMarker 模版引擎</@html>在 FreeMarker 中,用戶自定義的宏必須以 @ 開頭來調用,并傳入頁面標題 title 的參數(shù)。而 <@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 這個項目來實現(xiàn)。另外對于一些需要實現(xiàn)更復雜邏輯的宏,還可以通過 Java 類來進行定義。 FreeMarker 提供了一個 TemplateDirectiveModel 接口,通過實現(xiàn)該接口可以實現(xiàn)自定義宏的功能,這樣可以更好的跟應用邏輯進行集成,不過需要注意的是暫不支持通過參數(shù)的位置來調用宏,調用時必須指定參數(shù)名,該問題將在 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 自定義函數(shù)
與宏不同,宏一般用來執(zhí)行某個過程,而函數(shù)可以定義返回值,例如對一組數(shù)據(jù)求和、平均值、最大值、最小值等等運算。 FreeMarker 的函數(shù)支持可變個數(shù)的參數(shù)。例如下面定義了一個求平均值的函數(shù):
清單 6. 求平均值的函數(shù)例子
<#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>其中函數(shù)名為 avg ,支持可變個數(shù)的參數(shù) nums 。可用下面的代碼來要調用該函數(shù):
${avg(3,5,100,3453)}跟宏相同,FreeMarker 也可以用 Java 來編寫自定義函數(shù)。例如我們用 Java 代碼來生成一個隨機的整數(shù),其代碼如下:
清單 7. 使用 Java 編寫的自定義函數(shù)
/*** 生成一個隨機的整數(shù)* @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)));}}同樣的,需要將該函數(shù)的定義植入 FreeMarker :
cfg.setSharedVariable("rand",newRandomFunction());使用方法:${rand(1000)} 。
FreeMarker 自定義模版文件加載器
模版文件加載器用來告訴 FreeMarker 引擎到什么地方去加載模版文件。 FreeMarker 自帶了三種文件加載器,分別是:文件目錄加載器、類路徑加載器以及 Web 上下文加載器。當在 Web 環(huán)境中使用 FreemarkerServlet 來加載模版文件時,默認使用第三種加載器,并通過 Servlet 的配置 TemplatePath 來指定模版文件所存放的路徑,該路徑是相對于 Web 的根目錄的。
在某種情況下,我們可能會希望把模版文件的源碼進行加密處理,例如我們使用 DES 加密方式將模版源文件加密后進行存儲,然后我們通過自行實現(xiàn)一個加密的模版文件加載器來讀取這些模版文件,解密后交給 FreeMarker 引擎解釋執(zhí)行并得到執(zhí)行的結果。 FreeMarker 為模版文件加載器定義了一個統(tǒng)一的接口 —— TemplateLoader ,該接口有以下四個方法:
| findTemplateSource | 根據(jù)名稱返回指定的模版資源 |
| getLastModified | 返回模版資源最后一次修改的時間 |
| getReader | 返回讀取模版資源的 Reader |
為了簡單起見,我們可以在 FreeMarker 自帶的加載器上進行擴展,重寫 getReader 方法對讀取到的模版文件內容進行解密后生成一個新的 Reader 實例并返回(詳細過程不再敘述)。
FreeMarker 自帶的幾個 TemplateLoader 分別是:
重載模版加載器后通過下面代碼使之生效:
cfg.setTemplateLoader(loader)FreeMarker 緩存處理
FreeMarker 的緩存處理主要用于模版文件的緩存,一般來講,模版文件改動不會很頻繁,在一個流量非常大的網(wǎng)站中,如果頻繁的讀取模版文件對系統(tǒng)的負擔還是很重的,因此 FreeMarker 通過將模版文件的內容進行緩存,來降低模版文件讀取的頻次,降低系統(tǒng)的負載。
當處理某個模版時,FreeMarker 直接從緩存中返回對應的 Template 對象,并有一個默認的機制來保證該模版對象是跟模版文件同步的。如果使用的時候 FreemarkerServlet 時,有一個配置項 template_update_delay 用來指定更新模版文件的間隔時間,相當于多長時間檢測一下是否有必要重新加載模版文件,0 表示每次都重新加載,否則為多少毫秒鐘檢測一下模版是否更改。
FreeMarker 定義了一個統(tǒng)一的緩存處理接口 CacheStorage ,默認的實現(xiàn)是 MruCacheStorage 最近最少使用的緩存策略。一般情況下,很少需要對緩存進行擴展處理。您可以通過下面的代碼指定最大緩存的模版數(shù):
cfg.setCacheStorage(new freemarker.cache.MruCacheStorage(20, 250))其中第一個參數(shù)是最大的強引用對象數(shù),第二個為最大的弱引用對象數(shù)。這兩個值 FreeMarker 默認的是 0 和 Integer.MAX_VALUE,表明模版緩存數(shù)是無限的。
FreeMarker 異常處理
當使用 FreeMarker 做為模版引擎的時候,可能發(fā)生的異常包括:
配置異常:配置異常指的是 FreeMarker 初始化時發(fā)生的異常,例如錯誤的配置導致,該異常時由 FreeMarker 的 API 拋出來的。
模版加載異常:模版加載異常可能是模版不存在或者沒有讀權限,或者是解析模版時發(fā)生錯誤,例如模版語法錯誤等。
模版執(zhí)行異常:模版執(zhí)行異常是指模版已經(jīng)成功的加載但在執(zhí)行過程中由于代碼執(zhí)行錯誤所拋出的異常,這類異常一般都是用戶的代碼導致。
正常情況下,前兩種異常會在開發(fā)過程中就會發(fā)現(xiàn)并得以解決,而第三種異常往往跟實際的運行環(huán)境和數(shù)據(jù)有關,例如由于某些數(shù)據(jù)不存在導致的空指針異常等等。因此第三種異常才是我們真正需要關心以及監(jiān)控的。
為此,FreeMarker 定義了一個統(tǒng)一的異常處理接口 TemplateExceptionHandler 。該接口只有一個方法如下:
void handleTemplateException(TemplateException te,Environment env,java.io.Writer out)通過調用 cfg.setTemplateExceptionHandler 來使用自定義的異常處理方法。下面是一個簡單的異常處理擴展的例子:
清單 8. 異常處理擴展的例子
class MyTemplateExceptionHandler implements TemplateExceptionHandler {public void handleTemplateException(TemplateException te, Environment env, java.io.Writer out) throws TemplateException {try {out.write("[ERROR: " + te.getMessage() + "]");} catch (IOException e) {throw new TemplateException("Failed to print error message. Cause: " + e, env);}}}...cfg.setTemplateExceptionHandler(new MyTemplateExceptionHandler());Eclipse 的 FreeMarker 插件
為了方便編寫 FreeMarker 模版,您可以使用 FreeMarker IDE 這個 Eclipse 插件。該插件具有語法高亮、錯誤提示等功能。雖然該插件還有很多問題,而且已經(jīng)很久沒更新了,但也能很好地使用。
總結
從上面對于 FreeMarker 的可擴展性的介紹來看,FreeMarker 確實是一個功能非常之強大的模版引擎,可以說遠在 Velocity 之上。不過從使用的直觀程度以及上手的時間來看,其復雜度也大大的超過了 Velocity 。當我們在面臨這兩個模版引擎的選擇時,不能只是從功能或者容易上手的角度來決定,更應該根據(jù)業(yè)務本身的需要綜合進行比較。
相關主題
- 查看?FreeMarker?相關信息。
- 查看?Velocity?相關信息。
- 查看 FreeMarker 的?Eclipse 編輯插件?相關信息。
- “編寫自定義的 Velocity 指令”(developerWorks,2009 年 4 月):本文通過一個實際應用例子對 Velocity 的模板語言中的指令系統(tǒng)進行了介紹,并演示了如何通過編寫自定義的指令來擴展 Velocity 的功能。
- Java 技術專區(qū):尋找 Java 編程各方面的技術文章。
from:?https://www.ibm.com/developerworks/cn/java/j-lo-freemarker/index.html
總結
以上是生活随笔為你收集整理的全面探索 FreeMarker 模版引擎的扩展性的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: FreeMarker 快速入门
- 下一篇: java中Freemarker list