跳到主要内容

velocity模板语言(VTL)

· 阅读需 29 分钟

参考官网记录:https://velocity.apache.org/

基本符号

  • #

    使用“#”用来标识Velocity的指令语句,比如:#set#if#else#end#foreach...

  • $

    访问Velocity变量使用$,比如:$cosmic

  • !

    变量名称前加上!,比如:$!cosmic,如果cosmic有值,将显示cosmic的值,如果不存在就会显示为空白,一般会推荐使用。

  • |

    访问引用时,可以设置默认值,而不是空白值:My name is ${name|'John Doe'},name不存在时,会输出John Doe。

注释

单行注释:

## This is a single line comment.

多行注释:

#*
Thus begins a multi-line comment. Online visitors won't
see this text because the Velocity Templating Engine will
ignore it.
*#

文档注释:(VTL注释块)它可用于存储要在模板中跟踪的任何类型的额外信息(例如 javadoc 样式的作者和版本控制信息)

#**
This is a VTL comment block and
may be used to store such information
as the document author and versioning
information:
@author John Doe
@version 5
*#

变量(引用)

声明变量

变量始终以$开头

// 定义变量`$a`
#set( $a = "Velocity" )

字符串用引号(单引号或双引号)括起来,单引号将确保将引用的值按原样分配给引用。双引号允许您使用Velocity引用和指令进行插值,例如 "Hello $name",其中$name将替换为当前值,然后再将字符串文本分配给=

访问变量(Variables)

变量的简化形式由前导 $ 字符后跟 VTL 标识符组成

$foo
$mudSlinger
$mud_slinger
$mudSlinger1

访问属性(Properties)

简化形式由一个前导$字符后跟一个 VTL 标识符、后跟一个点字符 (“.”) 和另一个 VTL 标识符组成。以下是 VTL 中有效属性的示例:

$customer.Address
$purchase.Total

访问方法(Methods)

在 Java 代码中定义了一个方法,它能够执行一些有用的操作,例如运行计算或做出决策。

VTL 方法体由一个 VTL 标识符组成,后跟一个左括号字符 (“(”),后跟一个可选参数列表,后跟右括号字符 (“)”)。以下是 VTL 中有效方法引用的示例:

$customer.getAddress()
$purchase.getTotal()
$page.setTitle("My Home Page")
$person.setAttributes(["Strange", "Weird", "Excited"])

前两个示例$customer.getAddress()$purchase.getTotal() 可能看起来类似于上面 Properties 部分中使用的示例,$customer.Address$purchase.Total。如果您猜到这些示例必须以某种方式相关,那么您是对的!

注意

$customer.Address为例,它可以有两个含义。

这可能意味着,在标识为customer的哈希表中查找,并返回与键Address关联的值。

$customer.Address也可以指代方法:$customer.Address可以是 $customer.getAddress() 的缩写方式。

当您的页面被请求时,Velocity 将确定这两种可能性中的哪一种有意义,然后返回适当的值。

总结:

VTL 属性可用作 VTL 方法的简写表示法。Property $customer.Address 与使用 Method $customer.getAddress() 的效果完全相同。

通常,最好使用 Property (如果可用)。Properties 和 Methods 之间的主要区别在于,您可以为 Method 指定参数列表。

数组

  • 所有数组引用都被视为固定长度列表。这意味着您可以对数组引用调用 java.util.List 方法和属性。所以,如果你有一个对数组的引用(假设这个数组是一个具有三个值的 String[]),你可以这样做:
$myarray.isEmpty() or $myarray.empty
$myarray.size()
$myarray.get(2)
$myarray.set(1, 'test')
  • Velocity 还支持 vararg 方法。像下边的方法现在在模板中调用时可以接受任意数量的参数。

    public void setPlanets(String... planets)

    public void setPlanets(String[] planets)

$sun.setPlanets('Earth', 'Mars', 'Neptune')
$sun.setPlanets('Mercury')
$sun.setPlanets()## 只传递一个空的零长度数组

✨: 从 Velocity 2.0 开始,方法调用现在提供所有 Java 基本内置类型(数字、布尔值和字符串)之间的隐式转换。

属性查找规则

如前所述,属性通常引用父对象的方法。在确定哪个方法对应于请求的属性时,Velocity 非常聪明。它根据几个已建立的命名约定尝试不同的替代方案。确切的查找序列取决于属性名称是否以大写字母开头。对于小写名称(如$customer.address),序列为

  1. getaddress()
  2. getAddress()
  3. get("address")
  4. isAddress()
  5. address() (从 Velocity 2.4 开始,以匹配 Java 16 记录字段 getter)

