Velocity开发指南

简介

Velocity 历史悠久的免费java模板引擎。官网:velocity.apache.org/

Velocity是基于Java的模板引擎,这是一种简单而强大的开发工具,可让您轻松创建和呈现用于格式化和显示数据的文档

*.vm :Velocity 模板文件

VTL : Velocity Template Language

使用Velocity模板引擎时的需要关注两部分:Velocity模板Java代码调用

Velocity模板由VTL引擎上下文对象构成;

Java代码调用部分则负责初始Velocity引擎构建引擎上下文对象加载Velocity模板启动模版渲染

而Velocity模板与Java代码调用部分通信的纽带就是引擎上下文对象了。

Velocity被移植到不同的平台上,如.Net的NVelocity和js的Velocity.js,虽然各平台在使用和实现上略有差别,但大部分语法和引擎核心的实现是一致的,因此学习成本降低不少哦。

版本要求

Velocity2.1以上版本要求jdk1.8以上

Velocity2.2的maven 依赖

<dependency>  
<groupId>org.apache.velocity</groupId>  
<artifactId>velocity-engine-core</artifactId>  
<version>2.2</version>
</dependency><dependency>  
<groupId>org.apache.velocity.tools</groupId>  
<artifactId>velocity-tools-generic</artifactId>  
<version>3.0</version>
</dependency><dependency>  
<groupId>org.apache.velocity.tools</groupId>  
<artifactId>velocity-tools-view</artifactId>  
<version>3.0</version>
</dependency><dependency>  
<groupId>org.apache.velocity.tools</groupId> 
 <artifactId>velocity-tools-view-jsp</artifactId> 
 <version>3.0</version>
</dependency>复制代码

其它所有版本官方下载地址:archive.apache.org/dist/veloci…

Velocity1.7要求jdk1.4版本以上。官网地址:velocity.apache.org/engine/1.7/

1.7 api doc地址:tool.oschina.net/apidocs/api…

快速入门

Example2.java

import java.io.StringWriter;
import org.apache.velocity.app.Velocity;
import org.apache.velocity.VelocityContext;

public class Example2
{
    public static void main( String args[] )
    {
        /* 首先,初始化运行时引擎,使用默认的配置 */

        Velocity.init();

        /* 创建Context对象,然后把数据放进去 */

        VelocityContext context = new VelocityContext();

        context.put("name", "Velocity");
        context.put("project", "Jakarta");

        /* 渲染模板 */

        StringWriter w = new StringWriter();

        Velocity.mergeTemplate("testtemplate.vm", context, w );
        System.out.println(" template : " + w );

        /* 渲染字符串 */

        String s = "We are using $project $name to render this.";
        w = new StringWriter();
        Velocity.evaluate( context, w, "mystring", s );
        System.out.println(" string : " + w );
    }
}复制代码

testtemplate.vm

Hi! This $name from the $project project.复制代码

总结

首先,你还是需要先创建一个context,放进你需要的数据。

然后合并内容

Velocity的Java编码

以下内容转自:www.cnblogs.com/fsjohnhuang… 肥仔John

模板与宿主环境通信

模板指的是使用VTL编写的Velocity模板,宿主环境指的是Java代码调用部分。而两者通信的纽带就是引擎上下文对象( VelocityContext实例 ),下面是常用的 VelocityContext实例 方法。

// 构造函数,入参为上下文的键值对集
VelocityContext(Map context)
// 添加上下文的键值对
Object put(String key, Object value)
// 从上下文获取指定键的值
Object get(String key)
// 检查上下文中是否存在指定的键值对
boolean containsKey(Object key)
// 获取所有键
Object[] getKeys()
// 移除指定键
Object remove(Object key)
// 获取上下文链中邻近的上下文对象
Context getChainedContext()复制代码

宿主环境向模板传值

// 1. 通过构造函数传值
HashMap<String, String> baseCtx = new HashMap<String, String>();
baseCtx.put("version", "1");
VelocityContext ctx = new VelocityContext(baseCtx);

