1.简介
Velocity是一款基于Java的模板引擎。它允许Web页面设计者引用Java代码中定义的方法。Web设计者能与根据Model-View-Controller (MVC)模型开发Web网站的Java程序员合作,意味着Web页面设计者能将注意力放在创建设计精良的网站,而程序员能将注意力放在编写代码上。Velocity从Web页面中分离Java代码,使Web网站在长时间运行时更可维护性和提供Java Server Pages(JSP)或PHP的替代方案。
Velocity能用于从模板产生Web页面、SQL、PostScript和其它输出。它能用于作为单独的工具类生成源码和报表,或作为其它系统的一个整合组件。Velocity将为Turbine Web应用程序框架提供模板服务。Velocity+Turbine将提供模板服务允许Web应用程序根据真实MVC模型开发。
2.安装
JDK(必须)
Jakarta Commons Collections(必须)
Jakarta Commons Lang(必须)
Excalibur(ex-Avalon)Logkit(可选,如果使用默认的基于文件的日志时需要)
Jakarta ORO(可选,当使用org.apache.velocity.convert.WebMacro模板转换工具或org.apache.velocity.app.event.implement.Escape Reference ReferenceInsertionEventHandler时使用)
3.实践
3.1 基本步骤
初始化Velocity
创建Context对象
添加数据对象到Context
选择模板
“合并”模板和数据产生输出
3.2 单例
模板:
代码:
Velocity.init();VelocityContext context = new VelocityContext();context.put( "word", "Hello Velocity!");// 模板路径默认是当前项目的根目录Template template = Velocity.getTemplate("src/main/java/com/study/velocity/HelloWord.vm");StringWriter sw = new StringWriter();template.merge(context, sw);System.out.println(sw);
3.3 多例
与单例不同,多例可以在用一个JVM或Web应用程序中维护多组配置。
VelocityEngine ve = new VelocityEngine();ve.init();Template t = ve.getTemplate("src/main/java/com/study/velocity/HelloWord.vm");StringWriter sw = new StringWriter();VelocityContext context = new VelocityContext();context.put( "word", "Hello Velocity!");t.merge(context, sw);System.out.println(sw);
4.注释
注释一向是一门编程语言的必须成员,没有注释的代码不能称之为好代码。注释允许放
置描述文本在模板引擎中。注释通常用于提示自己和解释别人VTL(VelocityTemplate Language)语句在做什么。
Velocity包含三种注释:
单行注释
多行注释
文档注释
4.1 单行注释
单行注释以 ## 开始,从被注释的位置开始到行结尾的所有字符将不被模板引擎解析。
## 这是单行注释
4.2 多行注释
以 #* 开始,*#结尾,中间的内容不被模板引擎解析。
#* 这是多行注释*#
4.3 文档注释
类似于JavaDoc注释,用于存储各种额外信息。
#**这是文档注释@author 赵凡@version 1.0*#
5.引用
在VTL中,有三种类型的引用:变量、属性和方法。
5.1 变量
变量的速记符号由前导“$”字符后随VTL标识符组成。VTL标识符必须以字母开头,合法字符包含:
字母
数值
连字符
下划线
下面是VTL中有效的变量引用:
$foo$mudSlinger$mud-slinger$mud_slinger$mudSlinger1
当VTL引用一个变量时,例如$foo,变量能从模板中的set指令或Java代码中的Context获取值。
#set( $word = "Hello Velocity!" )${word}
5.2 属性
代码:
Velocity.init();VelocityContext context = new VelocityContext();User user = new User();user.setUsername("xxxx");context.put( "user", user);Template template = Velocity.getTemplate("src/main/java/com/study/velocity/Properties.vm");StringWriter sw = new StringWriter();template.merge(context, sw);System.out.println(sw);public class User { private String username; public String getUsername() { System.out.println("do get"); return username; } public void setUsername(String username) { this.username = username; }}
模板:
$user.username
输出结果:
do getxxxx
由上面的结果表明,$user.username调用的就是user的getUsername方法。
5.3 方法
方法引用由前导“$”字符后随VTL标识符和VTL方法体。VTL方法体由VTL标识符后随左圆括号、可选参数列表、右圆括号。下面是有效的VTL:
$sun.getPlanets()$annelid.getDirt()$album.getPhoto()$sun.getPlanet( ["Earth", "Mars", "Neptune"] )$sisyphus.pushRock()$book.setTitle( "Homage to Catalonia" )
5.4 属性查找规则
就像前面所说的,属性通常引用父对象的方法。Velocity很聪明,计算请求属性的方法。它基于命名规则查找方法。期望的查找序列依赖于是否属性名开始与大写字母。对于小写字母,例如$customer.address的顺序是:
getaddress()getAddress()get("address")isAddress()
对于大写字母稍有不同:
getAddress()getaddress()get("Address")isAddress()
5.5 渲染
每个引用(无论变量、属性或方法)最后产生的值转换为字符串对象。如果一个对象是Integer,那么Velocity将调用它的.toString()方法解析对象为字符串。
5.6 下标表示法
使用下标形式$foo[0]能用于访问指定索引的对象。这种形式的同义词在指定对象上调用get(Object)方法上一致,例如$foo.get(0)
$foo[0] ## $foo takes in an Integer look up$foo[$i] ## Using another reference as the index$foo["bar"] ## Passing a string where $foo may be a Map
括号语法也适用于Java数组,因为Velocity包装数组在访问对象中,提供get(Integer)
方法返回指定元素。
括号语法在任何适用.get的地方都是有效的,例如:
$foo.bar[1].junk$foo.callMethod()[1]$foo["apple"][4]
引用也可以使用索引设置,例如:
#set($foo[0] = 1)#set($foo.bar[1] = 3)#set($map["apple"] = "orange")
5.7 正规引用符号
上面的引用符号都是缩写,正式的引用符号如下所示:
${mudSlinger}${customer.Address}${purchase.getTotal()}
大多数情况下,可以使用缩写,但是在一种情况下,必须使用正式写法。
Jack is a $vicemaniac.
上面的例子中,变量是vicemaniac,但是我们实际想要的是vice,此时,我们可以在变
量前后加上中括号来表示这个变量。
Jack is a ${vice}maniac.
5.8 处理未定义引用
当Velocity遇到了未定义的引用时,默认会原样输出引用。可以使用以下方式使其输出
空串。
$!email$!{email}
Velocity 1.6引入严格引用模式的概率,通过设置Velocity配置属性
"runtime.references.strict”为true激活。在未定义或歧义的情况下,Velocity将抛出异常。使用该设置引用必须明确放置在context中或使用#set指令定义或抛异常。
引用在context中有一个null值将不会产生一个异常。此外,如果试图调用不存在的方
法或属性将抛出异常。如果试图调用的对象是null抛出异常。
在下面的例子中,$bar已定义,$foo未定义,所有这些语句将抛出异常:
$foo ## Exception#set($bar = $foo) ## Exception#if($foo == $bar)#end ## Exception#foreach($item in $foo)#end ## Exception
当试图调用的方法或属性不存在时Velocity抛出异常。在这种情况下,$bar包含一个对
象定义一个属性"foo"返回一个字符串,而retnull防御null。
$bar.bogus ## $bar没有bogus属性,Exception$bar.foo.bogus ## $bar.foo没有bogus属性,Exception$bar.retnull.bogus ## 不能在null上调用属性,Exception
#if和#elseif指令中的引用比较特殊:
#if ($foo)#end ## False#if ( ! $foo)#end ## True#if ($foo && $foo.bar)#end ## False并且$foo.bar将不会计算#if ($foo && $foo == "bar")#end ## False并且$foo == "bar"将不会计算#if ($foo1 || $foo2)#end ## False $foo1并且$foo2将不会计算
严格模式必须在#if指令中包含>、=或<=。同时,参数#foreach必须迭代(该行
为能修改属性 directive.foreach.skip.invalid)。最后,在严格模式下,未定义宏引用
也将抛出异常。
this is $foo ## 抛出异常,因为$foo是nullthis is $!foo ## 渲染为"this is "没有异常this is $!bogus ## bogus不在context中,因此抛出异常
6.指令
指令允许模板设计者生成Web网站的动态内容,而指令——易于使用脚本元素能用于创建操作Java代码输出——允许Web设计者真正负责Web网站的外观和内容。
指令总是以#开始。如同引用,指令的名字可以通过{和}符号括起来。通常文本紧随其后。例如以下产生错误:
#if($a==1)true enough#elseno way!#end
在这种情况下,使用花括号分隔#else。
#if($a==1)true enough#{else}no way!#end
6.1 #set
#set指令用于设置引用的值。值能复制给变量引用或属性引用,这发生在括号中:
#set( $primate = "monkey" )#set( $customer.Behavior = $primate )
等于号左边必须是一个变量引用或属性引用。右边可以是以下类型:
变量引用
字符串字面值
属性引用
方法引用
数值字面值
ArrayList
Map
#set( $monkey = $bill ) ## variable reference#set( $monkey.Friend = "monica" ) ## string literal#set( $monkey.Blame = $whitehouse.Leak ) ## property reference#set( $monkey.Plan = $spindoctor.weave($w#set( $monkey = $bill ) ## variable reference#set( $eb) ) ## method reference#set( $monkey.Number = 123 ) ##number literal#set( $monkey.Say = ["Not", $my, "fault"] ) ## ArrayList#set( $monkey.Map = {"banana" : "good", "roast beef" : "bad"}) ## Map
注意:ArrayList例子中的元素使用[...]定义操作符,定义在ArrayList类中的方法都可以
访问。因此,例如,你可以使用$monkey.Say.get(0)访问第一个元素。类似,Map的例子,元素定义在{}操作符中,可以使用定义在Map类中的方法访问。因此,例如,你可以使用$monkey.Map.get("banana")访问第一个元素返回一个字符串"good",或$monkey.Map.banana返回相同值。等号右边也可以是简单的算术表达式:
#set( $value = $foo + 1 )#set( $value = $bar - 1 )#set( $value = $foo * $bar )#set( $value = $foo / $bar )
如果右边是计算为null的属性或方法引用,它将不赋值给左边。依赖于Velocity的配置,通常这种机制不能够删除已存在的引用。
#set( $result = $query.criteria("name") )第一个查询的结果是$result#set( $result = $query.criteria("address") )第二个查询的结果是$result
如果$query.criteria("name")返回字符串“bill”,而$query.criteria("address")返回null,上面VTL将渲染为:
第一个查询的结果是bill第二个查询的结果是bill
6.2 字面值
当使用#set指令时,封闭在双引号里面的字符串字面值将被解析和渲染:
#set( $directoryRoot = "www" )#set( $templateName = "index.vm" )#set( $template = "$directoryRoot/$templateName" )$template
输出:
www/index.vm
然而,当字符串字面值封装在单引号中时,它将不被解析:
#set( $foo = "bar" )$foo#set( $blargh = '$foo' )$blargh
渲染为:
bar$foo
默认,这种在Velocity中使用单引号渲染未解析的文本是有效的。该默认能通过velocity.properties编辑,例如stringliterals.interpolate=false。#[[不解析我!]]#语法允许模板设计者易于在VTL代码中使用大块的不解释和不解析内容。
#[[#foreach ($woogie in $boogie) nothing will happen to $woogie#end]]#
渲染为:
#foreach ($woogie in $boogie) nothing will happen to $woogie#end
7.条件指令
7.1 if/elseif/else
在Velocity中,#if指令允许在if语句条件为true时包括文本。例如:
#if( $foo ) Velocity!#end
变量$foo计算决定是否为true,这三种情况下将发生什么:
$foo是boolean(true/false),为true
$foo是字符串或集合,不为null和不为空
$foo是一个不为null的对象(除了字符串或集合)
记住,Velocity context只包含Object,因此,当我们说"boolean",它将表示为Boolean(类)。
如果条件为true,#if和#end语句之间的内容变成输出。在这种情况下,如果$foo是true,输出将是:“Velocity!”。相反的,如果$foo有一个null值,或者有一个boolean false,条件为false,那么没有输出。
#elseif或#else语句能与#if语句连用。注意,Velocity模板引擎将在第一个条件表达式为true的语句终止。在以下例子中,假设$foo为15,$bar为6.
#if( $foo < 10 ) Go North#elseif( $foo == 10 ) Go East#elseif( $bar == 6 ) Go South#else Go West#end
在该例子中,$foo大于10,因此前面两个比较失败。接下来$bar与6比较为true,因此输出时Go South。
7.2 关系和逻辑运算符
Velocity使用等价运算符决定变量之间的关系。下面是一个简单的例子来说明如何使用等价运算符。
#set ($foo = "deoxyribonucleic acid")#set ($bar = "ribonucleic acid")#if ($foo == $bar) In this case it's clear they aren't equivalent. So...#else They are not equivalent and this will be the output.#end
注意,==的语义与Java中的略有不同,只能用于测试对象是否相等。在Velocity中,
等价运算符能直接用于比较数值、字符串或对象。当对象是不同的类时,通过调用每
个对象的toString()获取字符串表现形式然后比较。
Velocity也有逻辑AND、OR和NOT运算符。下面例子演示,使用逻辑AND、OR和NOT运算符。
## 逻辑AND#if( $foo && $bar ) This AND that#end## 逻辑OR#if( $foo || $bar ) This OR That#end##逻辑NOT#if( !$foo ) NOT that#end
这些逻辑运算符与Java中的逻辑运算符一致,此处不再累述。需要注意的是$!foo不会
应为foo为null而报错。逻辑运算符也有自己的文本形式的版本:eq、ne、and、or、
not、gt、ge、lt和le。
8.循环指令
Velocity模板支持各种集合类型的迭代。
Object[] Velocity包装数据对象到迭代对象中,可以将其当做固定长度的List,可调用size()、isEmpty()和get(int)。
java.util.Collection Velocity将使用iterator()方法获取一个Iterator用于循环,因此,如果你实现了Collection接口,请确保iterator()返回一个Iterator。
java.util.Map Velocity依赖接口的values()方法获取一个Collection接口,调用iterator()方法检索Iterator用于循环。
java.util.Iterator、java.util.Enumeration 只是暂时支持。
任意不返回null的public Iterator iterator()方法。
8.1 Map
迭代Map中的Key,通过Key获取Map中的元素,来带到迭代的功能。
#foreach( $key in $allProducts.keySet() ) Key: $key -> Value: $allProducts.get($key)#end
8.2 集合
Velocity可以通过简单的方式获取循环中的计数器。
<table>#foreach( $customer in $customerList ) <tr><td>$foreach.counttd><td>$customer.Nametd>tr>#endtable>
Velocity也可以通过简单的方式告诉你,是否是最后一次循环。
#foreach( $customer in $customerList ) $customer.Name#if( $foreach.hasNext ),#end#end
如果你想要#foreach循环一个基于0的索引,你能使用$foreach.index替$foreach.count。同样,提供$foreach.first和$foreach.last。如果你想要访问外部循环的#foreach循环的属性,你能直接通过$foreach.parent或$foreach.topmost属性引用它们(例如,$foreach.parent.index或$foreach.topmost.hasNext)。可以设置循环允许执行的最大次数。默认没有最大值(表示为0或更小),但这能在velocity.properties文件中设置为任意数值。
directive.foreach.maxloops = -1
如果你想要终止foreach,你现在能使用#break指令终止循环。
## list first 5 customers only#foreach( $customer in $customerList ) #if( $foreach.count > 5 ) #break #end $customer.Name#end
9
导入外部文
件
Velocity有两种方式从外部引入文件:
包括和解析。
9.1 包括
#include脚本元素允许模板设计者导入本地文件,然后插入到#include指令定义的位置。文件的内容通过模板引擎渲染。由于安全原因,文件只能包含在TEMPLATE_ROOT。
#include("one.txt")
#include指令引用的文件被封闭在双引号中。如果多个文件被include,应该通过逗号分隔。
#include("one.gif","two.txt","three.htm")
可以使用变量替换文件名作为参数。
#include("greetings.txt", $seasonalstock)
9.2 解析
#parse脚本元素允许模板设计者导入包含VTL的本地文件。Velocity将解析VTL并渲染
模板。
#parse("me.vm")
像#include指令一样,#parse可以传入一个变量而不是模板。任意引用的模板必须在TEMPLATE_ROOT中。和#include指令不同,#parse只需要一个参数。VTL模板可以在模板中递归使用#parse引用。velocity.properties中的directive.parse.max.depth属性允许用户自定义最大递归数,默认设置为10。
(如果velocity.properties文件中没有出现directive.parse.max.depth,Velocity将默认设置为10)。
Count down.#set( $count = 8 )#parse( "parsefoo.vm" )All done with dofoo.vm!
引用的模板:
$count#set( $count = $count - 1 )#if( $count > 0 ) #parse( "parsefoo.vm" )#else All done with parsefoo.vm!#end
10.宏调用
#macro脚本元素允许模板设计者定义重复的VTL模板片段。宏调用在简单和复杂的场
景中非常广泛。
定义宏:
#macro( d )#end
调用宏:
#d()
定义有体的宏,$!bodyContent为宏体:
#macro( d )<tr><td>$!bodyContenttd>tr>#end
调用有体的宏:
#@d()Hello!#end
定义带参数的宏:
#macro( tablerows $color $somelist )#foreach( $something in $somelist ) $something#end#end
调用带参数的宏:
#set( $greatlakes = ["Superior","Michigan","Huron","Erie","Ontario"] )#set( $color = "blue" )
#tablerows( $color $greatlakes )