Velocity 是一個基於 Java 的模板引擎,它容許用戶使用簡單的模板語言來引用由 Java 代碼定義的對象。當 Velocity 應用於 Web 開發時,界面設計人員能夠和 Java 程序開發人員同步開發一個遵循 MVC 架構的 Web 站點。也就是說,頁面設計人員能夠只關注頁面的顯示效果,而 Java 程序開發人員關注後臺業務邏輯的編碼。 Velocity 將 Java 代碼從 Web 頁面中分離出來,這樣爲 Web 站點的長期維護提供了便利,同時也爲咱們在 JSP 和 PHP 以外又提供了一種可選的方案。css
Velocity 的能力不單單用於 Web 開發領域,它也能夠被看成一個獨立工具來產生源代碼和報告(例如,能夠產生 SQL 和 PostScript、XML 等),或者做爲其餘系統的集成組件使用。html
下面是一個簡單的用 Velocity 編寫的網頁代碼:node
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <meta http-equiv="Content-Language" content="zh-CN"/> <title>$page_title</title> <link rel="stylesheet" href="osc-global.css" type="text/css" /> </head> <body> <div id="OSC_top"> #parse("header.vm") </div> <div id="OSC_content"> <table> #foreach($idx in [1..20]) <tr> <td>$velocityCount</td> <td>Now is $date.now()</td> </tr> #end </table> </div> <div id="OSC_bottom"> #include("bottom.vm") </div> </body> </html>
在 Velocity 模板語言的語法中,以美圓符 $ 開頭的爲變量的聲明或者引用,而以井號 # 開頭的語句則爲 Velocity 的指令(Directive)。其中 Velocity 的指令又分爲內置指令、自定義宏和自定義指令。 Velocity 包含下列這些內置指令:web
另外也能夠經過宏來擴展指令,例如,請看下面這個宏的定義:數據庫
#macro(invoke $__p_page) #if($__p_page.startsWith("/")) #parse($__p_page) #else #set($__uri = $resource.this_vm()) #set($__path = $__uri.substring(0, $__uri.lastIndexOf("/"))) #parse("$__path/$__p_page") #end #end
上面這個名爲 invoke 的宏的做用是以相對路徑的方式嵌入某個動態的頁面。使用的方法是 #invoke( 「 hello.vm 」 )。儘管 Velocity 的自定義宏能夠用來擴展指令,可是宏有一個不足的地方,它更適合用來執行一些比較通用的代碼嵌入和簡單的功能處理。apache
由此引出來的問題是,我如何來編寫一個相似於 #foreach 的指令呢,並具備邏輯判斷的功能?緩存
Velocity 的指令類型簡介架構
在 Velocity 的指令定義上,有兩種指令類型分別是行指令和塊指令。行指令例如 #set($name= 」 Winter Lau 」 ) 賦值指令,只有一行,中間沒有任何的代碼;而塊指令例如循環指令 #foreach($idx in [1..20]) $idx #end,塊指令須要用 #end 來結束。在 Velocity 自帶的指令中塊指令包括有:#if #elseif #foreach #define 和 #macro 這幾個指令,除此以外都是行指令。ide
編寫自定義的 Velocity 指令工具
Velocity 容許您對指令系統進行擴展,在 Velocity 引擎初始化的時候會加載系統內置指令和用戶的自定義指令。系統的內置指令已經在 Velocity 的 Jar 包中的 directive.properties 文件中定義,不建議直接修改該文件。而自定義的指令要求用戶在 velocity.properties 文件中定義的,例如:userdirective=net.oschina.toolbox.CacheDirective。若是是多個自定義指令則使用 逗號隔開。
全部的自定義指令要求擴展 org.apache.velocity.runtime.directive.Directive 這個類。爲了更加形象直觀的表現 Velocity 自定義指令的優勢,接下來咱們將以一個實際的應用場景進行講解。
在該應用場景中,全部的頁面請求直接指向 vm 文件,中間沒通過任何的控制器。數據是經過 Velocity 的 toolbox 直接讀取並顯示在頁面上。若是數據是來自數據庫的,並且訪問量很是大的時候,咱們就須要對這些數據進行緩存以便快速響應用戶請求和下降系統負載。一種方法 是直接在 toolbox 的讀取數據的方法中進行數據的緩存;另一種就是咱們接下來要介紹的,經過編寫自定義的緩存指令來緩存頁面上的某個 HTML 片斷。
首先咱們定義一個這樣的塊指令:#cache( 「 CacheRegion 」 , 」 Key 」 ) ,其中第一個參數爲緩存區域、第二個參數爲對應緩存數據的鍵值。該指令自動將包含在指令內部的腳本執行後的結構緩存起來,當第一次請求時檢查緩存中是否存 在此 HTML 片斷數據,若是存在就直接輸出到頁面,不然執行塊指令中的腳本,執行後的結果輸出到頁面同時保存到緩存中以便下次使用。使用方法以下所示:
#cache("News","home") ## 讀取數據庫中最新新聞並顯示 <ul> #foreach($news in $NewsTool.ListTopNews(10)) <li> <span class='date'> $date.format("yyyy-MM-dd",${news.pub_time}) </span> <span class='title'>${news.title}</span> </li> #end </ul> #end
其中 $NewsTool.ListTopNews(10) 是用來從數據庫中讀取最新發布的 10 條新聞信息。
接下來咱們來看 #cache 這個指令對應的源碼:
/** * Velocity模板上用於控制緩存的指令 */ public class CacheDirective extends Directive { final static Hashtable<String,String> body_tpls = new Hashtable<String, String>(); @Override public String getName() { return "cache"; } //指定指令的名稱 @Override public int getType() { return BLOCK; } //指定指令類型爲塊指令 /* (non-Javadoc) * @see org.apache.velocity.runtime.directive.Directive#render() */ @Override public boolean render(InternalContextAdapter context, Writer writer, Node node) throws IOException, ResourceNotFoundException, ParseErrorException, MethodInvocationException { //得到緩存信息 SimpleNode sn_region = (SimpleNode) node.jjtGetChild(0); String region = (String)sn_region.value(context); SimpleNode sn_key = (SimpleNode) node.jjtGetChild(1); Serializable key = (Serializable)sn_key.value(context); Node body = node.jjtGetChild(2); //檢查內容是否有變化 String tpl_key = key+"@"+region; String body_tpl = body.literal(); String old_body_tpl = body_tpls.get(tpl_key); String cache_html = CacheHelper.get(String.class, region, key); if(cache_html == null || !StringUtils.equals(body_tpl, old_body_tpl)){ StringWriter sw = new StringWriter(); body.render(context, sw); cache_html = sw.toString(); CacheHelper.set(region, key, cache_html); body_tpls.put(tpl_key, body_tpl); } writer.write(cache_html); return true; } }
Directive 是全部指令的基類,Directive 是一個抽象類,它有三個方法必須實現的,分別是:
•getName:返回指令的名稱
•getType:返回指令的類型,行指令:LINE、塊指令:BLOCK
•render:指令執行的入口
其中 render 方法的最後一個參數 node 表示爲該指定對應在 Velocity 模板中的節點對象,經過調用 node 的 jjtGetChild 方法能夠獲取到傳遞給該指令的參數以及包含在該指令的腳本內容。
上面的代碼中,首先獲取傳遞給指令的參數,也就是緩存的區域名和對應緩存數據的鍵值。 接着判斷距上次數據被緩存時,指令所包含的腳本代碼是否有更改(以便頁面開發人員修改了 vm 腳本時自動刷新緩存數據),而後判斷緩存中是否已有數據。當緩存中無數據或者頁面代碼被修改時,從新執行塊指令中的腳本並將執行的結果置入緩存,不然直接 將緩存中的數據輸出到頁面。
上述例子中,傳遞給 #cache 指令的參數也能夠是某個變量,例如
#set($region = "news") #set($key = "home") #cache("CACHE_$region",$key)
如此,便以很小的代碼侵入,來實現頁面的緩存。