歡迎您光臨本站 註冊首頁

Java進階:編寫自定義的Velocity指令

←手機掃碼閱讀     火星人 @ 2014-03-10 , reply:0

Velocity 是一個基於 Java 的模板引擎,它允許用戶使用簡單的模板語言來引用由 Java 代碼定義的對象.當 Velocity 應用於 Web 開發時,界面設計人員可以和 Java 程序開發人員同步開發一個遵循 MVC 架構的 Web 站點.也就是說,頁面設計人員可以只關注頁面的顯示效果,而 Java 程序開發人員關注後台業務邏輯的編碼. Velocity 將 Java 代碼從 Web 頁面中分離出來,這樣為 Web 站點的長期維護提供了便利,同時也為我們在 JSP 和 PHP 之外又提供了一種可選的方案.

Velocity 的能力不僅僅用於 Web 開發領域,它也可以被當作一個獨立工具來產生源代碼和報告(例如,可以產生 SQL 和 PostScript、XML 等),或者作為其他系統的集成組件使用.

下面是一個簡單的用 Velocity 編寫的網頁代碼:

<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 包含下列這些內置指令:

另外也可以通過宏來擴展指令,例如,請看下面這個宏的定義:

#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 的自定義宏可以用來擴展指令,但是宏有一個不足的地方,它更適合用來執行一些比較通用的代碼嵌入和簡單的功能處理.

由此引出來的問題是,我如何來編寫一個類似於 #foreach 的指令呢,並具有邏輯判斷的功能?

Velocity 的指令類型簡介

在 Velocity 的指令定義上,有兩種指令類型分別是行指令和塊指令.行指令例如 #set($name= 」 Winter Lau 」 ) 賦值指令,只有一行,中間沒有任何的代碼;而塊指令例如循環指令 #foreach($idx in [1..20]) $idx #end,塊指令需要用 #end 來結束.在 Velocity 自帶的指令中塊指令包括有:#if #elseif #foreach #define 和 #macro 這幾個指令,除此之外都是行指令.

編寫自定義的 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)

如此,便以很小的代碼侵入,來實現頁面的緩存.

自定義指令在 Veloeclipse 插件中的使用事項

Veloeclipse 是一個在 Eclipse 開發環境中編輯 Velocity 模板和 HTML 代碼的插件,具有語法著色、自動代碼完成以及錯誤提示等功能.如果在編輯 Velocity 模板時使用了自定義插件,則該插件會提示錯誤,我們可以通過下面的界面添加自定義的指令來使 Veloeclipse 支持這些指令.

點擊菜單 Windows - Preferences - Veloeclipse ,通過如下的界面來添加自定義指令.

圖 1. 添加自定義指令的界面

總結

Velocity 是一個非常高效、簡潔的 Java 模板引擎,其強大的擴展性,使之特別適合在 Web 項目中使用.本文通過一個實際應用中的例子對 Velocity 的指令系統進行了介紹,歡迎大家跟我一起深入探討 Velocity 的相關擴展問題.


[火星人 ] Java進階:編寫自定義的Velocity指令已經有750次圍觀

http://coctec.com/docs/java/show-post-61703.html