在現今的軟件開發過程當中,軟件開發人員將更多的精力投入在了重複的類似勞動中。特別是在現在特別流行的MVC架構模式中,軟件各個層次的功能更加獨立,同時代碼的類似度也更加高。因此咱們須要尋找一種來減小軟件開發人員重複勞動的方法,讓程序員將更多的精力放在業務邏輯以及其餘更加具備創造力的工做上。Velocity這個模板引擎就能夠在必定程度上解決這個問題。javascript
Velocity是一個基於Java的模板引擎框架,提供的模板語言可使用在Java中定義的對象和變量上。Velocity是Apache基金會的項目,開發的目標是分離MVC模式中的持久化層和業務層。可是在實際應用過程當中,Velocity不只僅被用在了MVC的架構中,還能夠被用在如下一些場景中。html
1.Web 應用:開發者在不使用JSP 的狀況下,能夠用Velocity 讓HTML 具備動態內容的特性。java
2.源代碼生成:Velocity能夠被用來生成Java代碼、SQL或者PostScript。有不少開源和商業開發的軟件是使用Velocity來開發的。程序員
3.自動Email:不少軟件的用戶註冊、密碼提醒或者報表都是使用Velocity來自動生成的。使用Velocity能夠在文本文件裏面生成郵件內容,而不是在Java代碼中拼接字符串。web
4.轉換xml:Velocity提供一個叫Anakia的ant任務,能夠讀取XML文件並讓它可以被Velocity模板讀取。一個比較廣泛的應用是將xdoc文檔轉換成帶樣式的HTML文件。spring
和學習全部新的語言或者框架的順序同樣,咱們從Hello Velocity開始學習。首先在Velocity的官網上下載最新的發佈包,以後使用Eclipse創建普通的Java項目。引入解壓包中的velocity-1.7.jar和lib文件夾下面的jar包。這樣咱們就能夠在項目中使用Velocity了。apache
在作完上面的準備工做以後,就能夠新建一個叫HelloVelocity 的類,代碼以下:編程
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的內容以下:架構
#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,甚至能夠定義相似於函數的宏,下面來看看具體的語法規則。
1、變量
和咱們所熟知的其餘編程語言同樣,Velocity 也能夠在模板文件中有變量的概念。
1. 變量定義
#set($name =「velocity」)
等號後面的字符串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])
2、循環
在Velocity 中循環語句的語法結構以下:
#foreach($element in $list) This is $element $velocityCount #end
Velocity 引擎會將list 中的值循環賦給element 變量,同時會建立一個$velocityCount 的變量做爲計數,從1 開始,每次循環都會加1.
3、條件語句
條件語句的語法以下
#if(condition) ... #elseif(condition) … #else … #end
4、關係操做符
Velocity 引擎提供了AND、OR 和NOT 操做符,分別對應&&、||和! 例如:
#if($foo && $bar) #end
5、宏
Velocity中的宏能夠理解爲函數定義。定義的語法以下:
#macro(macroName arg1 arg2 …) ... #end
調用這個宏的語法是:
#macroName(arg1 arg2 …)
這裏的參數之間使用空格隔開,下面是定義和使用Velocity 宏的例子:
#macro(sayHello $name) hello $name #end #sayHello(「velocity」)
輸出的結果爲hello velocity
6、#parse 和#include
#parse和#include指令的功能都是在外部引用文件,而二者的區別是,#parse會將引用的內容當成相似於源碼文件,會將內容在引入的地方進行解析,#include是將引入文件當成資源文件,會將引入內容原封不動地以文本輸出。分別看如下例子:
foo.vm 文件:
#set($name =「velocity」)
parse.vm:
#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來創建和管理工程。創建好的工程目錄結構以下圖所示:
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的例子。
#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的內容:
<%@ 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 border="1" 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的類中來作,它的源碼以下:
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也得到了很是普遍的應用,有興趣的讀者能夠去深刻研究更多的功能和用途。