对于大写属性名称,如$customer.Address,略有不同:

  1. getAddress()
  2. getaddress()
  3. get("Address")
  4. isAddress()
  5. Address()(从 Velocity 2.4 开始,以匹配 Java 16 记录字段 getter)

渲染

每个引用(无论是变量、属性还是方法)的最终值在呈现到最终输出时都会转换为 String 对象。如果存在表示 $foo 的对象(例如 Integer 对象),则 Velocity 将调用其.toString()方法将对象解析为 String。

索引表示法(数组、Map)

使用 $foo[0] 形式的表示法可以用来访问对象的给定索引。这种形式与在给定对象上调用 get(Object) 方法同义,本质上是为此类操作提供了简化形式。由于这只是调用 get 方法,因此以下所有用途都是有效的:

$foo[0]       ## $foo接受一个索引查询
$foo[$i] ## 使用另一个引用作为索引
$foo["bar"] ## 传递一个字符串,其中$foo可能是一个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")

指定的元素使用给定的值进行设置。Velocity 首先在元素上尝试 'set' 方法,然后 'put' 进行赋值。

正式引用表示法

上面列出的示例使用了参考文献的简化形式表示法,但也有参考文献的正式表示法,如下所示:

${mudSlinger}
${customer.Address}
${purchase.getTotal()}

在几乎所有情况下,您都会使用速记表示法作为引用,但在某些情况下,正确处理需要正式表示法。

需要正式表示法

假设您正在动态构建一个句子,其中$vice将用作句子名词中的基词。目标是允许某人选择基本词并产生以下两个结果之一:“Jack is a pyromaniac.”或“Jack is a kleptomaniac.”。使用速记表示法对于此任务来说是不够的。请考虑以下示例:

Jack is a $vicemaniac.

这里存在歧义,Velocity 假定 $vicemaniac 而不是 $vice 是您要使用的属性。如果找不到$vicemaniac的值,它将返回:$vicemaniac。使用正式表示法可以解决此问题。

Jack is a ${vice}maniac.

现在 Velocity 知道 $vice 而不是 $vicemaniac 是引用。当引用与模板中的文本直接相邻时,正式表示法通常很有用。

替代值用法(正式表示法)

当引用的布尔值为 false 时,正式引用表示法也可用于提供替代值。

My name is ${name|'John Doe'}

如果 $name 为 null、empty、false 或 0(请参阅条件),则将显示'John Doe'字符串。

静默的参考符号($!)

当 Velocity 遇到未定义的引用时,其正常行为是输出引用的图像。例如,假设以下引用显示为 VTL 模板的一部分。

<input type="text" name="email" value="$email"/>

最初加载表单时,变量引用$email没有值,但您更喜欢空白文本字段,而不是值为“$email”的文本字段。使用安静的引用符号会规避 Velocity 的正常行为;不要在 VTL 中使用$email,而是使用$!email。所以上面的例子将如下所示:

<input type="text" name="email" value="$!email"/>

现在,当表单最初加载并且*$email*仍然没有值时,将输出一个空字符串而不是 “$email”。

正式和安静的引用符号可以一起使用,如下所示。

<input type="text" name="email" value="$!{email}"/>

严格渲染模式

通过将 Velocity 配置属性 'runtime.strict_mode.enable' 设置为 true 来激活严格渲染模式。此设置的一般目的是使 Velocity 在未定义或不明确的情况下表现得更严格,类似于编程语言,这可能更适合 Velocity 的某些用途。在这种未定义或不明确的情况下,Velocity 将引发异常。以下讨论概述了严格行为不同于传统行为的情况。

严格模式导致异常情况的说明

使用此设置时,需要将引用显式放置在上下文中或使用 #set 指令定义,否则 Velocity 将引发异常。上下文中值为 null 的引用不会产生异常。此外,如果尝试在未定义指定方法或属性的引用中调用对象的方法或属性,则 Velocity 将引发异常。如果尝试对 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指令中的特殊情况除外。如果在没有任何方法或属性的#if#elseif指令中使用引用,并且未将其与其他值进行比较,则允许未定义的引用。此行为提供了一种简单的方法来测试在模板中使用引用之前是否定义了引用。

在下面的示例中,如果未定义$foo,则语句不会引发异常。