// 2. 通过put传值
ctx.put("author", "fsjohnhuang");复制代码

注意键值对中值的数据类型为

Integer、Long等简单数据类型的装箱类型;

String类型

Object子类

Object[] 数组类型,从1.6开始Velocity将数组类型视为 java.util.List 类型看待,因此模板中可调用 size() 、 get(intindex) 和 isEmpty() 的变量方法;

java.util.Collection子类;

java.util.Map子类;

java.util.Iterator对象;

java.util.Enumeration对象。

除此之外,我们还可以将一个静态类赋予到上下文对象中,如 java.lang.Math静态类

ctx.put("Math", java.lang.Math.class);复制代码

模板向宿主环境传值

1. 通信示例1——通过引擎上下文对象获取变量

模板文件frm.vm

#set($action="./submit")
<form action="$action">
  .........
</form>复制代码

Java代码部分

VelocityContext ctx = new VelocityContext();
VelocityEngine ve = new VelocityEngine();
StringWriter sw = new StringWriter();
ve.mergeTemplate("frm.vm", ctx, sw);
String actionStr = ctx.get("action");
System.out.println(actionStr); // 显示./submit复制代码

2. 通信示例2——通过副作用修改变量、属性值

模板文件change.vm

$people.put("john", "john huang")
#set($version = $version + 1)复制代码

Java代码部分

VelocityContext ctx = new VelocityContext();
ctx.put("version", 1);
HashMap<String, String> people = new HashMap<String,String>();
ctx.put("people", people);
VelocityEngine ve = new VelocityEngine();
StringWriter sw = new StringWriter();
ve.mergeTemplate("change.vm", ctx, sw);
System.out.println(ctx.get("version")); // 显示2
System.out.println(people.get("john")); //显示john huang复制代码

上述示例表明在模板中对引用类型实例进行操作时,操作结果将影响到该引用类型实例本身,因此必须谨慎操作。

引擎上下文链

也叫容器链,目前最常用的就是提供层次数据访问和工具箱

VelocityContext context1 = new VelocityContext();

context1.put("name","Velocity");
context1.put("project", "Jakarta");
context1.put("duplicate", "I am in context1");

VelocityContext context2 = new VelocityContext( context1 );

context2.put("lang", "Java" );
context2.put("duplicate", "I am in context2");

template.merge( context2, writer );复制代码

所谓引擎上下文链就是将原有的上下文对象赋予给新建的上下文对象,从而达到上下文内的键值对复用。具体代码如下:

VelocityContext ctx1 = new VelocityContext();
ctx1.put("name", "fsjohuang");
ctx1.put("version", 1);
VelocityContext ctx2 = new VelocityContext(ctx1);
ctx2.put("version", 2);

System.out.println(ctx2.get("name")); // 显示fsjohnhuang
System.out.println(ctx2.get("version")); // 显示2复制代码

就是当前上下文对象没有该键值对时,则查询上下文链的对象有没有该键值对,有则返回,无则继续找链上的其他上下文对象,直到找到该键值对或遍历完所有链上的上下文对象。

但VelocityContext实例除了put、get方法外,还有remove、getKeys、containsKey方法,它们的行为又是如何的呢?下面我们通过源码来了解吧!

官网中涉及的java编码部分

自定义属性

/opt/templates

...

import java.util.Properties;
 ...

