0x00 前言
Apache Velocity 是一个老牌 Java 模板语言。相对于另一个老牌模板语言 JSP 而言,Velocity 语法和结构上更加灵活和丰富,因此除了 JSP (JSP 是官方、正统正妻身份)之外的一个比较受欢迎的选择,大多数的 Web Framework 也因此将其作为内置支持模板语言之一,例如 SpringMVC。 所谓模板语言,就是用语言编写出一个模板文件(例如一个动态网页),然后提供具体的数据,经由模板语言引擎处理,最终输出所需要的内容。下面是模板语言通用理解图:
到目前为止,Velocity 的最高版本是 1.7 (2.0 还未出来),所以下面的内容也是针对这个版本来说。
0x01 引入
我们可以使用下面的 Maven 来引入 Velocity 包:
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity</artifactId>
<version>1.7</version>
</dependency>
0x10 起航
-
文件扩展名: vm
-
**文件存放路径,默认在 “.” **
- 可以放到 classes 中,设置比较简单:
VelocityEngine ve = new VelocityEngine(); // Velocity 模板引擎
Properties props = new Properties();
props.setProperty(VelocityEngine.RESOURCE_LOADER, "classpath");
props.setProperty("classpath." + VelocityEngine.RESOURCE_LOADER + ".class",
ClasspathResourceLoader.class.getName());
ve.init(props);
+ 如果要放到 WEB-INF 就比较麻烦,不过可以参考或使用:Velocity-tools。 它提供了 Servlet 和 Struts1 的实现,如果项目也刚好是这样的 Web 框架,直接用就可以。如果不是,也可以直接将 org.apache.velocity.tools.view.WebappResourceLoader 拷贝出来用。
- Hello World
<!-- 存成文件 hello.vm -->
<html>
<body>
#set( $foo = "Velocity" )
Hello $foo World!
</body>
<html>
public class HelloTest {
/**
*
* @param args
*/
public static void main(String[] args) {
//初始化并取得Velocity引擎
VelocityEngine ve = new VelocityEngine();
Properties props = new Properties();
props.setProperty(VelocityEngine.RESOURCE_LOADER, "classpath");
props.setProperty("classpath." + VelocityEngine.RESOURCE_LOADER + ".class",
ClasspathResourceLoader.class.getName());
ve.init(props);
//取得velocity的模版,HelloTest 放在根目录,如果放在包中:例如 my.test.HelloTest ,那么就要用 my/test/hello.vm
Template t = ve.getTemplate("hello.vm");
//取得velocity的上下文context
VelocityContext context = new VelocityContext();
//输出流
StringWriter writer = new StringWriter();
//转换输出
t.merge(context, writer);
System.out.println(writer.toString());
}
}
0x10 语法规范
- **规范一:使用 “##”、“#...#”、“#[[...]]#” 来做注释 **
## 我是行注释 <==== 这个不会输出任何东西
#* <==== 这个会输出一个空行
我是块注释
*#
#** <==== 这个也会输出一个空行
我是文档注释
**#
#[[ <==== 这个里面的内容会输出
本注释的内容不被 Velocity 引擎解释,但会原样输出
]]#
-
**规范二:使用 "$" + 标识符来定义变量,变量不需要指定类型 **
-
标识符:符合 Java 变量定义规范即可。
-
常用示例: $name,$templateName,$request 等等
-
#set($name="David")
#set($templateName = "header.vm")
#set($width = 100)
- 如果变量是对象,可以用“.”(点号)操作属性。对!是属性(即带 set/get 方法的成员变量)!
项目名称:$project.name ## 这里等价于 $project.getName()
#set($template.path = "WEB-INF/") ## 这里等价于 $template.setPath("WEB-INF")
#set($issue.reportor.name = "David")
- 兼容定义:${name}、${templateName}。随个人喜好使用。但在一些特定场合,例如可能会跟内容混淆在一起时,只能使用这种定义方式。
width: ${width}px; /* 如果用 $widthpx ,就乱了 */
- 加“!”(叹号)为空显示。即当变量不存在,又或者是 null 时,强制显示为空白。
$!name
$!email 或者 $!{email}
- **规范三:象 Java 那样调用对象的方法 **
//java 方面,将对象放入 velocity 的上、下文对象中(VelocityContext)
context.put("template", new Template());
$template.show() ##<-- 执行 template 对象的 show() 方法
-
规范四:使用“[ ]”定义列表,使用“{ }”定义 Map,$list[index] 进行列表和 Map 访问
- 使用“[]” 和 “{}” 定义列表或 Map
## 定义列表
$set( #list=[1, 2, 3, 4]) ## 数字列表
$set( #list=[1..10]) ## 使用范围来定义数字列表
$set( #list=['red', 'blue', 'green']) ## 字符串列表
$set( #map={'name':'David', 'address':'address'}) ## map 即键值表
- 列表元素访问:$list[index] 或者 $list.get(index)
## 要注意,Velocity 目前版本并不能支持数组的元素直接访问,要访问单独元素时,只能将数组转为列表,如 ArrayList
$set( #list = [0..10] ) ## 定义一个列表
#list[3] ## 相当于 list.get(3)
$set( #strs = ['name', 'address'] ) ## 定义字符串列表
#strs[1] ## 列表元素
$set( #map = {'name':'David', 'job':'normal'}) ## 定义一个 map
#map['name'] ## 访问 name 键的值,相当于 map.get('name')
#map.job ## 也是访问键值的方法,相当于 map.get('job')
- **规范五:#set 赋值语句,定义和给变量赋值 **
## 要对变量进行赋值,只能通过 #set 语句进行
$set( $var = "value" )
$var ## 这里输出是 “value” 值
$var="Hello" ## 这不是赋值,本行的结果是:value="Hello"
$set( $bill = $price + 10)
$set( $monkey.friend = "monica" )
$set( $monkey.Say = ["Not", $my, "fault"])
$set( $result = $query.criteria("address") )
-
**规范六:#if 条件语句,进行分支处理 **
- 完整语句:#if(条件) ... #elseif(条件) ... #else ... #end
#if($man)
<strong>$man</Strong>
#elseif($man.age > 40)
老员工
#elseif(!$man.married)
未婚
#else
其他
#end
- 布尔值:true, false。除了 true 之外,如果变量的值不为 null,列表或map 不为空,也看作 true 。
#if($man) ## 因为 $man 未定义,所以走 #else 这个分支
<strong>$man</strong>
#else
未定义
#end
#set( $man="David" ) ## 已经给 $man 赋值,即使不是布尔值,但是不为 null,所以也为 true
#if($man)
<strong>$man</strong> ## 本例将执行这个分支
#else
未定义
#end
- 逻辑表达式:&&、||、! 同 java 一样的操作符
#if( $foo && $bar )
这是 AND 操作
#elseif( $head || $menu )
这是 OR 操作
#elseif( !$query )
这是 not 操作
#end
- 关系表达式:==、>、<、!= 同 java 一样的操作符。要注意,在这里 == 是值比较,不是 java 那个引用比较
#if( $color=="blue" )
color: $color;
#{elseif}( $width < 1000 ) ## #elseif 也可以写成 #{elseif}
width: 900 px;
#end
-
**规范七:#foreach 循环语句,列表、数组或 map 的循环遍历。注意:可以用来遍历数组。 **
- 语句结构: #foreach ( $var in $list ) ... #end
## 列表遍历
<table> ## 列出所有客户的名称
#foreach( $customer in $customerList )
<tr><td>$customer.name</td></tr>
#end
</table>
## Map 表的遍历
<table> ## 假设 $customerList 是一个 <name, customer> 键值表
#foreach( $name in $customerList.keyset() ) ## 遍历键
<tr><td>$name</td></tr>
#end
</table>
- 计数($foreach.count)、索引值($foreach.index)和中止遍历(#break)
## $foreach 是循环内部自动生成的变量,记录循环的信息
$foreach.count ## 循环计数,从 1 开始每循环一次加1
$foreach.index ## 遍历列表的索引值,从0开始
$foreach.hasNext ## 是否还有下一个(遍历),可以用来判断是否到达最后
$foreach.first ## 第一个元素
$foreach.last ## 最后一个元素
$foreach.parent ## 外层循环
<table>
#foreach( $customer in $customerList )
<tr><td>$foreach.count</td><td>$customer.name</td></tr>
#end
</table>
## 显示前五个客户
<table>
#foreach( $customer in $customerList )
#if( $foreach.count > 5 )
#break
#end
<tr><td>$foreach.count</td><td>$customer.name</td></tr>
#end
</table>
- 可以在 velocity.properties 中配置全局的最大循环次数,作为一种防护手段
directive.foreach.maxloops = -1 ## -1 是默认值,表示不限制
-
**规范八:#include、#parse 引入外部资源和模板文件 **
- #include(file1, file2,....) 导入外部资源,但不进行解释
#include( "license.txt", $footer )
- #parse( file ) 导入 Velocity 模板文件并解释,结果原地输出。下层模板能够使用上层模板的变量
## parent.vm
#set( $list = [1..10])
#parse( "child.vm" )
## child.vm
parent's $list ## 使用父模板的变量
-
**规范九:#macro 、#define 自定义可重用模块 **
- #macro 宏定义,定义可带参数的模板内函数:#macro( name [$param1 $param2 ...]) ... #end。参数可以是零或多个。
## 可以将宏定义看作是模板内函数,由名称和参数组成
#macro( log $color $message ) ## 定义了一个名称为 log 的宏,并带两个参数: $color 和 $message
<p style="color:$color">log: $message</p>
#end
## 使用时,可以当成普通函数一样用
#log("blue" "log 输出...") ## 在使用时,要象函数那样加上"()" 小括号
## 没参数的宏
#macro( show )
Hello World!
#end
##使用,要加上小括号
#show()
- 宏在使用时,传入的数据个数,不需要与定义中的参数个数一样。
## 可以将宏定义看作是模板内函数,由名称和参数组成
#macro( log $color $message ) ## 定义了一个名称为 log 的宏,并带两个参数: $color 和 $message
<p style="color:$color">log: $message</p>
#end
## 可以省略后面的参数
#log('blue') ## 只输入一个参数,输出结果为:<p style="color: blue">log: $message</p> 其中 $message 原样输出,不过如果在宏外面定义了 $message 的值,在这里也可以输出
## 也可以传入更多的数据,当然,会被忽略
#log('blue' '参数2' '参数3') ## 多出来的参数由于没有使用而被忽略
- #define 定义可重用块,是 #macro 不带参数的简化版
#define( $block ) ## 定义块引用,引用名称为 $block
Hello World!
#end
$block ## 进行引用,要留意的是,不需要添加 “()”,这是与 macro 唯一不同的地方
- 建立一个全局的、独立的可重用库。我们可以将常用的宏聚集起来形成可重用库。
## 下面是一些相关的配置
velocimacro.library——用逗号分隔的一组文件名,是Velocity宏模板库。默认值是VM_global_library.vm
velocimacro.permissions.allow.inline——宏是否可以在一个普通模板中定义。默认值是false,表示可以。
velocimacro.permissions.allow.inline.to.replace.global——是否允许模板中的宏覆盖library中的宏。默认值是false,表示不可以覆盖。
velocimacro.permissions.allow.inline.local.scope——一个在普通模板中定义的宏,是否允许其他模板使用。默认是false。
velocimacro.context.localscope——在一个宏里通过#set()修改了context,此修改是否仅仅对这个宏自身,而不是永久性修改了context。默认值是false。
velocimacro.library.autoreload——Velocity宏模板库修改之后,是否自动重新加载。默认值是false。debug时可以设置为true,发布时设置为false。
-
**规范十:#evaluate 动态执行字符串,#stop 中止并退出模板解释 **
- #evaluate 可以执行内嵌 Velocity 指令的字符串。我们可以在有需要时,动态生成一些指令,然后由本方法执行。
## 根据需要生成命令,暂时找不到好例子,就用官方文档的例子
#set($source1 = "abc")
#set($select = "1")
#set($dynamicsource = "$source$select")
## $dynamicsource is now the string '$source1'
#evaluate($dynamicsource)
- #stop 中止并退出模板解释。在调试或出现严重错误,需要中止时有用。
#stop ## 中止
#stop( '$order was not in context' ) ## 可以加一个中止说明,这个说明将会写入到日志文件中去
0x11 还未结束
任何一项技术(不紧紧是 Velocity)在用时,都是三大件:环境,使用和深入。环境当然包含了如何加入到项目中,如何加载和配置配置项;灵活使用满足项目需求;深入掌握技术的精髓,助力项目在满足需求情况下,减少开发成本,提高稳定性和可维护性。 本文只是说明基础的规范与使用,如何灵活运用和深入掌握,还得要多用、多想、多看。