#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修改此行为)。最后,未定义的宏引用也会在严格模式下引发异常。

Velocity 尝试渲染但计算结果为 null 的引用将导致 Exception。在这种情况下,要简单地渲染任何内容,引用前面可以有 '$!' 而不是 '$',类似于非严格模式。请记住,这与上下文中不存在的引用不同,后者在尝试以严格模式呈现引用时总是会引发异常。例如,下面的 $foo 在上下文中的值为 null

this is $foo    ## 抛出异常,因为$foo为null
this is $!foo ## 返回"this is ",没有异常
this is $!bogus ## bogus没在上下文中,因此,抛出异常

用例替换(替代用法)

$foo

$foo.getBar()
## 等价于
$foo.Bar

$data.setUser("jon")
## 等价于
#set( $data.User = "jon" )

$data.getRequest().getServerName()
## 等价于
$data.Request.ServerName
## 等价于
${data.Request.ServerName}

Velocity 是根据 Sun Microsystems 定义的 Bean 规范建模的,区分大小写;但是,它的开发人员一直努力尽可能地捕获和纠正用户的任何可能的错误。当模板中的方法getFoo()被引用时,Velocity会先尝试$getfoo,如果失败了,然后回尝试$getFoo。同样地,当模板中用到$bar.Foo,Velocity会先尝试$getFoo() 再尝试getfoo().

: *对实例变量的引用不会在模板中解析。*仅解析对 JavaBean getter/setter 方法的等效属性的引用(即 $foo.Name在类 Foo中解析为getName()实例方法,而不是 Foo 的 Name 公共实例变量)。

指令

执行脚本。以#开头

✨set(声明变量、赋值)

备注

单引号,原值输出,双引号会被解析。

#set(${name}='velocity')
#set($cosmic="hello ${name}")

赋值的左侧 (LHS) 必须是变量引用或属性引用。右侧 (RHS) 可以是以下类型之一:

  • 变量引用
  • 字符串文本
  • 属性引用
  • 方法参考
  • 数字字面量
  • ArrayList
  • Map
高级用法
#set( $monkey = $bill ) ## 变量引用
#set( $monkey.Friend = "monica" ) ## 字符串常量
#set( $monkey.Blame = $whitehouse.Leak ) ## 属性引用
#set( $monkey.Plan = $spindoctor.weave($web) ) ## 方法引用
#set( $monkey.Number = 123 ) ## 数值常量
#set( $monkey.Say = ["Not", $my, "fault"] ) ## ArrayList
#set( $monkey.Map = {"banana" : "good", "roast beef" : "bad"}) ## Map
ArrayList和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 )

如果 RHS 是计算结果为 null 的属性或方法引用,则 LHS 将设置为 null。

与其他一些 Velocity 指令不同,#set 指令没有 #end 语句。

不解析引用写法(作为常量输出)

双引号会解析引用,单引号不会解析。#[[内容]]#不会解析。

#set( $directoryRoot = "www" )
#set( $templateName = "index.vm" )

#set( $template = "$directoryRoot/$templateName" )
$template ## 输出:www/index.vm

#set( $template2 = '$directoryRoot/$templateName' )
$template2 ## 输出:$directoryRoot/$templateName

#[[don't parse me!]]#的语法,允许模板设计者,在模板中方便地使用大块的无需翻译或解析的内容。在代替那些无效的VTL内容(因此也不可解析)的转义多个指令或转义段时,这尤其有用。

#[[
#foreach ($woogie in $boogie)
对于$woogie,什么也不会发生
#end
]]#

✨If / ElseIf / Else

#if( $foo )
<strong>foo Velocity!</strong>
#elseif( $bar )
<strong>bar Velocity!</strong>
#end

评估变量 $foo 以确定它是否为 true,这将在以下情况下发生:

  • $foo 是一个布尔值 (true/false),其值为 true
  • $foo 是一个字符串或集合,它不是 null 且不为空
  • $foo 是一个不等于零的数字
  • $foo 是一个不为 null 的对象(字符串、数字或集合除外)(对于该对象,用于查询对象大小、长度或布尔值/字符串表示形式的标准公共方法(如果存在)表示非空、非零、非 false 对象)。
常见用法

普通用法

#if($age > 10 && $age <20)
……
#elseif($age > 30 || $age <10)
……
#else
……
#end