public static void main( String args[] )
{
    /* 首先,我们还是初始化运行时引擎  */

    Properties p = new Properties();
    p.setProperty("file.resource.loader.path", "/opt/templates");
    Velocity.init( p );

 ...复制代码

虽然Velocity允许你创建自己的容器类来满足特殊的需求和技术(比如像一个直接访问LDAP服务器的容器),一个叫VelocityContext的基本实现类已经作为发行版的一部分提供给你。

VelocityContext适合所有的一般需求,所以我们强烈推荐你使用VelocityContext这个容器。只有在特殊情况和高级应用中,才需要你扩展或者创建你自己的容器实现。

for和foreach()遍历对象的支持

Velocity支持几种集合类型在VTL中使用foreach()语句:

Object []

普通对象数组 如果一个类中提供了迭代器接口,Velocity会自动包装你的数组

Velocity现在允许模板设计者把数组当作定长链表来处理(Velocity 1.6中就是这样)

java.util.Collection

Velocity通过iterator()方法返回一个迭代器在循环中使用

java.util.Map

Velocity通过接口的values()方法返回一个Collection接口,iterator()方法在它上面调用来检索用于循环的迭代器。

java.util.Iterator

目前只是暂时支持,迭代器不能重置

如果一个未初始化的迭代器被放进了容器,并且在多个foreach()语句中使用,如果第一个foreach()失败了,后面的都会被阻塞,因为迭代器不会重启

java.util.Enumeration

和java.util.Iterator一样

对于Iterator和Enumeration,推荐只有在万不得已的情况下才把它们放进容器,你也应该尽可能地让Velocity找到合适的、可复用的迭代接口。

Vector v = new Vector();
v.addElement("Hello");
v.addElement("There");

context.put("words", v.iterator() );//不推荐
context.put("words", v );复制代码

对静态类的支持

context.put("Math", Math.class);

这样你就可以在模板中用$Math引用调用java.lang.Math中的任何公有静态方法。

java.lang.Math这样的类不提供任何公有的构造函数,但是它包含了有用的静态方法

Servlet使用Velocity

web.xml 中配置Velocity

velocity.apache.org/tools/devel…

<!-- Define Velocity template handler -->
<servlet>
  <servlet-name>velocity</servlet-name>
  <servlet-class>
    org.apache.velocity.tools.view.VelocityViewServlet
  </servlet-class>

  <!-- 
    Unless you plan to put your tools.xml and velocity.properties
    under different folders or give them different names, then these
    two init-params are unnecessary.  The
    VelocityViewServlet will automatically look for these files in
    the following locations.
    -->
  <init-param>
    <param-name>org.apache.velocity.toolbox</param-name>
    <param-value>/WEB-INF/tools.xml</param-value>
  </init-param>

  <init-param>
    <param-name>org.apache.velocity.properties</param-name>
    <param-value>/WEB-INF/velocity.properties</param-value>
  </init-param>
</servlet>

<!-- Map *.vm files to Velocity -->
<servlet-mapping>
  <servlet-name>velocity</servlet-name>
  <url-pattern>*.vm</url-pattern>
</servlet-mapping>复制代码

tools.xml就像定义了一个工具箱,里面放着很多工具,比如有个“扳手”。

具体示例:考虑考虑让我们的朋友乔恩(Jon)从真实的工具箱中抓取我们的“扳手”。乔恩只需要知道我们想要哪个扳手。他不需要知道扳手做什么,也不需要知道我们打算如何做。

Velocity Toolbox的工作方式与上面的例子相同,我们仅需指定所需的工具,然后Velocity引擎就可以在vm模板中使用任何在工具箱Tool.xml中定义好的公共方法来处理其余的工作。

PipeWrench.java

public class PipeWrench {
    public String getSize() {
        return "Large Pipe Wrench!";
    }
}复制代码

tools.xml

<?xml version="1.0"?>
<tools>
  <toolbox scope="application">
    <tool key="wrench" class="PipeWrench"/>
  </toolbox>
</tools>复制代码

.vm模板中可以使用:

$wrench.size.

VM模板

官方VTL指南:

velocity.apache.org/engine/2.2/…

VTL: Velocity Template Language

以下内容转自:www.cnblogs.com/fsjohnhuang… 肥仔John

注释

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. 方法的返回值将输出到最终结果中

  1. 入参的数据类型

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

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

指令

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

#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复制代码

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

#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复制代码

#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()复制代码

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
//复制代码

#break:跳出循环

#foreach($item in $items)
#if($item == "over")
#break;
#end
$item
#end复制代码

#stop:中止模板解析操作

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

#include引入外部资源

(引入的资源不被引擎所解析)

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

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

#parse引入外部资源

(引入的资源将被引擎所解析)

格式: #parse(resource)

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

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

#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复制代码

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

#define($log)
hello log!
#end
$log复制代码

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

#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复制代码

模板实践

内容引自:www.cnblogs.com/fsjohnhuang… 肥仔

示例结果是生成如下的html表单:

<form action="./submit">
<div>
  <label for="title">标题:</label>
  <input type="text" id="title" name="title"/>
</div>
<div>
  <label for="brief">摘要:</label>
  <input type="text" id="brief" name="brief"/>
</div>
<div>
  <label for="sex">性别:</label>
  <select id="sex" name="sex">
    <option value="0"></option>
    <option value="1"></option>
  </select>
</div>
<div>
  <label for="job">职业:</label>
  <select id="job" name="job">
    <option value="0">Java工程师</option>
    <option value="1">Net工程师</option>
  </select>
</div>
</form>复制代码

引入依赖项——velocity-1.7-dep.jar

模板文件frm.vm

##表单模板
##@author fsjohnhuang
##@version 1.0
## 引入外部模板文件
#parse('macro.vm')
## 主逻辑
<form action="$action">
#foreach($input in $inputs)
#input($input.title $input.id)
#end
#foreach($select in $selects)
#select($select.title $select.id $select.items)
#end
</form>复制代码

模板文件macro.vm

## 生成input表单元素区域的宏
#macro(input $title $id)
<div>
  <label for="$id">$title</label>
  <input type="text" id="$id" name="$id"/>
</div>
#end
## 生成select表单元素区域的宏
#macro(select $title $id $items)
<div>
  <label for="$id">$title</label>
  <select id="$id" name="$id">
## VTL指令紧贴左侧才能确保结果的排版正常(不会有多余空格)
#foreach($key in $items.keySet())
    <option value="$key">$items.get($key)</option>
#end
  </select>
</div>
#end复制代码

Java代码

public static void main(String[] args) {
        // 初始化模板引擎
        Properties props = new Properties();
        props.put("file.resource.loader.path", ".\\vm");
        VelocityEngine ve = new VelocityEngine(props);
        // 配置引擎上下文对象
        VelocityContext ctx = new VelocityContext();
        ctx.put("action", "./submit");
        ArrayList<HashMap<String, String>> inputs = new ArrayList<HashMap<String,String>>();
        HashMap<String, String> input1 = new HashMap<String, String>();
        input1.put("id", "title");
        input1.put("title", "标题:");
        inputs.add(input1);
        HashMap<String, String> input2 = new HashMap<String, String>();
        input2.put("id", "brief");
        input2.put("title", "摘要:");
        inputs.add(input2);
        ctx.put("inputs", inputs);
        ArrayList<HashMap<String, Object>> selects = new ArrayList<HashMap<String,Object>>();
        HashMap<String, Object> select1 = new HashMap<String, Object>();
        selects.add(select1);
        select1.put("id", "sex");
        select1.put("title", "性别:");
        HashMap<Integer, String> kv1 = new HashMap<Integer, String>();
        kv1.put(0, "男");
        kv1.put(1, "女");
        select1.put("items", kv1);
        HashMap<String, Object> select2 = new HashMap<String, Object>();
        selects.add(select2);
        select2.put("id", "job");
        select2.put("title", "职业:");
        HashMap<Integer, String> kv2 = new HashMap<Integer, String>();
        kv2.put(0, "Java工程师");
        kv2.put(1, "Net工程师");
        select2.put("items", kv2);
        ctx.put("selects", selects);
        // 加载模板文件
        Template t = ve.getTemplate("test.vm");
        StringWriter sw = new StringWriter();
        // 渲染模板
        t.merge(ctx, sw);
        System.out.print(sw.toString());
    }复制代码

参考文章

开发指南原文地址:

velocity.apache.org/engine/deve…

用户指南原文地址:

velocity.apache.org/engine/deve…

中文翻译开发指南地址:

ifeve.com/velocity-gu…

肥仔 john优秀网文地址:

www.cnblogs.com/fsjohnhuang…