Velocity 自定義標籤
編寫自定義的 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模板上用於控制快取的指令
* @author Winter Lau
* @date 2009-3-16 下午04:40:19
*/
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)
如此,便以很小的程式碼侵入,來實現頁面的快取。