判断特殊值

  • #if ($ref == $null)专门测试 null 值(前提是您没有在$null)
  • #if ($ref == false)专门测试 false 值
  • #if ($ref == '')专门测试空字符串
  • #if ($ref == 0)专门测试零
  • #if ($ref.size() == 0)专门测试空集合

关系和逻辑运算符

#set ($foo = "deoxyribonucleic acid")
#set ($bar = "ribonucleic acid")

#if ($foo == $bar && $jack || ($ma && $bar.length() > 1) )
...
#else
...
#end

请注意,==的语义与 Java 略有不同,其中==只能用于测试对象相等性。在 Velocity 中,等效运算符可用于直接比较数字、字符串或对象。当对象属于不同的类时,通过调用每个对象的toString()来获取字符串表示形式,然后进行比较。

提示
  • AND、OR、NOT、大于、小于...和Java语法一致。不在此赘述。可以看上边的例子。

  • 所有逻辑运算符都有文本版本,包括 eqneandornotgtgeltle

✨Foreach

基本语法:

<ul>
#foreach( $product in $allProducts )
<li>$product.getName()</li>
#end
</ul>
用法说明

$allProducts是数组对象时,可以像上边代码块中那么用。

  • 也可以遍历Map对象:
<ul>
#foreach( $key in $allProducts.keySet() )
<li>Key: $key -> Value: $allProducts.get($key)</li>
#end
</ul>
  • 获取索引计数

count从1开始,index从0开始

<table>
#foreach( $customer in $customerList )
<tr><td>$foreach.index</td><td>$foreach.count</td><td>$customer.Name</td></tr>
#end
</table>
  • 获取迭代第一次(first)和最后一次(last)

Velocity 还提供了一种简单的方法来判断您是在循环的第一次迭代还是最后一次迭代中:

#foreach( $item in $template3 )
#if( $foreach.first ) There are first: #end
#if( $foreach.last ) There are last: #end
$item #if( $foreach.hasNext ),#end
#end
  • break跳出循环
## list first 5 customers only
#foreach( $customer in $customerList )
#if( $foreach.count > 5 )
#break
#end
$customer.Name
#end

Include

#include 脚本元素允许模板设计者导入本地文件,然后将其插入到定义 #include 指令的位置。文件的内容不会通过模板引擎呈现。出于安全原因,要包含的文件可能仅在 TEMPLATE_ROOT 下。

#include( "one.txt" )

如果要导入多个文件,它们应该用逗号分开。

#include( "one.gif","two.txt","three.htm" )

所包含的文件不需要按名称引用;事实上,使用变量而不是文件名通常更可取。这对于根据提交页面请求时确定的标准定位输出可能很有用。下面是一个同时显示文件名和变量的示例。

#include( "greetings.txt", $seasonalstock )

Parse

#parse 脚本元素允许模板设计者导入包含 VTL 的本地文件。Velocity 将解析 VTL 并呈现指定的模板。

#parse( "me.vm" )

#include 指令一样,#parse 可以采用变量而不是模板。#parse 引用的任何模板都必须包含在 TEMPLATE_ROOT 下。与 #include 指令不同,#parse 将只接受一个参数。

VTL模板允许*#parse语句导入的模板,也包含#parse语句。默认情况下,velocity.properties中的directive.parse.max.depth值是10,它也允许用户自定义。(注意:如果velocity.properties文件中的directive.parse.max.depth*未设置,Velocity会将它设置为缺省值10)。递归是允许的,例如,模板dofoo.vm包含如下的文件:

Count down.
#set( $count = 8 )
#parse( "parsefoo.vm" )
All done with dofoo.vm!

它将引用模板,其中可能包含以下 VTL:parsefoo.vm

$count
#set( $count = $count - 1 )
#if( $count > 0 )
#parse( "parsefoo.vm" )
#else
All done with parsefoo.vm!
#end

显示 “Count down.” 后,Velocity 会通过 parsefoo.vm,从 8 开始倒计时。当计数达到 0 时,将显示 “All done with parsefoo.vm!” 消息。此时,Velocity 将返回dofoo.vm并输出 “All done with dofoo.vm!” 消息。

Break

有效范围更广的Break,不只能在循环中使用。

#break 指令为停止对当前执行范围的任何进一步渲染。“执行范围”本质上是任何具有内容(即 #foreach、#parse、#evaluate、#define、#macro 或 #@somebodymacro)或任何“根”范围(即 template.merge(...)、Velocity.evaluate(...) 或 velocityEngine.evaluate(...))的指令。与 #stop 不同,#break 只会停止最内部的直接范围,而不是全部范围。

