Velocity魔法堂系列二:VTL语法详解

一、前言                            

  Velocity作为历史悠久的模板引擎不单单可以替代JSP作为Java Web的服务端网页模板引擎,而且可以作为普通文本的模板引擎来增强服务端程序文本处理能力。而且Velocity被移植到不同的平台上,如.Net的NVelocity和js的Velocity.js,虽然各平台在使用和实现上略有差别,但大部分语法和引擎核心的实现是一致的,因此学习成本降低不少哦。

  最好的学习资源——官网:http://velocity.apache.org/

  本系列打算采用如下结构对Velocity进行较为全面的学习,若有不妥或欠缺望大家提出,谢谢。

1. 入门示例

  2. VTL语法详解

3. 模板与宿主环境通信

  4. 基础配置项

  5. 深入模板引擎及调优配置

二、VTL语法详解                        

  VTL的语句分为4大类:注释直接输出的内容引用指令。另外由于VTL中以 # 和 $ 作为关键字起始字符,因此输出它们时需要通过转义符 \ 来将其转换为普通字符。

  由于内容较多,特设目录一坨!

三. 注释(行注释、 块注释、 文档注释)

四. 直接输出的内容

五. 引用(变量、属性、方法)

六. 指令(#set、#if、#foreach、#include、#parse、#break、#stop、#macro、#define、#evaluate)

七、转义符

三、注释                            

  1. 行注释

## 行注释内容

  2. 块注释

#*
块注释内容1
块注释内容2
*#

  3. 文档注释

#**
文档注释内容1
文档注释内容2
*#

  踩过的坑 

    块注释和文档注释虽然均不输出到最终结果上,但会导致最终结果出现一行空行。使用行注释则不会出现此情况。

四、直接输出的内容                            

   也就是不会被引擎解析的内容。

#[[
直接输出的内容1
直接输出的内容2
]]#

五、引用                                   

   引用语句就是对引擎上下文对象中的属性进行操作。语法方面分为常规语法( $属性 )和正规语法( ${属性} )。在普通模式下上述两种写法,当引擎上下文对象中没有对应的属性时,最终结果会直接输出 $属性 或 ${属性} ,若要不输出则需要改写为 $!属性 和 $!{属性} 。

   1. 变量(就是引擎上下文对象的属性)

$变量名, 常规写法,若上下文中没有对应的变量,则输入字符串"$变量名"
${变量名}, 常规写法,若上下文中没有对应的变量,则输入字符串"${变量名}" 
$!变量名, 常规写法,若上下文中没有对应的变量,则输入空字符串"" 
$!{变量名}, 常规写法,若上下文中没有对应的变量,则输入空字符串""

   变量的命名规则:

     由字母、下划线(_)、破折号(-)和数字组成,而且以字母开头。

   变量的数据类型为:

Integer、Long等简单数据类型的装箱类型; String类型Object子类Object[] 数组类型,从1.6开始Velocity将数组类型视为 java.util.List 类型看待,因此模板中可调用 size() 、 get(int index) 和 isEmpty() 的变量方法; java.util.Collection子类; java.util.Map子类; java.util.Iterator对象; java.util.Enumeration对象。

   2. 属性(就是引擎上下文对象的属性的属性)

$变量名.属性, 常规写法
${变量名.属性}, 正规写法
$!变量名.属性, 常规写法
$!{变量名.属性}, 正规写法

    属性搜索规则:

     Velocity采用一种十分灵活的方式搜索变量的属性, 具体如下:

// 假如引用$var.prop,那么Velocity将对prop进行变形,然后在$var对象上尝试调用
// 变形和尝试的顺序如下
1. $var.getprop()
2. $var.getProp()
3. $var.get("prop")
4. $var.isProp()

// 对于$var.Prop则如下
1. $var.getProp()
2. $var.getprop()
3. $var.get("Prop")
4. $var.isProp()

因此获取 java.util.Map 对象的键值时可以简写为 $map.key ,Velocity会自动转为 $map.get("key") 来搜索!

 3. 方法(就是引擎上下文对象的属性的方法)

$变量名.方法([入参1[, 入参2]*]?), 常规写法
${变量名.方法([入参1[, 入参2]*]?)}, 正规写法
$!变量名.方法([入参1[, 入参2]*]?), 常规写法
$!{变量名.方法([入参1[, 入参2]*]?)}, 正规写法

   引用方法实际就是方法调用操作,关注点返回值入参副作用的情况如下:

   1. 方法的返回值将输出到最终结果中

   2. 入参的数据类型

