> For the complete documentation index, see [llms.txt](https://yangsx95.gitbook.io/notes/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://yangsx95.gitbook.io/notes/programming-language/java/velocity.md).

# 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编程/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)
