# Velocity

## 前言

由于在做比较老的PE项目，代码模板大致类似，所以想做一个代码模板，进而想到了模板引擎。thymeleaf 与 velocity 以及FreeMarker 都可以完成需求，各种调研与试用之后决定使用并学习velocity，原因有以下几种：

* thymeleaf评价较差，虽然是spring boot推荐的项目
* 网上可以找到的性能测试结果，velocity与FreeMarker都比较强，thymeleaf 次之
* thymeleaf相比velocity或者其他模板引擎的主要优势主要在于兼容xml语言，可以前端静态预览，但是相应的使用体检就变得较差，并且此功能对于我的代码生成器来说没什么作用
* 现有的很多的代码生成器都是使用velocity（比如人人的代码生成器），可以用来参考
* FreeMarker方面，与Velocity同样常用，功能也同样强大，但是感觉可读性不如Velocity，所以再三考虑最终选择了Velocity

> 注意，Spring Boot 1.4以后就不支持Velocity了，对于我来说这是一个致命缺点

## 简介

Apache Velocity 是一个基于java的模板引擎（template engine）。它允许任何人仅仅简单的使用模板语言（template language）来引用由java代码定义的对象。可用于生成网页、SQL、PostScript以及其他模板的输出，其它系统的集成组件等。

### 应用场景

* Web 应用：开发者在不使用 JSP 的情况下，可以用 Velocity 让 HTML 具有动态内容的特性
* 源代码生成：Velocity 可以被用来生成 Java 代码、SQL 或者 PostScript等代码生成工具 自动
* Email：很多软件的用户注册、密码提醒或者报表都是使用 Velocity 来自动生成的 转换 xml

### 相关资源