$变量 或 $属性,数据类型参考第一小节;
范围操作符(如:[1..2]或[$arg1..$arg2]),将作为java.util.ArrayList处理
字典字面量(如:{a:"a",b:"b"}),将作为java.util.Map处理
数字字面量(如:1),将自动装箱或拆箱匹配方法定义中的int或Integer入参

  3. 副作用

// 若操作如java.util.Map.put方法,则会修改Java代码部分中的Map对象键值对
$map.put("key", "new value")

六、指令                              

  指令主要用于定义重用模块、引入外部资源、流程控制。指令以 # 作为起始字符。

1. #set:向引擎上下文对象添加属性或对已有属性进行修改

  格式: #set($变量 = 值) ,具体示例如下:

#set($var1 = $other)
#set($var1.prop1 = $other)
#set($var = 1)
#set($var = true)
#set($var = [1,2])
#set($var = {a:"a", b:"b"})
#set($var = [1..3])
#set($var = [$arg1..$arg2])
#set($var = $var1.method())
#set($var = $arg1 + 1)
#set($var = "hello")
#set($var = "hello $var1") // 双引号可实现字符串拼接(coffeescript也是这样哦!),假设$var1为fsjohnhuang,则$var为hello fsjohnhuang
#set($var = 'hello $var1') // 单引号将不解析其中引用,假设$var1为fsjohnhuang,则$var为hello $var1

  作用域明显是全局有效的。

2. #if:条件判断

  格式:  

#if(判断条件)
  .........
#elseif(判断条件)
  .........
#else
  .........
#end

  通过示例了解判断条件:

#if($name)   //$name不为false、null和undefined则视为true
$name
#elseif($job == "net") // ==和!=两边的变量将调用其toString(),并对比两者的返回值
Net工程师
#elseif($age <= 16) // 支持<=,>=,>,<逻辑运算符
未成年劳工
#elseif(!$married) // 支持!逻辑运算符
未婚
#elseif($age >= 35 && !$married) // 支持&&,||关系运算符
大龄未婚青年
#end

  3. #foreach:循环

  格式:

#foreach($item in $items)
    ..........
#end

$item 的作用范围为#foreach循环体内。

$items 的数据类型为 Object[]数组 、 [1..2] 、 [1,2,3,4] 、 {a:"a",b:"b"} 和含 public Iterator iterator() 方法的对象,具体如下:

java.util.Collection子类,Velocity会调用其iterator方法获取Iterator对象
java.util.Map子类,Velocity会调用value()获取Collection对象,然后调用调用其iterator方法获取Iterator对象
java.util.Iterator对象,直接将该Iterator对象添加到上下文对象中时,由于Iterator对象为只进不退的操作方式,因此无法被多个#foreach指令遍历
java.util.Enumeration对象,直接将该Enumeration对象添加到上下文对象中时,由于Iterator对象为只进不退的操作方式,因此无法被多个#foreach指令遍历

内置属性$foreach.count ,用于指示当前循环的次数,从0开始。可以通过配置项 directive.foreach.maxloops 来限制最大的循环次数,默认值为-1(不限制)。

示例——使用Vector和Iterator的区别:

  模板:

#macro(show)
#foreach($letter in $letters)
$letter
#end
#end
#show()
#show(

  Java代码部分:

Vector<String> v = new Vector<String>();
v.add("a");
v.add("b");
VelocityContext ctx = new VelocityContext();
ctx.put("letters",v);
Template t = Velocity.getTemplate("模板路径");
StringWriter sw = new StringWriter();
t.merge(ctx,sw);
System.out.println(sw.toString());
// 结果
// a
// b
// a
// b

ctx.put("letters",v.iterator());
// 结果
// a
// 

  4. #break:跳出循环

#foreach($item in $items)
#if($item == "over")
#break;
#end
$item
#end

5. #stop:中止模板解析操作

#set($cmd="stop")
$cmd
#if($cmd == "stop")
#stop
#end
$cmd  // 该语句将不执行

  6. #include:引入外部资源(引入的资源不被引擎所解析)

  格式: #include(resource[ otherResource]*)

  resource、otherResource可以为单引号或双引号的字符串,也可以为$变量,内容为外部资源路径。注意为相对路径,则以引擎配置的文件加载器加载路径作为参考系,而不是当前模板文件的路径为参考系。

  7. #parse:引入外部资源(引入的资源将被引擎所解析)

  格式: #parse(resource)

  resource可以为单引号或双引号的字符串,也可以为$变量,内容为外部资源路径。注意为相对路径,则以引擎配置的文件加载器加载路径作为参考系,而不是当前模板文件的路径为参考系。

  由于#parse指令可能会引起无限递归引入的问题,因此可通过配置项 directive.parse.max.depth来限制最大递归引入次数,默认值为10.

  8. #macro:定义重用模块(可带参数)

  定义格式:

