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

相关资源

使用

添加依赖

使用步骤

  1. 初始化Velocity,单例,无需多次执行

  2. 创建上下文对象

  3. 添加变量到Context对象中

  4. 选择模板

  5. Merge 合并模板与数据,完成输出

使用规约

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

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

单例与非单例

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

Context上下文

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

Context由VelocityContext类定义,主要提供了两个方法:

支持#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 Chaining

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

上述输出结果为 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 针对模板渲染提供了以下几个方法:

模板根路径是由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

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

bc

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

lines

吃掉当前行的空白符:

structured

http://velocity.apache.org/engine/2.1/developer-guide.html#space-gobbling

没看明白,待解决

ResourceLoader

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

  • 从文件获取资源,包含属性

    • resource.loader.file.path=rootpath1,path2,path3.. 资源加载根路径

    • resource.loader.file.cache=true/false 加载资源是否缓存

    • resource.loader.file.modification_check_interval=100每隔100s检查一次配置是否需要更新

    • 这是默认资源加载器

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

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

    • resource.loader.jar.pathresource.loader.file.path作用一致

    • 其他属性与 FileResourceLoader一致

  • ClasspathResourceLoader 从类加载器中获取资源,通常是放入类路径下的模板文件,通常用于Servlet容器

  • 从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指令的事件监听:

共有两个实现类:

  • 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

扩展类型转换

扩展类型转换,可以通过 Converter<T>接口实现:

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

  • 继承org.apache.util.introspection.TypeConversionHandlerImpl类,并在构造函数中添加转换器(2.0废弃)

  • 配置属性runtime.conversion.handler.instance=apache.util.introspection.TypeConversionHandler

编码

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

集成到JSR223

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

VTL详解

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

  1. velocity的模板文件以.vm为拓展名

  2. 模板中的所有关键字都以#号开头,可以加入大括号#{set xx}#{if} #{else}

  3. 模板中的所有变量都已$符号开头,同样可以加入大括号 ${name}

  4. 如果想要输出$或者#需要在前面加上\进行转移,相同的,如果想要输出\则需要\\

  5. $数字可以直接输出,比如$1 输出为$1

解析模板

注释

单行注释

多行注释

文档注释

变量操作

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

变量命名规范

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

变量/属性输出

对应的模板为:

得到的输出结果为:

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

数组操作

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

默认缺省值

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

属性检测规则

假设有$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关键字声明一个变量:

其中"velocity"就是变量值,你可以设置:

  • "Hello World" 字符串字面量

  • 10 数字字面量

  • ["唐", "宋", "元"] 数组类型

  • 属性、方法引用

  • ArrayList、Map

方法调用

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

支持vararg methods

渲染

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

右侧运算

除此之外,右侧值也可以进行简单的运算:

双引号和单引号

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

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

输出结果:

条件判断

if/elseif/else

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

  • boolean 类型:true/false

  • string 类型:null与空字符被视为false

  • number类型: 0 视为false,其他被视为true

  • 集合类型: 集合长度size()为0

逻辑运算符

  • 逻辑与: #if( $foo && $bar )

  • 逻辑或: #if( $foo || $bar )

  • 逻辑非: #if( !$foo )

循环

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

判断循环是否是第一次

判断循环是否是最后一次:

判断循环次数是否为0:

获取当前索引(从0开始):

获取当前循环到底几次(从1开始)

设置循环次数,防止故障循环逻辑:

控制语句

break

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

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

stop

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

灵活引用模板

include

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

parse

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

define

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

velocimacros

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

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

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

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_globalvelocimacro.inline.local_scope全都设置为true

参考

最后更新于

这有帮助吗?