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