#macro(宏名 [$arg[ $arg]*]?)
   .....
#end

  调用格式:

#宏名([$arg[ $arg]]?)

  示例1——定义与调用位于同一模板文件时,无需遵守先定义后使用的规则

#log("What a happy day")
#macro(log $msg)
log message: $msg
#end

  示例2——定义与调用位于不同的模板文件时,需要遵守先定义后使用的规则

## 模板文件macro.vm
#macro(log $msg)
log message: $msg
#end
## 模板文件main.vm
#parse("macro.vm")
#log("What a happy day")

  原理解析:Velocity引擎会根据模板生成语法树并缓冲起来然后再执行,因此宏定义和调用位于同一模板文件时,调用宏的时候它已经被引擎识别并初始化了(类似js中的hosit)。

                若定义与调用位于不同的模板文件中时,由于 #parse 是引擎解析模板文件时才被执行来引入外部资源并对其中的宏定义进行初始化,因此必须遵循先定义后使用的规则。

  我们可配置全局宏库,配置方式如下:

Properties props = new Properties();
// velocimacro.library的值为模板文件的路径,多个路径时用逗号分隔
// velocimacro.library的默认值为VM_global_library.vm 
props.setProperty("velocimacro.library", "global_macro1.vm,global_macro2.vm");
VelocityEngine ve = new VelocityEngine(props);

   另外#macro还有另一种传参方式——$!bodyContent

#macro(say)
$!bodyContent
#end
#@say()Hello World#end
// 结果为Hello World

  9. #define:定义重用模块(不带参数)

  示例:

#define($log)
hello log!
#end
$log

  可视为弱版#macro,一般不用,了解就好了。

  10. #evaluate:动态计算

  示例:

#set($name = "over")
#evalute("#if($name=='over')over#{else}not over#end") // 输出over

  一般不用,了解就好了。

七、转义符                            

通过 \ 对 $ 和 #进行转义,导致解析器不对其进行解析处理。

#set($test='hello')
$test ## 结果:hello
\$test ## 结果:$test
\\$test ## 结果:\hello
\\\$test ## 结果:\$test

$!test ## 结果:hello
$\!test ## 结果:$!test
$\\!test ## 结果:$\!test
$\\\!test ## 结果:$\\!test

八、总结                             

  VTL语法部分KO了,接下来就是模板与宿主环境通信——核心在引擎上下文对象(VelocityContext)上!

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏技术小站

编程填空:第i位替换 编程填空:第i位取反 编程填空:左边i位取反

写出函数中缺失的部分,使得函数返回值为一个整数,该整数的第i位和m的第i位相同,其他位和n相同。

2261
来自专栏别先生

python的学习和使用

1560
来自专栏Zephery

2017-03-01学习笔记

知识点 1.java中只有值传递,没有引用传递http://guhanjie.iteye.com/blog/1683637 2.final关键字 final修饰...

3678
来自专栏Android开发指南

7:多线程

3048
来自专栏程序员的SOD蜜

C#中?与??的区别

起初我也不知道C#中有??操作符,今天张鹏在查看我的MVC示例程序的时候问了这个问题,检查代码后发现,下面的代码是VS2010在生成MVC应用程序自己添加的: ...

2367
来自专栏数据结构与算法

3185 队列练习 1

3185 队列练习 1 时间限制: 1 s 空间限制: 128000 KB 题目等级 : 黄金 Gold 题目描述 Description 给定一...

3106
来自专栏从零开始学 Web 前端

09 - JavaSE之线程

PS: 如果我们没有 new一个 Thread 对象出来,而是直接使用 MyThread 的 run 方法(mt.run()),这就是方法调用,而不是启动线程了...

1415
来自专栏云霄雨霁

设计模式----状态模式

1560
来自专栏python3

Python语句-if.....else......

似乎所有的条件语句都使用if.....else.....,它的作用可以简单地概括为非此即彼,满足条件A则执行A的语句,否则执行B语句,python的if.......

1002
来自专栏转载gongluck的CSDN博客

Lua学习笔记

--Lua笔记-- --0.Lua开篇-- --http://www.cnblogs.com/stephen-liu74/archive/2012/06/11/...

6156

扫码关注云+社区

领取腾讯云代金券