Velocity 模闆引擎介紹
在現今的軟體開發過程中,軟體開發人員将更多的精力投入在了重複的相似勞動中。特别是在如今特别流行的MVC架構模式中,軟體各個層次的功能更加獨立,同時代碼的相似度也更加高。是以我們需要尋找一種來減少軟體開發人員重複勞動的方法,讓程式員将更多的精力放在業務邏輯以及其他更加具有創造力的工作上。Velocity這個模闆引擎就可以在一定程度上解決這個問題。
Velocity是一個基于Java的模闆引擎架構,提供的模闆語言可以使用在Java中定義的對象和變量上。Velocity是Apache基金會的項目,開發的目标是分離MVC模式中的持久化層和業務層。但是在實際應用過程中,Velocity不僅僅被用在了MVC的架構中,還可以被用在以下一些場景中。
1.Web 應用:開發者在不使用JSP 的情況下,可以用Velocity 讓HTML 具有動态内容的特性。
2.源代碼生成:Velocity可以被用來生成Java代碼、SQL或者PostScript。有很多開源和商業開發的軟體是使用Velocity來開發的。
3.自動Email:很多軟體的使用者注冊、密碼提醒或者報表都是使用Velocity來自動生成的。使用Velocity可以在文本檔案裡面生成郵件内容,而不是在Java代碼中拼接字元串。
4.轉換xml:Velocity提供一個叫Anakia的ant任務,可以讀取XML檔案并讓它能夠被Velocity模闆讀取。一個比較普遍的應用是将xdoc文檔轉換成帶樣式的HTML檔案。
Hello Velocity
和學習所有新的語言或者架構的順序一樣,我們從Hello Velocity開始學習。首先在Velocity的官網上下載下傳最新的釋出包,之後使用Eclipse建立普通的Java項目。引入解壓包中的velocity-1.7.jar和lib檔案夾下面的jar包。這樣我們就可以在項目中使用Velocity了。
在做完上面的準備工作之後,就可以建立一個叫HelloVelocity 的類,代碼如下:
清單1. HelloVelocity.java
public class HelloVelocity { public static void main(String[] args) { VelocityEngine ve = new VelocityEngine(); ve.setProperty(RuntimeConstants.RESOURCE_LOADER, "classpath"); ve.setProperty("classpath.resource.loader.class", ClasspathResourceLoader.class.getName()); ve.init(); Template t = ve.getTemplate("hellovelocity.vm"); VelocityContext ctx = new VelocityContext(); ctx.put("name", "velocity"); ctx.put("date", (new Date()).toString()); List temp = new ArrayList(); temp.add("1"); temp.add("2"); ctx.put( "list", temp); StringWriter sw = new StringWriter(); t.merge(ctx, sw); System.out.println(sw.toString()); } }
在HelloVelocity的代碼中,首先new了一個VelocityEngine類,這個類設定了Velocity使用的一些配置,在初始化引擎之後就可以讀取hellovelocity.vm這個模闆生成的Template這個類。之後的VelocityContext類是配置Velocity模闆讀取的内容。這個context可以存入任意類型的對象或者變量,讓template來讀取。這個操作就像是在使用JSP開發時,往request裡面放入key-value,讓JSP讀取一樣。
接下來就是寫hellovelocity.vm檔案了,這個檔案實際定義了Velocity的輸出内容和格式。hellovelocity.vm的内容如下:
清單2. Hellovelocity.vm
輸出結果如下:#set( $iAmVariable = "good!" ) Welcome $name to velocity.com today is $date. #foreach ($i in $list) $i #end $iAmVariable
Welcome velocity to velocity.com today is Sun Mar 23 19:19:04 CST 2014. 1 2 good!
在輸出結果中我們可以看到,$name、$date都被替換成了在HelloVelocity.java裡面定義的變量,在foreach語句裡面周遊了list的每一個元素,并列印出來。而$iAmVariable則是在頁面中使用#set定義的變量。
回頁首
基本模闆語言文法使用
在hellovelocity.vm裡面可以看到很多以#和$符開頭的内容,這些都是Velocity的文法。在Velocity中所有的關鍵字都是以#開頭的,而所有的變量則是以$開頭。Velocity的文法類似于JSP中的JSTL,甚至可以定義類似于函數的宏,下面來看看具體的文法規則。一、變量
和我們所熟知的其他程式設計語言一樣,Velocity 也可以在模闆檔案中有變量的概念。
1. 變量定義
等号後面的字元串Velocity 引擎将重新解析,例如出現以$開始的字元串時,将做變量的替換。#set($name =“velocity”)
#set($hello =“hello $name”)
上面的這個等式将會給$hello 指派為“hello velocity”
2. 變量的使用
在模闆檔案中使用$name或者${name}來使用定義的變量。推薦使用${name}這種格式,因為在模闆中同時可能定義了類似$name和$names的兩個變量,如果不選用大括号的話,引擎就沒有辦法正确識别$names這個變量。
對于一個複雜對象類型的變量,例如$person,可以使用${person.name}來通路person的name屬性。值得注意的是,這裡的${person.name}并不是直接通路person的name屬性,而是通路person的getName()方法,是以${person.name}和${person.getName()}是一樣的。
3. 變量指派
在第一小點中,定義了一個變量,同時給這個變量賦了值。對于Velocity來說,變量是弱資料類型的,可以在賦了一個String給變量之後再賦一個數字或者數組給它。可以将以下六種資料類型賦給一個Velocity變量:變量引用,字面字元串,屬性引用,方法引用,字面數字,數組清單。
#set($foo = $bar) #set($foo =“hello”) #set($foo.name = $bar.name) #set($foo.name = $bar.getName($arg)) # set($foo = 123) #set($foo = [“foo”,$bar])
二、循環
在Velocity 中循環語句的文法結構如下:Velocity 引擎會将list 中的值循環賦給element 變量,同時會建立一個$velocityCount 的變量作為計數,從1 開始,每次循環都會加1.#foreach($element in $list) This is $element $velocityCount #end
三、條件語句
條件語句的文法如下#if(condition) ... #elseif(condition) … #else … #end
四、關系操作符
Velocity 引擎提供了AND、OR 和NOT 操作符,分别對應&&、||和! 例如:#if($foo && $bar) #end
五、宏
Velocity中的宏可以了解為函數定義。定義的文法如下:調用這個宏的文法是:#macro(macroName arg1 arg2 …) ... #end
這裡的參數之間使用空格隔開,下面是定義和使用Velocity 宏的例子:#macroName(arg1 arg2 …)
輸出的結果為hello velocity#macro(sayHello $name) hello $name #end #sayHello(“velocity”)
六、#parse 和#include
#parse和#include指令的功能都是在外部引用檔案,而兩者的差別是,#parse會将引用的内容當成類似于源碼檔案,會将内容在引入的地方進行解析,#include是将引入檔案當成資源檔案,會将引入内容原封不動地以文本輸出。分别看以下例子:
foo.vm 檔案:
parse.vm:#set($name =“velocity”)
#parse(“foo.vm”)
輸出結果為:velocity
include.vm:
#include(“foo.vm”)
輸出結果為:#set($name =“velocity”)
以上内容包含了部分Velocity 的文法,詳細的文法内容可以參考Velocity 的官方文檔。
回頁首
自動生成代碼的例子
在上個例子中我們可以生成任意的字元串并且列印出來,那為什麼我們不能生成一些按照既定格式定義的代碼并且寫入檔案呢。
在這裡我們以一個實際的demo來完成這部分内容。相關内容的源碼可以參照附件。這個demo的功能是要實作一個學生和老師的管理,實際上都是單張表的維護。我們希望能夠隻定義model層,來生成MVC的所有代碼。在這個demo中,隻自動生成action和JSP的内容,因為現在有很多工具都可以幫助我們自動生成這兩個包的代碼。
首先在eclipse中建立一個Java web工程,在例子中為了友善管理jar包,使用的是maven來建立和管理工程。建立好的工程目錄結構如下圖所示:
圖1. 項目目錄結構
![]()
使用Velocity 模闆引擎快速生成代碼 Java Resource中放的是Java源碼以及資源檔案,Deployed Resources中放的是web相關的檔案。在Java檔案中使用了類似Spring的@Component和@Autowired的注解來實作IoC,使用@Action這樣的注解實作MVC,而在JSP中則使用了JSTL來輸出頁面。在上圖所示的目錄中,annotation、filter、framework和util這四個package是作為這個項目架構的,跟業務沒有關系,類似于spring和struts的功能。
在實際的項目中我們當然希望能夠一開始就編寫一個通用的模闆檔案,然後一下子生成所有的代碼,但是很多時候這樣做是不可能的,或者說比較困難。為了解決這個問題,我們可以在編寫Velocity模闆檔案之前先按照原本的流程編寫代碼,暫時先忘掉Velocity。編寫的代碼應該能夠在一個功能上完整的調通涉及MVC中所有層次的内容。在這個例子中,先編寫好StudentAction.java檔案,以及上圖中webapp目錄中所示的檔案。在寫好以上代碼,同時也能順利運作之後,我們可以參照之前編寫的代碼來寫模闆檔案。這裡我們來分别看一個Java檔案和JSP的例子。
清單3. ActionTemplate.vm
#parse ("macro.vm") @Action("${classNameLowCase}Action") public class ${classNameUpCase}Action extends BaseAction{ @Autowired public ${classNameUpCase}Dao ${classNameLowCase}Dao; private List<${classNameUpCase }> ${classNameLowCase}s; private ${classNameUpCase} ${classNameLowCase}; #foreach ($attr in ${attrs}) private ${attr[0]} ${attr[1]}; #end public String $ {classNameLowCase}List() { ${classNameLowCase}s = ${classNameLowCase}Dao.retrieveAll${classNameUpCase}s(); return "${classNameLowCase}List.jsp"; } ... }
上面的代碼展示了一個Java 類轉換成vm 模闆之後的部分内容,完整内容請參考附件。
macro.vm檔案中定義了一些使用的宏。JSP的改造相對于Java檔案來說稍微有點複雜,因為JSP中使用JSTL取request中的值也是使用${name}這樣的文法,是以想要輸出${name}這樣的字元串而不是被模闆引擎所替換,則需要使用轉義字元,就像這樣:\${name}。
為了能夠讓這個檔案中的table得到複用,我們将這個檔案中的表格單獨拿出來,使用#parse指令來包含。下面是ListJspTemplate.vm和ListTableTemplate.vm的内容:
清單4. ListJspTemplate.vm
清單5. ListTableTemplate.vm<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@taglib prefix="c" uri="http://java.sun .com/jsp/jstl/core" %> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd" > <html> <head> <%@ include file="includeJS.jsp" %> <script type="text/javascript"> var pageConfig = { "list" : { "action" : "${classNameLowCase}Action! ${classNameLowCase}List.action" } ... "idName" : "${classNameLowCase}Id" }; </script> <script type="text/javascript" src="common.js"></script> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>${classNameUpCase} List</title> </head> <body> <h1>${classNameUpCase } List</h1> <div><button id="addButton">Add</button></div> #parse ("ListTableTemplate.vm") <div id="modifyDiv"></div> <div id ="addDiv"></div> </body> </html>
#parse ("macro.vm") #set($plus = "status.index+1") <table style="width: 100%"> <thead> <tr><th>No. </th>#generateTH($attrs)</tr> </thead> <tbody> <c:forEach var="${classNameLowCase}" items="${${classNameLowCase}s }" varStatus="status" > <tr ${classNameLowCase}Id="${${classNameLowCase}.id }"> <td>${${plus}}</td>#generateTD($classNameLowCase $attrs)<td> <button class= "modifyButton">Modify</button> <button class="deleteButton">Delete</button></td></tr> </c:forEach> </tbody> </table>
在定義好所有的模闆檔案之後,需要做的是讀取這些檔案,然後根據這些檔案将model的資料類型以及名稱設定到context中,最後将解析出來的内容寫到相應的目錄中去。這些工作我們放在了一個叫做VelocityGenerator的類中來做,它的源碼如下:
清單6. TemplateGenerator.java
public class VelocityGenerator { public static void main(String[] args) { VelocityEngine ve = new VelocityEngine(); ve.setProperty(RuntimeConstants.RESOURCE_LOADER, "classpath"); ve.setProperty("classpath.resource.loader.class", ClasspathResourceLoader.class.getName()); ve.init(); Template actionTpt = ve.getTemplate("ActionTemplate.vm"); Template listJspTpt = ve.getTemplate("ListJspTemplate.vm"); Template addTpt = ve.getTemplate( "AddTemplate.vm"); Template modifyTpt = ve.getTemplate("ModifyTemplate.vm"); VelocityContext ctx = new VelocityContext(); ctx.put("classNameLowCase", "teacher"); ctx.put("classNameUpCase", "Teacher"); String[][] attrs = { {"Integer","id"}, {"String","name"}, {"String","serializeNo"}, {"String","titile "}, {"String","subject"} }; ctx.put("attrs", attrs); String rootPath = VelocityGenerator.class.getClassLoader().getResource("").getFile() + "../ ../src/main"; merge(actionTpt,ctx,rootPath+"/java/com/liuxiang/velocity/action/TeacherAction.java"); merge(listJspTpt,ctx,rootPath+"/webapp/teacherList.jsp"); merge(addTpt,ctx,rootPath+"/webapp/teacherAdd.jsp"); merge(modifyTpt,ctx,rootPath+"/webapp/teacherModify.jsp"); System.out.println("success..."); } private static void merge(Template template, VelocityContext ctx, String path) { PrintWriter writer = null; try { writer = new PrintWriter(path); template.merge(ctx, writer); writer.flush(); } catch (FileNotFoundException e) { e.printStackTrace(); } finally { writer.close(); } } }
在運作以上代碼之後,項目檔案夾中将會出現與Teacher 相關的代碼檔案。
在實際項目中可能不會出現很多這種單張表維護的情況,而且業務邏輯和系統架構會更加複雜,編寫模闆檔案就更加不容易。但是無論多複雜的系統,不同的業務邏輯之間一定或多或少會有相似的代碼,特别是在JSP和JS顯示端檔案中,因為我們在一個系統中要求顯示風格、操作方式一緻的時候就免不了會有相似内容的代碼出現。在總結這些相似性之後我們還是可以使用Velocity來幫助我們生成部分内容的代碼,而且即使有一些非共性的内容,我們也可以在生成的代碼中繼續修改。使用Velocity的另外一個好處是生成出來的代碼更好維護,風格更加統一。
結束語
Velocity可以被應用在各種各樣的情景下,本文介紹的隻是它的一種用途而已,它還可以被用來做MVC結構中的view層,或者動态内容靜态化等。另外,Velocity并不是唯一的模闆架構,同樣很優秀的Freemarker也獲得了非常廣泛的應用,有興趣的讀者可以去深入研究更多的功能和用途。
作者:一行代碼的事
出處:http://www.cnblogs.com/zkycode/
聲明:歡迎分享和轉載,出于對部落格園社群和作者的尊重一定要保留原文位址。
緻讀者:堅持寫部落格,堅持原創。與大家一起讓技術改變世界!