1. 程式人生 > >[前端]全面探索 FreeMarker 模版引擎的擴充套件性

[前端]全面探索 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 )

1

2

3

4

5

6

7

8

9

10

11

12

<#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. 呼叫巨集

1

2

3

4

<#include "html.ftl">

<@html title="FreeMarker 巨集測試 ">

歡迎使用 FreeMarker 模版引擎

</@html>

在 FreeMarker 中,使用者自定義的巨集必須以 @ 開頭來呼叫,並傳入頁面標題 title 的引數。而 <@html> 標籤中包含的文字“歡迎使用 FreeMarker 模版引擎”將替換巨集定義中的 <#nested> 標籤。因此這個模版將會生成如下的 HTML 資訊:

清單 3. 模版生成結果

1

2

3

4

5

6

7

8

9

10

<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. 自定義巨集功能的例子

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

/**

* 將標籤中的程式碼全部轉為大寫並輸出

* @author Winter Lau ([email protected])

* 使用方法:

* <@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

1

2

3

4

5

6

@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. 求平均值的函式例子

1

2

3

4

5

6

7

8

9

<#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 。可用下面的程式碼來要呼叫該函式:

1

${avg(3,5,100,3453)}

跟巨集相同,FreeMarker 也可以用 Java 來編寫自定義函式。例如我們用 Java 程式碼來生成一個隨機的整數,其程式碼如下:

清單 7. 使用 Java 編寫的自定義函式

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

/**

* 生成一個隨機的整數

* @author Winter Lau ([email protected])

* @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 :

1

cfg.setSharedVariable("rand",newRandomFunction());

使用方法:${rand(1000)} 。

FreeMarker 自定義模版檔案載入器

模版檔案載入器用來告訴 FreeMarker 引擎到什麼地方去載入模版檔案。 FreeMarker 自帶了三種檔案載入器,分別是:檔案目錄載入器、類路徑載入器以及 Web 上下文載入器。當在 Web 環境中使用 FreemarkerServlet 來載入模版檔案時,預設使用第三種載入器,並通過 Servlet 的配置 TemplatePath 來指定模版檔案所存放的路徑,該路徑是相對於 Web 的根目錄的。

在某種情況下,我們可能會希望把模版檔案的原始碼進行加密處理,例如我們使用 DES 加密方式將模版原始檔加密後進行儲存,然後我們通過自行實現一個加密的模版檔案載入器來讀取這些模版檔案,解密後交給 FreeMarker 引擎解釋執行並得到執行的結果。 FreeMarker 為模版檔案載入器定義了一個統一的介面 —— TemplateLoader ,該介面有以下四個方法:

closeTemplateSource 關閉模版資源
findTemplateSource 根據名稱返回指定的模版資源
getLastModified 返回模版資源最後一次修改的時間
getReader 返回讀取模版資源的 Reader

為了簡單起見,我們可以在 FreeMarker 自帶的載入器上進行擴充套件,重寫 getReader 方法對讀取到的模版檔案內容進行解密後生成一個新的 Reader 例項並返回(詳細過程不再敘述)。

FreeMarker 自帶的幾個 TemplateLoader 分別是:

  1. ClassTemplateLoader :基於類路徑的模版載入器
  2. FileTemplateLoader :基於檔案目錄的模版載入器
  3. MultiTemplateLoader :多種載入器的混合
  4. StringTemplateLoader :基於字串的模版載入器
  5. URLTemplateLoader :基於 URL 的模版載入器
  6. WebappTemplateLoader :基於 Web 上下文的模版載入器

過載模版載入器後通過下面程式碼使之生效:

1

cfg.setTemplateLoader(loader)

FreeMarker 快取處理

FreeMarker 的快取處理主要用於模版檔案的快取,一般來講,模版檔案改動不會很頻繁,在一個流量非常大的網站中,如果頻繁的讀取模版檔案對系統的負擔還是很重的,因此 FreeMarker 通過將模版檔案的內容進行快取,來降低模版檔案讀取的頻次,降低系統的負載。

當處理某個模版時,FreeMarker 直接從快取中返回對應的 Template 物件,並有一個預設的機制來保證該模版物件是跟模版檔案同步的。如果使用的時候 FreemarkerServlet 時,有一個配置項 template_update_delay 用來指定更新模版檔案的間隔時間,相當於多長時間檢測一下是否有必要重新載入模版檔案,0 表示每次都重新載入,否則為多少毫秒鐘檢測一下模版是否更改。

FreeMarker 定義了一個統一的快取處理介面 CacheStorage ,預設的實現是 MruCacheStorage 最近最少使用的快取策略。一般情況下,很少需要對快取進行擴充套件處理。您可以通過下面的程式碼指定最大快取的模版數:

1

cfg.setCacheStorage(new freemarker.cache.MruCacheStorage(20, 250))

其中第一個引數是最大的強引用物件數,第二個為最大的弱引用物件數。這兩個值 FreeMarker 預設的是 0 和 Integer.MAX_VALUE,表明模版快取數是無限的。

FreeMarker 異常處理

當使用 FreeMarker 做為模版引擎的時候,可能發生的異常包括:

配置異常:配置異常指的是 FreeMarker 初始化時發生的異常,例如錯誤的配置導致,該異常時由 FreeMarker 的 API 丟擲來的。

模版載入異常:模版載入異常可能是模版不存在或者沒有讀許可權,或者是解析模版時發生錯誤,例如模版語法錯誤等。

模版執行異常:模版執行異常是指模版已經成功的載入但在執行過程中由於程式碼執行錯誤所丟擲的異常,這類異常一般都是使用者的程式碼導致。

正常情況下,前兩種異常會在開發過程中就會發現並得以解決,而第三種異常往往跟實際的執行環境和資料有關,例如由於某些資料不存在導致的空指標異常等等。因此第三種異常才是我們真正需要關心以及監控的。

為此,FreeMarker 定義了一個統一的異常處理介面 TemplateExceptionHandler 。該介面只有一個方法如下:

1

2

3

void handleTemplateException(TemplateException te,

Environment env,

java.io.Writer out)

通過呼叫 cfg.setTemplateExceptionHandler 來使用自定義的異常處理方法。下面是一個簡單的異常處理擴充套件的例子:

清單 8. 異常處理擴充套件的例子

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

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 外掛。該外掛具有語法高亮、錯誤提示等功能。雖然該外掛還有很多問題,而且已經很久沒更新了,但也能很好地使用。

總結

從上面對於 FreeMarker 的可擴充套件性的介紹來看,FreeMarker 確實是一個功能非常之強大的模版引擎,可以說遠在 Velocity 之上。不過從使用的直觀程度以及上手的時間來看,其複雜度也大大的超過了 Velocity 。當我們在面臨這兩個模版引擎的選擇時,不能只是從功能或者容易上手的角度來決定,更應該根據業務本身的需要綜合進行比較。

相關主題

  • 檢視 FreeMarker 的 Eclipse 編輯外掛 相關資訊。
  • 編寫自定義的 Velocity 指令”(developerWorks,2009 年 4 月):本文通過一個實際應用例子對 Velocity 的模板語言中的指令系統進行了介紹,並演示瞭如何通過編寫自定義的指令來擴充套件 Velocity 的功能。
  • Java 技術專區:尋找 Java 程式設計各方面的技術文章。