* 官方网站： [http://velocity.apache.org](https://gitee.com/link?target=http%3A%2F%2Fvelocity.apache.org%2F)

## 使用

### 添加依赖

```
<dependency>
    <groupId>org.apache.velocity</groupId>
    <artifactId>velocity</artifactId>
    <version>1.7</version>
</dependency>
```

### 使用步骤

1. 初始化Velocity，单例，无需多次执行
2. 创建上下文对象
3. 添加变量到Context对象中
4. 选择模板
5. Merge 合并模板与数据，完成输出

```
import java.io.StringWriter;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.Template;
import org.apache.velocity.app.Velocity;
import org.apache.velocity.exception.ResourceNotFoundException;
import org.apache.velocity.exception.ParseErrorException;
import org.apache.velocity.exception.MethodInvocationException;

// 初始化Velocity
Velocity.init();
// 创建上下文对象
VelocityContext context = new VelocityContext();
// 添加变量到Context对象中
context.put( "name", new String("Velocity") );
// 选择模板
Template template = null;
try {
  template = Velocity.getTemplate("mytemplate.vm");
} catch( ResourceNotFoundException rnfe ){
  // couldn't find the template
} catch( ParseErrorException pee ) {
  // syntax error: problem parsing the template
} catch( MethodInvocationException mie ) {
  // something invoked in the template
  // threw an exception
} catch( Exception e ){ }

// Merge 合并模板与数据，完成输出
StringWriter sw = new StringWriter();
template.merge( context, sw );
```

### 使用规约

不得使用\`\`org.apache.velocity.runtime `包下的`Runtime, RuntimeConstants, RuntimeSingleton or RuntimeInstance\`，这些类仅供Velocity内部使用

可以使用的是`org.apache.velocity.app` 下的所有类，如果此包下缺少必要的功能，建议修改

### 单例与非单例

`Velocity.init()`方法会创建一个单例的`VelocityEngine`模板引擎对象，所以，他们拥有相同的`properties`配置，有时需要不同的配置，此时可以通过手动创建多例模板引擎对象来实现：

```
VelocityEngine ve = new VelocityEngine();
ve.setProperty(VelocityEngine.RUNTIME_LOG_NAME, "mylog");
ve.setProperty(RuntimeConstants.RESOURCE_LOADER, "classpath");
ve.setProperty("classpath.resource.loader.class", ClasspathResourceLoader.class.getName());
ve.init();
Template t = ve.getTemplate("foo.vm");
```

### Context上下文

Context是Velocity核心，是Java层与模板层直接数据的载体。你的模板需要什么数据，只需要将其放入到其中，由模板引擎合并。

Context由`VelocityContext`类定义，主要提供了两个方法：

```
public Object put(String key, Object value);
public Object get(String key);
```

#### 支持#foreach

要想支持模板的#foreach循环，传入的Java对象，会依据情况做出相应处理：

* 数组， `java.util.List` 遍历方式一致
* `java.util.Collection`，将会使用`iterator()`方法遍历
* `java.util.Map`，依赖于接口的`values()`方法来获取Collection接口，并通过`iterator()`遍历
* `java.util.Iterator`，小心使用，迭代器不可重置性，迭代器只可使用一次，意味着多个`#foreach`只有第一个会成功
* `java.util.Enumeration`, 通过枚举的`hasMoreElements()`与`nextElement()`方法操作，此方式与Iterator由一样的问题

#### 放入'静态类'

有些工具类不可以被实例化，但是提供了一些公共静态方法，我们可以将其class对象放入Context中，以便使用:

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

#### Context Chaining

可以翻译为连续上下文，多个上下文之间含有链式关系，如下：

```
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 );
```

上述输出结果为 `duplicate= "I am in context2"` 。并且context2中包含所有context1 的变量，但是context1中的变量`duplicate`仍然存在，并且值仍为`I am in context1`

**此功能常用于分层数据访问以及工具集。**

### Velocity

Velocity类是一个帮助类，帮助用户轻松使用模板引擎。\*\*Velocity运行时引擎是一个单例，同一JVM中运行所有Velocity用户资源、日志以及其他服务。\*\*所以运行时引擎只会初始化一次，其余的尝试将会被忽略。

#### 配置模板引擎

Velocity运行时引擎提供了五种常用方法，用于配置引擎：

* `setProperty( String key, Object o )` 设置属性配置
* `Object getProperty( String key )` 获取属性配置
* `init()` 使用默认配置初始化引擎
* `init( Properties p )` 使用指定的Properties对象初始化引擎
* `init( String filename )` 指定配置文件路径初始化引擎

> 重复调用init方法无效，Velocity只会被初始化一次

#### 渲染模板

Velocity 针对模板渲染提供了以下几个方法：

```
// 使用context作为参数渲染模板并输入到write流中，其中instring以及instream是模板信息，logTag则是在发生错误时用作日志消息的模板名称的字符串
evaluate( Context context, Writer out, String logTag, String instring )
evaluate( Context context, Writer writer, String logTag, InputStream instream )

// 直接访问velocimacro, vmName代表宏的名称、namespace代表宏的命名空间，调用velocimacro args的键
invokeVelocimacro( String vmName, String namespace, String params[], Context context, Writer writer )

// 建议使用此种方式，根据模板名称解析合并模板， 即从模板根路径下寻找模板
mergeTemplate( String templateName, Context context, Writer writer )

// 根据模板名称判断模板是否存在
boolean templateExists( String name )
```

> 模板根路径是由`p.setProperty("resource.loader.file.path", "/opt/templates");`属性决定的

#### 异常

在解析时，Velocity可能会抛出异常，这些异常集成RuntimeException：

* `ResourceNotFoundException` 找不到模板资源
* `ParseErrorException` 解析时发现VTL语法错误
* `TemplateInitException` 模板初始化异常，通常是宏和指令初始化的问题
* `MethodInvocationException`: 模板调用的方法抛出异常

### 日志

Velocity2.0开始使用SLF4J作为日志门面。启用日志需要：

1. 保证slf4j的jar存在
2. 保证slf4j具体实现的jar存在

Velocity的日志皆是以 `org.apache.velocity`作为父路径的，其中包含如下几个子路径：

* `directive`, and `directive.`指令相关日志
* `parser` 模板解析日期
* `loader`, and `loader.` 资源加载日志
* `macro` 宏相关
* `rendering` 模板渲染的相关内容，包含内省、方法调用
* `event` 事件日志

如果想要自己定义日志文件名称，可以通过`runtime.log.name`属性指定

### 缩进控制

2.o 可以通过\`\`parser.space\_gobbling 属性控制缩进，（属性名称翻译空间吞噬？？？）共有

* none 无空间吞噬
* bc (aka backward compatible) 向后兼容
* lines 默认值
* structured 结构化

四种可选值。

#### none

不吞噬任何空间，指令所在行附近的空间，不做任何删除操作：

```
#set($foo = 'foo')↲
<table>↲
</table>↲
↲ ## 此行仍然保留原有的换行
<table>↲
</table>↲
```

#### bc

此模式会吃掉指令后面所有的空白符：

```
#set($foo = 'foo')↲
<table>↲
</table>↲
<table>↲
</table>↲
```

#### lines

吃掉当前行的空白符：

```
#set($foo = 'foo')↲
<table>↲
</table>↲
<table>↲
</table>↲
```

#### structured

[http://velocity.apache.org/engine/2.1/developer-guide.html#space-gobbling](https://gitee.com/link?target=http%3A%2F%2Fvelocity.apache.org%2Fengine%2F2.1%2Fdeveloper-guide.html%23space-gobbling)

没看明白，待解决

### ResourceLoader

资源加载是Velocity的重要一部分，它负责加载模板或者是非模板资源(`#include`)，主要位于包`org.apache.velocity.runtime.resource.loader`。Velocity目前共有四种资源加载器：

* ```
  FileResourceLoader
  ```

  从文件获取资源，包含属性

  * `resource.loader.file.path=rootpath1,path2,path3..` 资源加载根路径
  * `resource.loader.file.cache=true/false` 加载资源是否缓存
  * `resource.loader.file.modification_check_interval=100`每隔100s检查一次配置是否需要更新
  * 这是默认资源加载器
* ```
  JarResourceLoader
  ```

  从特定的jar包中获取资源，与

  ```
  FileResourceLoader
  ```

  很相似，只是绑定到了jar中

  * `resource.loader.jar.path` 与 `resource.loader.file.path`作用一致
  * 其他属性与 `FileResourceLoader`一致
* `ClasspathResourceLoader` 从类加载器中获取资源，通常是放入类路径下的模板文件，通常用于Servlet容器
* ```
  URLResourceLoader 
  ```

  从URL连接获取资源：

  * `resource.loader.url.root` url根路径
  * `resource.loader.url.cache` 是否缓存
  * `resource.loader.url.modification_check_interval` 检查间隔 秒
  * `resource.loader.url.timeout` 超时时间 -1 代表超时时间为正无穷
* `DataSourceResourceLoader` 从数据库中获取

### ApplicationAttribute

应用程序属性是一种能够和运行时实例相关联（通过Velocity引擎或者Velocity单例）的键值对。它可以被Velocity引擎中任何运行时实例访问。这个特性是为了在应用层和Velocity引擎的特定部件之间通信的应用程序而设计的，比如日志记录器，资源加载器，资源管理器 。

> 举例，可以通过ApplicationAttribute 设置UrlResourceLoader的超时时间，外部在使用应用程序时，可以改变这个值，像是程序配置入口

`Velocity`类中包含操作`ApplicationAttribute`的相关方法：

* `Velocity.setApplicationAttribute( Object key, Object value )`
* `Velocity.getApplicationAttribute( Object key )`

调用时间没有要求，不一定要在`init()`方法之前调用。

### 事件处理

Velocity含有细粒度的事件处理系统，允许自定义引擎的操作。这些事件是同步的。所有事件处理处理程序接口都位于`org.apache.velocity.app.event.implement`包下，具体使用需要查看javadoc，这里只列举几例：

#### IncludeEventHandler

IncludeEventHandler是针对include指令的事件监听：

```
public IncludeEventHandler extends EventHandler {
    public String includeEvent( Context context,
                                String includeResourcePath, 
                                String currentResourcePath, 
                                String directiveName );
}
```

共有两个实现类：

* `IncludeNotFound (org.apache.velocity.app.event.implement)` 未找到资源事件处理
* `IncludeRelativePath (org.apache.velocity.app.event.implement)`

### 方法参数转换

`UberspectorImpl` 或者`SecureUberspector`会根据需要对方法参数进行类型转换：

* boolean to number: `true` 1, false 0
* boolean to string: "true" and "false"
* number to boolean: 0 `false`, 其他 `true`
* number to string
* string to boolean: "true" 转为 `true`, 其他字符串将会被转换为false(包含"false") `false`
* string to number: 如果字符串不是数字格式，则会抛出异常
* 字符串下转型 : 如果不符合预期数字类型，抛出异常
* string to enum constant: 如果不是预期枚举，抛出异常
* string to locale (since 2.1)

下图中， implicit代表Java隐士转换，none代表无法转换，strict代表严格转换（通常指代相同类型）， 而explicit代表期望转换（转换失败会抛出异常）

![1564224777160](https://github.com/yangsx95/notes/blob/master/programming-language/java/web%E7%BC%96%E7%A8%8B/velocity/README.assets/1564224777160.png)

#### 扩展类型转换

扩展类型转换，可以通过 `Converter<T>`接口实现：

```
package mypackage;

import java.util.Date;
import org.apache.velocity.util.introspection.*;

public class MyUberspector extends UberspectorImpl
{
    public void init()
    {
        super.init();
        // 注册转换器
        getConversionHandler().addConverter(Integer.class, Date.class, new ConvertToDate());
        getConversionHandler().addConverter(Long.class, Date.class, new ConvertToDate());
    }
	
	// 日期转换
    public static class ConvertToDate extends Converter<Date>
    {
        @Override
        public Date convert(Object o)
        {
            return new Date(((Number)o).longValue());
        }
    }
}
```

除了通过继承`UberspectorImpl`类注册类型转换器，还可以

* 继承`org.apache.util.introspection.TypeConversionHandlerImpl`类，并在构造函数中添加转换器（2.0废弃）
* 配置属性`runtime.conversion.handler.instance=apache.util.introspection.TypeConversionHandler`

### 编码

Velocity允许在模板的基础上指定编码:

```
org.apache.velocity.app.Velocity

public static Template getTemplate(String name, String encoding)

public static boolean mergeTemplate( String templateName, String encoding, Context context, Writer writer )
```

### 集成到JSR223

Velocity可以集成到Java Scripting Language Framework 即 JSR223中：

```
// get script manager, create a new Velocity script engine factory and get an engine from it
ScriptEngineManager manager = new ScriptEngineManager();
manager.registerEngineName("velocity", new VelocityScriptEngineFactory());
ScriptEngine engine = manager.getEngineByName("velocity");

System.setProperty(VelocityScriptEngine.VELOCITY_PROPERTIES, "path/to/velocity.properties");
String script = "Hello $world";
Writer writer = new StringWriter();
engine.getContext().setWriter(writer);
Object result = engine.eval(script);
System.out.println(writer);
```

## VTL详解

Velocity Template Language，是Velocity的模板语言。

1. velocity的模板文件以`.vm`为拓展名
2. 模板中的所有关键字都以`#`号开头，可以加入大括号`#{set xx}`， `#{if} #{else}`
3. 模板中的所有变量都已`$`符号开头，同样可以加入大括号 ${name}
4. 如果想要输出`$`或者`#`需要在前面加上`\`进行转移，相同的，如果想要输出`\`则需要`\\`
5. `$数字`可以直接输出，比如`$1` 输出为`$1`

### 解析模板

```
 // 初始化模板引擎
VelocityEngine ve = new VelocityEngine();
ve.setProperty(RuntimeConstants.RESOURCE_LOADER, "classpath");
ve.setProperty("classpath.resource.loader.class", ClasspathResourceLoader.class.getName());
ve.init();

// 使用文件创建模板对象
Template t = ve.getTemplate("hello-velocity.vm");

// 初始化VelocityContext对象
VelocityContext ctx = new VelocityContext();
ctx.put("name", "Velocity");
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
ctx.put("list", list);

// 解析模板并输出结果
StringWriter sw = new StringWriter();
t.merge(ctx, sw);
System.out.println(sw.toString());
```

### 注释

#### 单行注释

```
## 这是单行注释
```

#### 多行注释

```
#*
这是多行注释
*#
```

#### 文档注释

```
#**
  这个代码是xx锤子用的
information:
@version 1.1
@author yangsx
*#
```

### 变量操作

velocity 中含有变量的概念，变量类型为弱类型。在执行模板时，模板可以从`VelocityContext`对象中获取其中存放的值，也可以从`#set($foo = "bar")`定义的变量中获取值。

#### 变量命名规范

Velocity变量与java类似，只允许字母、下划线以及数字组成，不能以数字开头，所以`$1abc`的输出结果就是`$1abc`。

#### 变量/属性输出

```
 // 获取模板文件
Template t = ve.getTemplate("基础语法.vm");

// 设置变量
VelocityContext ctx = new VelocityContext();
Obj obj = new Obj();
obj.setString("张三");
obj.setInteger(100);
obj.setArray(new String[]{"唐", "送", "元", "明", "清"});
obj.setList(Arrays.asList("魏", "晋", "南朝", "北朝"));
obj.setMap(new HashMap<String, String>(){{
    this.put("A", "1");
    this.put("B", "2");
    this.put("C", "3");
    this.put("D", "4");
}});
ctx.put("obj", obj);

StringWriter sw = new StringWriter();
t.merge(ctx, sw);
```

对应的模板为：

```
变量的类型：

1. 字符类型:$obj.string
2. 数字类型:$obj.integer
3. 数组类型:$obj.array, 第0个元素为 $obj.array[0]
4. 集合类型:$obj.list, 第0个元素为 $obj.list[0]
5. map类型:$obj.map, key为A的值为 $obj.map.A
7. 引用类型直接输出toString方法: $obj
8. 获取方法返回值： $obj.getString()
```

得到的输出结果为：

```
变量的类型：

1. 字符类型:张三
2. 数字类型:100
3. 数组类型:[Ljava.lang.String;@45afc369, 第0个元素为 唐
4. 集合类型:[魏, 晋, 南朝, 北朝], 第0个元素为 魏
5. map类型:{A=1, B=2, C=3, D=4}, key为A的值为 1
7. 引用类型直接输出toString方法: 基础语法.Obj(string=张三, integer=100, array=[唐, 送, 元, 明, 清], list=[魏, 晋, 南朝, 北朝], map={A=1, B=2, C=3, D=4})
8. 获取方法返回值： 张三
```

> name是name是{name}的简写 二者没有什么实际区别

#### 数组操作

除了可以通过方括号获取数组元素，Velocity引擎也提供了几个方便的方法，便于我们操作数组：

```
$myarray.isEmpty() or $myarray.empty
$myarray.size()
$myarray.get(2)
$myarray.set(1, 'test')
```

#### 默认缺省值

在展示变量或者属性时，不可避免的出现空值，可以使用`|`设置缺省值：

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

#### 属性检测规则

假设有`$obj.name`，Velocity模板引擎会依次从以下几个地方从obj对象中寻找此变量的值：

1. `getname()`方法
2. `getName()`方法
3. `get("name")`方法，通常用于map
4. `isName()`方法

如果是`$obj.Name`属性，则会略有不同：

1. `getName()`方法
2. `getname()`方法
3. `get("Name")`方法，通常用于map
4. `isName()`方法

#### 变量定义赋值

此外，Velocity 也支持在模板内定义变量， 可以使用`set`关键字声明一个变量：

```
#set(${name} = "velocity")
```

其中`"velocity"`就是变量值，你可以设置：

* `"Hello World"` 字符串字面量
* `10` 数字字面量
* `["唐", "宋", "元"]` 数组类型
* 属性、方法引用
* ArrayList、Map

### 方法调用

可以在VTL中调用传入的Java对象的public方法：

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

#### 支持vararg methods

```
## void setAttributes(String... args)
$person.setAttributes("Strange", "Weird", "Excited")
```

### 渲染

Velocity中的所有动态内容，比如属性、变量等，最终都会被渲染为字符串。

#### 严格渲染模式

Strict渲染模式适用于某些特殊用途，在此模式下，任何模棱两可的表达方式，都会被模板引擎视为异常：

* 变量未定义或者 中不存在，引用此变量将会抛出异常
* 此变量未定义或者VelocityContext中不存在，或者此变量为null，引用此变量的属性会为null
* `#if`指令如果引用变量未定义或者为null，则返回false: `#if ($foo)#end ## False`，`# if(!$foo) #end ## True`

### 指令详解

指令以 `#`开头\~

#### set

set指令用于给变量赋值或者定义变量，被赋予的值可以是：

* Number literal 数字字面量
  * `#set( $monkey.Number = 123 )`
* String literal 字符串字面量:
  * `#set( $monkey.Friend = "monica" )`
* ArrayList
  * `#set( $monkey.Say = ["Not", $my, "fault"] )`
  * `$monkey.Say.get(0)`
* Map
  * `#set( $monkey.Map = {"banana" : "good", "roast beef" : "bad"})`
  * `$monkey.Map.banana`
  * `$monkey.Map.get("banana")`
* Variable reference 变量引用
  * `#set( $monkey = $bill )`
* Property reference 属性引用
  * `#set( $monkey.Blame = $whitehouse.Leak )`
* Method reference 方法引用
  * `#set( $monkey.Plan = $spindoctor.weave($web) )`

**右侧运算**

**除此之外，右侧值也可以进行简单的运算：**

```
#set( $value = $foo + 1 )
#set( $value = $bar - 1 )
#set( $value = $foo * $bar )
#set( $value = $foo / $bar )
```

**双引号和单引号**

```
#set( $foo = "bar" )
#set( $a = '$foo' )
#set( $b = "$foo" )
$a ## $foo
$b ## bar
```

双引号会解析引号内的变量， 此功能可以通过`velocity.properties`的`stringliterals.interpolate=false`属性关闭。

如果有大段代码不需要解析，可以通过如下方式设置：

```
#[[
#foreach ($woogie in $boogie)
  不会被解析 $woogie
#end
]]#
```

输出结果:

```
#foreach ($woogie in $boogie)
  不会被解析 $woogie
#end
```

#### 条件判断

**if/elseif/else**

```
#if( $foo < 10 )
    xxx
#elseif( $foo == 10 )
    xxx
#elseif( $bar == 6 )
    xxx 
#else
    xxx 
#end
```

if的条件的判断遵循以下规则：

* boolean 类型：true/false
* string 类型：null与空字符被视为false
* number类型： 0 视为false，其他被视为true
* 集合类型： 集合长度`size()`为0

**逻辑运算符**

* 逻辑与： `#if( $foo && $bar )`
* 逻辑或： `#if( $foo || $bar )`
* 逻辑非： `#if( !$foo )`

#### 循环

```
<ul>
#foreach( $product in $allProducts )
    <li>$product</li>
#end
</ul>
```

此种方式还可以对Java对象中的所有属性进行遍历，操作方式与操作map一致：

```
<ul>
#foreach( $key in $allProducts.keySet() )
    <li>Key: $key -> Value: $allProducts.get($key)</li>
#end
</ul>
```

**判断循环是否是第一次**：

```
#foreach( $customer in $customerList )
    #if( $foreach.first ) There are customer: #end
#end
```

**判断循环是否是最后一次：**

```
#foreach( $customer in $customerList )
    #if( $foreach.hasNext ),#end
#end
```

**判断循环次数是否为0：**

```
#foreach( $customer in $customerList )
#else
空的集合
#end
```

**获取当前索引（从0开始）：**

```
#foreach( $customer in $customerList )
当前索引为：$foreach.index
#end
```

**获取当前循环到底几次（从1开始）**：

```
#foreach( $customer in $customerList )
当前索引为：$foreach.count
#end
```

**设置循环次数，防止故障循环逻辑：**

```
## -1代表无限制次数
directive.foreach.max_loops = -1
```

#### 控制语句

**break**

结束当前的执行范围，也是最直接的范围，比如`#foreach` `#parse`，`#evaluate`, `#define`, `#macro`或者根范围。

除此之外，break也可以跳出到指定的范围：比如，`#break($macro)`, `#break($foreach)`

**stop**

停止执行模板，通常用于调试或者提示，参数可以设置提示信息： `#stop('$foo 不在context中，无法继续执行')`

#### 灵活引用模板

**include**

include指令可以导入本地文件，并将其插入到指定的位置，本地文件的位置只能为`TEMPLATE_ROOT`定义的路径下：

```
#include( "one.txt" )
#include( "one.gif","two.txt","three.htm" )
#include( "greetings.txt", $seasonalstock )
```

**parse**

include可以引入本地的资源，而parse可以解析本地的VTL模板：

```
#parse( "parsefoo.vm" )
```

**define**

为变量赋值一个VTL代码块：

```
## 定义一个名称为 block的变量，此变量是一个VTL块，块中的内容为  Hello $who
#define( $block )Hello $who#end
## 为块中的$who设置值
#set( $who = 'World!' )
## 输出块的值
$block
```

#### velocimacros

`#macro`指令允许模板设计者定义VTL模板重复段，定义与使用宏的方式如下：

```
## 定义
#macro( d )
<tr><td></td></tr>
#end
## 使用
#d()
```

宏可以包含body，使用时需要制定body，如果不指定默认为空：

```
## 定义包含body的宏
#macro( d )
<tr><td>$!bodyContent</td></tr>
#end
## 使用
#@d()我是内容体#end
#d()  ## 没有数据的空的宏
```

宏也可以指定任意数量的参数，但是调用时必须指定相同数量的参数：

```
##定义一个包含两个参数的宏
#macro( macroname $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>
    #macroname( $color $greatlakes )
</table>
```

> macroname是定义的宏的名称

**参数类型**

VTL的宏共支持如下几种参数类型：

* Reference : 以$开头的任何变量
* String literal : 字符串字面量
* Number literal : 数字字面量
* IntegerRange : 数字范围，比如`[1..2]` , `[$foo .. $bar]`
* ObjectArray : 数组，`[ "a", "b", "c"]`
* boolean

**配置属性**

关于Velocimacro，有相关常用属性需要了解，以便灵活应用：

* `velocimacro.inline.allow`是否允许Velocimacro，值为true/false
* `velocimacro.library.autoreload` 控制Velocimacro是否自动加载，默认false，设置为true时，将会检查velocimacro 库是否发生更改，必要时进行重新加载。（前置条件：关闭缓存`resource.loader.file.cache = false`），用于开发环境
* `velocimacro.library.path` 模板库路径，默认会去查找`velocimacros.vtl`
* `velocimacro.inline.replace_global`，true/false,是否覆盖全局的macro(`velocimacro.library.path`中的macro)，如果macro名称相同
* `velocimacro.inline.local_scope`，true/false，如果为true，当前模板定义的micro尽可以被当前模板使用，用于限定作用域。
* 当两个模板定义了一个相同名称的宏时，需要将 `velocimacro.inline.replace_global`和`velocimacro.inline.local_scope`全都设置为true

## 参考

* [https://blog.csdn.net/tttzzztttzzz/article/details/90720877](https://gitee.com/link?target=https%3A%2F%2Fblog.csdn.net%2Ftttzzztttzzz%2Farticle%2Fdetails%2F90720877)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://yangsx95.gitbook.io/notes/programming-language/java/velocity.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