如果你希望跳出一个特定的执行范围,而这个范围不一定是最直接的,那么你可以将范围控制引用(即 $foreach、$template、$evaluate、$define、$macro 或 $somebodymacro)作为参数传递给 #break。(例如 #break($macro))。这将停止渲染所有范围,直到指定的范围。当在相同类型的嵌套作用域中时,请记住,你总是可以通过 $.parent 或 $.topmost 访问父级,并将它们传递给 #break(例如 #break($foreach.parent) 或 #break($macro.topmost))。

Stop

像断言

#stop 指令会停止模板的任何进一步渲染和执行。即使指令嵌套在通过 #parse 访问的另一个模板中或位于 Velocity 宏中,也是如此。生成的合并输出将包含遇到 #stop 指令之前的所有内容。这在模板的早期退出时很方便。出于调试目的,您可以提供一个信息参数(例如 #stop('$foo was not in context')),该参数将在 stop 命令完成后写入日志(当然是 DEBUG 级别)。

Evaluate(求值)

#evaluate 指令可用于动态对 VTL 求值。这允许模板计算在渲染时创建的字符串。这样的字符串可用于国际化模板或包含数据库中模板的部分。

传入字符串,动态解析字符串。

下面的例子会显示abc

#set($source1 = "abc")
#set($select = "1")
#set($dynamicsource = "$source$select")
## $dynamicsource 现在是字符串'$source1'
#evaluate($dynamicsource)

⭐Define(复用VTL块)

定义VTL块,调用直接渲染出文本。类似:定义一个返回字符串的VTL块。

#define 指令允许将 VTL 块赋值给引用。

下面的例子将会显示Hello World!.

#define( $block )Hello $who#end
#set( $who = 'World!' )
$block

Macros宏

定义指令,非引用

#macro脚本元素,允许模板设计者定义VTL模板重复的片段。宏在简单场景或复杂场景的广大范围内,非常有用。这个宏,作为介绍宏概念的例子,为了保存击键,减少输入错误而创建。

#macro( d )
<tr><td></td></tr>
#end

这个例子定义了宏d,并且可以想调用其他VTL指令一样,调用它:

#d()
高级用法

基于上个例子,当调用了这个模板时,Velocity会用一组单个的空数据单元格,来代替*#d()*。如果我们想在单元格里放些东西,我们可以修改这个宏,来允许它有数据体:

一个参数

一个参数时,默认引用名称:bodyContent(固定)

下面的#@d()Hello#end的含义是:调用名为d的宏,参数为Hello


#macro( d )
<tr><td>$!bodyContent</td></tr>
#end


#@d()Hello#end
多个参数

多个参数时,需明确定义引用名称

  ## 定义宏:

#macro( tablerows $color $somelist )
#foreach( $something in $somelist )
<tr><td bgcolor=$color>$something</td></tr>
#end
#end

## 使用宏:

#set( $greatlakes = ["Superior","Michigan","Huron","Erie","Ontario"] )
#set( $color = "blue" )
<table>
#tablerows( $color $greatlakes )
</table>

定义公共宏

在模板中定义的宏默认只能该模板访问,可以将宏设置为公共的,供其他Velocity模板使用。

定义一个可以被其它模板共用的宏,有明显的好处:它能减少在大量模板中重复定义它;节省工作,并且减少出错的几率,还能保证对宏进行的一次修改,可被用于其它模板中。

配置需要将某个宏设置为公共的。 见配置:velocity.properties:velocimacro.library.path

将宏写入:mushroom.vm

#macro( tablerows $color $somelist )
#foreach( $something in $somelist )
<tr><td bgcolor=$color>$something</td></tr>
#end
#end

mushroom.vm配置在velocimacro.library.path内,

然后就可以随处访问该宏了。


基本该有的都有了,足够开发用了,剩下的可以参考原文档。

🎭 常见问题的解决方式:

总结一下用的不顺手的地方以及解决方案

如何原值输出内容?

下面方法可以自己考虑哪种方便,除此之外就不要考虑了。

  • 使用#[[***]]#包裹(最佳,最灵活,复用少最合适)
    #[[原值: ${name}']]#
  • 使用#set和单引号,创建一个引用(也可以,复用多可以考虑使用)
    #set( $name_str = '原值: ${name}' )
    // 在其他地方调用:`${name_str}`
  • 使用\$进行转义(不推荐,公式中带-会报错)
    原值: \\${name}