Apache Velocity Tools 3.1 通用工具与工具箱配置详解
Apache Velocity Tools 3.1 通用工具与工具箱配置详解:逐项实测 MathTool、DateTool、NumberTool、DisplayTool、EscapeTool、LoopTool、SortTool、LinkTool、JsonTool、XmlTool、FieldTool、ClassTool 的方法与回显,并覆盖 tools.xml、tools.properties、ToolboxFactory 三种配置路线与全部实测踩坑。
Apache Velocity Tools 3.1 通用工具与工具箱配置详解
目标:逐项实测 velocity-tools-generic 3.1 常用工具的方法调用与回显,覆盖 tools.xml、tools.properties、ToolboxFactory 三种配置路线及全部配置陷阱,命令与输出均可照抄复现。
一、环境信息
| 项目 | 值 |
|---|---|
| 操作系统 | Ubuntu 24.04.5 LTS |
| 登录账号 | root |
| JDK | OpenJDK 17.0.20.1(Temurin 发行版) |
| Maven | 3.9.16 |
| 模板引擎 | velocity-engine-core 2.4.1 |
| 工具箱 | velocity-tools-generic 3.1 |
| 日志绑定 | slf4j-simple 1.7.36 |
| 工程目录 | /root/velocity-tools-demo(工程结构、pom 与依赖落盘同《Apache Velocity Tools 3.1 快速入门与集成示例》3.2、3.3 节) |
二、generic 工具总览
velocity-tools-generic-3.1.jar 内 org/apache/velocity/tools/generic/ 包下共有 29 个顶层类,其中 23 个是可直接使用的工具,实测清单:
| 类别 | 工具类 | 默认作用域 |
|---|---|---|
| 数学与数字 | MathTool、NumberTool | application |
| 日期 | DateTool、ComparisonDateTool | application |
| 显示与转义 | DisplayTool、EscapeTool | application |
| 集合处理 | SortTool、CollectionTool、AlternatorTool、ConversionTool | application |
| 反射与字段 | FieldTool、ClassTool | application |
| 资源与日志 | ResourceTool、LogTool | application |
| 迭代与循环 | LoopTool | request |
| 链接 | LinkTool | request |
| 结构化数据 | JsonTool、XmlTool | request |
| 渲染与导入 | RenderTool、ImportTool | request |
| 上下文与解析 | ContextTool、ValueParser | request(ValueParser 默认不注册) |
| 基类与内部类 | SafeConfig、FormatConfig、LocaleConfig、ImportSupport、Alternator、JsonContent | 不单独使用 |
默认作用域依据是 jar 内置的默认配置文件 /org/apache/velocity/tools/generic/tools.xml,提取命令与文件内容(输出节选:许可头与注释已折叠):
cd /root && jar xf ~/.m2/repository/org/apache/velocity/tools/velocity-tools-generic/3.1/velocity-tools-generic-3.1.jar org/apache/velocity/tools/generic/tools.xml
<tools>
<data type="number" key="TOOLS_VERSION" value="3.0-SNAPSHOT"/>
<data type="boolean" key="GENERIC_TOOLS_AVAILABLE" value="true"/>
<toolbox scope="application">
<tool class="org.apache.velocity.tools.generic.ClassTool"/>
<tool class="org.apache.velocity.tools.generic.CollectionTool"/>
<tool class="org.apache.velocity.tools.generic.ComparisonDateTool"/>
<tool class="org.apache.velocity.tools.generic.DisplayTool"/>
<tool class="org.apache.velocity.tools.generic.EscapeTool"/>
<tool class="org.apache.velocity.tools.generic.FieldTool"/>
<tool class="org.apache.velocity.tools.generic.LogTool"/>
<tool class="org.apache.velocity.tools.generic.MathTool"/>
<tool class="org.apache.velocity.tools.generic.NumberTool"/>
<tool class="org.apache.velocity.tools.generic.ResourceTool"/>
<tool class="org.apache.velocity.tools.generic.AlternatorTool"/>
<tool class="org.apache.velocity.tools.generic.ConversionTool"/>
<tool class="org.apache.velocity.tools.generic.SortTool"/>
</toolbox>
<toolbox scope="request">
<tool class="org.apache.velocity.tools.generic.ContextTool"/>
<tool class="org.apache.velocity.tools.generic.ImportTool"/>
<tool class="org.apache.velocity.tools.generic.JsonTool"/>
<tool class="org.apache.velocity.tools.generic.LinkTool"/>
<tool class="org.apache.velocity.tools.generic.LoopTool"/>
<tool class="org.apache.velocity.tools.generic.RenderTool"/>
<tool class="org.apache.velocity.tools.generic.XmlTool"/>
</toolbox>
</tools>
两个细节:一是 3.1 的 jar 里这份默认配置的 TOOLS_VERSION 数据仍是 3.0-SNAPSHOT,属构件内遗留值,不影响功能;二是这份默认配置里没有单独的 DateTool(ComparisonDateTool 继承了它的全部能力),也没有 MarkupTool。
💡 技术拓展:application 与 request 的取舍标准是状态——无状态工具放 application 复用单例,带游标或请求上下文的工具(LoopTool 的迭代指针、LinkTool 的链接状态)必须放 request,放错会在 configure 阶段抛 InvalidScopeException。
三、实测模板与运行方式
3.1 工具矩阵模板 deep.vm
/src/main/resources/templates/deep.vm 覆盖本文涉及的全部工具调用:
=== MathTool ===
add/sub/mul/div: $math.add(19, 23) | $math.sub(19, 23) | $math.mul(6, 7) | $math.div(10, 4)
idiv/mod: $math.idiv(10, 4) | $math.mod(10, 3)
ceil/floor/round/roundTo: $math.ceil(3.2) | $math.floor(3.8) | $math.round(3.5) | $math.roundTo(2, 3.14159)
abs/pow/max/min: $math.abs(-7) | $math.pow(2, 10) | $math.max(3, 9, 5) | $math.min(3, 9, 5)
getTotal/getAverage: $math.getTotal($nums) | $math.getAverage($nums)
toInteger/toDouble: $math.toInteger('42') | $math.toDouble('3.14')
=== NumberTool ===
integer/number: $number.integer(1234567.891) | $number.number(1234567.891)
currency/percent: $number.currency(1234.5) | $number.percent(0.2568)
format: $number.format('0.00', 3.14159)
=== DateTool ===
format: $date.format('yyyy-MM-dd HH:mm:ss', $now)
get: yyyy=$date.get('yyyy') MM=$date.get('MM') dd=$date.get('dd')
toDate+format: $date.format('yyyy-MM-dd', $date.toDate('yyyy-MM-dd', '2026-01-01'))
=== ComparisonDateTool ===
#set($past = $date.toDate('yyyy-MM-dd HH:mm:ss', '2026-07-01 10:00:00'))
whenIs: $compdate.whenIs($past, $now)
difference: $compdate.difference($past, $now)
reversed: $compdate.whenIs($now, $past)
=== DisplayTool ===
truncate: $display.truncate('Apache Velocity Tools Generic 工具箱实测', 18)
alt-null: $display.alt($nothing, '默认值')
alt-val: $display.alt('有值', '默认值')
capitalize: $display.capitalize('hello world')
list: $display.list($names)
printf: $display.printf('%s 有 %d 个工具', 'velocity-tools', 8)
=== EscapeTool ===
html: $esc.html('<a href="x">a & b</a>')
url/unurl: $esc.url('quzuo net') | $esc.unurl('quzuo+net')
xml: $esc.xml('<root>1 < 2</root>')
sql: $esc.sql("it's a test")
java: $esc.java('a"b')
javascript: $esc.javascript("he said 'hi'")
symbols: quote=$esc.quote dollar=$esc.dollar hash=$esc.hash
=== LoopTool ===
#foreach( $i in $loop.watch($nums) )
count=$loop.count index=$loop.index value=$i first=$loop.isFirst() last=$loop.isLast()
#end
=== SortTool ===
sorted-nums: #foreach($i in $sorter.sort($nums))$i #end
#set($users = [{"name":"quzuo","age":30},{"name":"alice","age":25},{"name":"bob","age":28}])
sorted-users: #foreach($u in $sorter.sort($users, ['age']))$u.name:$u.age #end
=== LinkTool ===
set+param+anchor: $link.path('/docs/index.vm').param('id','42').param('tag','velocity').anchor('top')
set+param: $link.uri('https://quzuo.net/search').param('q','velocity tools')
=== JsonTool ===
#set($js = '{"name":"quzuo","stars":[86,92,78]}')
name: $json.parse($js).get('name')
star2: $json.parse($js).get('stars').get(2)
keys: $json.parse($js).keySet()
=== XmlTool ===
#set($xs = '<root><item id="1">alpha</item><item id="2">beta</item></root>')
size: $xml.parse($xs).find('item').size()
attr: $xml.parse($xs).find('item').attr('id')
text: $xml.parse($xs).find('item').get(1)
=== FieldTool / ClassTool ===
max-int: $field.get('java.lang.Integer.MAX_VALUE')
class-name: $class.inspect('java.time.LocalDate').name
class-full: $class.inspect('java.time.LocalDate').fullName
3.2 主类与上下文数据
/src/main/java/com/example/DeepProbe.java,结构同快速入门篇的 ToolsDemo,模板换为 deep.vm,并补充 names 变量:
package com.example;
import org.apache.velocity.Template;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.context.Context;
import org.apache.velocity.tools.ToolManager;
import java.io.StringWriter;
import java.util.Date;
import java.util.List;
public class DeepProbe {
public static void main(String[] args) {
VelocityEngine engine = new VelocityEngine();
engine.setProperty("resource.loaders", "classpath");
engine.setProperty("resource.loader.classpath.class",
"org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
engine.setProperty("resource.default_encoding", "UTF-8");
engine.init();
ToolManager manager = new ToolManager();
manager.configure("tools.xml");
Context context = manager.createContext();
context.put("now", new Date());
context.put("nums", List.of(5, 3, 9, 1, 7));
context.put("names", List.of("alpha", "beta", "gamma"));
Template template = engine.getTemplate("templates/deep.vm", "UTF-8");
StringWriter writer = new StringWriter();
template.merge(context, writer);
System.out.println(writer);
}
}
tools.xml 与快速入门篇 4.1 节完全一致(application 作用域 9 个工具,request 作用域 loop、json、xml、link 4 个工具)。
3.3 运行与完整回显
cd /root/velocity-tools-demo && mvn -q package && LANG=C.UTF-8 java -cp 'target/classes:libs/*' com.example.DeepProbe
实测回显全文(3.1 节模板的对应输出,后文按工具逐段解读):
=== MathTool ===
add/sub/mul/div: 42 | -4 | 42 | 2.5
idiv/mod: 2 | 1
ceil/floor/round/roundTo: 4 | 3 | 4 | 3.14
abs/pow/max/min: 7 | 1024 | 9 | 3
getTotal/getAverage: 25 | 5
toInteger/toDouble: 42 | 3.14
=== NumberTool ===
integer/number: 1,234,568 | 1,234,567.891
currency/percent: $1,234.50 | 26%
format: 3.14
=== DateTool ===
format: 2026-10-15 13:07:31
get: yyyy=2026 MM=10 dd=15
toDate+format: 2026-01-01
=== ComparisonDateTool ===
whenIs: 3 months later
difference: 3 months
reversed: 3 months earlier
=== DisplayTool ===
truncate: Apache Velocity...
alt-null: 默认值
alt-val: 有值
capitalize: Hello world
list: alpha, beta and gamma
printf: velocity-tools 有 8 个工具
=== EscapeTool ===
html: <a href="x">a & b</a>
url/unurl: quzuo+net | quzuo net
xml: <root>1 < 2</root>
sql: it''s a test
java: a\"b
javascript: he said \'hi\'
symbols: quote=" dollar=$ hash=#
=== LoopTool ===
count=1 index=0 value=5 first=true last=false
count=2 index=1 value=3 first=false last=false
count=3 index=2 value=9 first=false last=false
count=4 index=3 value=1 first=false last=false
count=5 index=4 value=7 first=false last=true
=== SortTool ===
sorted-nums: 1 3 5 7 9
sorted-users: alice:25 bob:28 quzuo:30
=== LinkTool ===
set+param+anchor: /docs/index.vm?id=42&tag=velocity#top
set+param: https://quzuo.net/search?q=velocity+tools
=== JsonTool ===
name: quzuo
star2: 78
keys: [name, stars]
=== XmlTool ===
size: 2
attr: 1
text: <item id="2">beta</item>
=== FieldTool / ClassTool ===
max-int: 2147483647
class-name: LocalDate
class-full: java.time.LocalDate
四、逐工具实测详解
4.1 MathTool
数学运算工具,add/sub/mul/div 接受任意数字或数字字符串,div 返回小数、idiv 返回整除、mod 取模;ceil/floor/round 返回 Integer;getTotal 与 getAverage 可对集合作合计与均值。两个参数序注意点:
roundTo第一个参数是小数位数、第二个是数值,实测$math.roundTo(2, 3.14159)输出 3.14,写反会得到 2.0。max/min支持多参数变长调用,实测$math.max(3, 9, 5)输出 9。
4.2 NumberTool
数字格式化工具,默认按美式 locale 输出:integer 四舍五入到整数并带千分位(1234567.891 输出 1,234,568),number 保留原值并带千分位,currency 输出 $1,234.50,percent 输出 26%,format 接受任意 java.text.DecimalFormat 模式('0.00' 输出 3.14)。需要本地化格式时给构造传入 locale 配置,或在 format 三参重载里显式指定 Locale。
4.3 DateTool 与 ComparisonDateTool
DateTool 三组高频方法实测结论:
- format 第一个参数是 java.text.SimpleDateFormat 模式,第二个是日期对象。
- get(String) 只接受格式模式串,不接受字段名:
$date.get('yyyy')输出 2026,而$date.get('year')会因模式非法抛 IllegalArgumentException: Illegal pattern character 'e'(实测踩坑)。 - toDate 第一个参数是格式、第二个是字符串值,实测
$date.toDate('yyyy-MM-dd', '2026-01-01')正常解析,写反返回 null 且不报错,后续引用全部静默失效。
ComparisonDateTool 继承 DateTool 并新增口语化时长描述:whenIs(基准, 目标) 描述目标相对基准的方向与量,实测 whenIs($past, $now) 输出 3 months later、参数对调后输出 3 months earlier;difference 输出不含方向的绝对量 3 months。两者都返回 Comparison 对象,模板里直接输出即调 toString。
4.4 DisplayTool
展示修饰工具:truncate(Object, int) 按 18 个字符截断并补省略号(实测输出 Apache Velocity...,默认后缀为 ...);alt(值, 替代值) 对 null 或空串返回替代值(实测 alt-null 输出 默认值);capitalize 首字母大写;list 把集合拼成 alpha, beta and gamma 这种自然语言列表;printf 支持标准格式串。
4.5 EscapeTool
全部转义方法实测通过:html 转义标签与引号、xml 转义尖括号、url 与 unurl 互逆(空格变加号)、sql 把单引号翻倍、java 与 javascript 转义引号和反斜杠、quote/dollar/hash 三个常量方法输出 "、$、#。模板里要输出字面 $ 或 # 时用 $esc.dollar、$esc.hash 比转义指令更直观。
4.6 LoopTool
循环增强工具,$loop.watch($nums) 包装集合并提供 count(1 起计数)、index(0 起下标)、isFirst、isLast、hasNext 等游标方法,实测回显见 3.3 节 LoopTool 段。它是有游标状态的工具,必须放在 request 作用域;同一模板里多次 watch 时,游标方法作用于最近一次 watch 返回的迭代器。其余常用方法还有 skip(n) 跳过元素、stopTo(label) 停止指定迭代、sync 同步遍历多个集合。
4.7 SortTool
排序工具:sort(集合) 按自然序排(实测 5 3 9 1 7 输出 1 3 5 7 9);sort(集合, [属性名列表]) 按对象属性排,实测对三个 Map 组成的列表按 age 排序输出 alice:25 bob:28 quzuo:30,Map 的键与 Bean 属性都支持(底层走 commons-beanutils)。它是无状态工具,放 application 作用域即可。
4.8 LinkTool
链接构造工具,链式方法实测:
- path 设置路径、uri 设置完整 URI、param 追加查询参数、anchor 追加锚点,输出
/docs/index.vm?id=42&tag=velocity#top。 - 默认 XHTML 模式开启,查询参数分隔符 & 输出为 &,直接放 HTML 属性里无需再转义;需要裸 & 时调用 setXHTML(false) 或改用 append。
- 3.x 的 set(Object, Object) 是双参方法(等价 param),单参设置路径要用 path 或 uri,写
$link.set('/docs/index.vm')会因方法不存在导致整条链原样输出(实测踩坑)。
4.9 JsonTool 与 XmlTool
两者都是 request 作用域。JsonTool.parse 接受 JSON 字符串(也可 read 从路径读、fetch 从 URL 拉),get 支持键名与下标连续取值,keySet 返回键集合,实测输出 quzuo、78、[name, stars]。XmlTool.parse 同样三入口,find 按 XPath 求值、attr 取属性、get(n) 定位节点后直接输出文本内容,实测 size 2、attr 1、text <item id="2">beta</item>。JsonTool 依赖 json-simple(3.1 传递依赖自带),XmlTool 用 JDK 内置解析器,两者都不需要额外引包。
💡 技术拓展:JsonTool 与 XmlTool 的取值语法不同——JsonTool 用 get('键') 逐层取,XmlTool 用 find('XPath') 一次定位;数据源是接口返回的 JSON 选 JsonTool,是配置文件或 SOAP 报文选 XmlTool。
4.10 FieldTool 与 ClassTool
反射类工具:FieldTool.get 传全限定类名加点分隔的静态字段名,实测 $field.get('java.lang.Integer.MAX_VALUE') 输出 2147483647;ClassTool.inspect 检查类元信息,name 返回 SimpleName(LocalDate)、fullName 返回全限定名(java.time.LocalDate),还提供 isInterface、isStatic、getSuper 等判定方法。两者都是只读工具,放 application 作用域。
五、tools.xml 配置详解
5.1 元素与属性
tools.xml 根元素 tools,下级元素三类:
| 元素 | 位置 | 说明 |
|---|---|---|
| toolbox | tools 下 | scope 属性取 application 或 request,一个文件可写多个 |
| tool | toolbox 下 | class 必填全限定类名;key 可省略,省略时取工具类默认键 |
| data | tools 下 | key 属性加 value 属性,或把值写在元素体里;type 属性可指定 string、number、boolean |
省略 key 的实测:tools-config.xml 里 NumberTool 未写 key,模板中 $number.currency(9876.5) 正常输出 $9,876.50,默认键来自工具类注解声明。
5.2 工具属性写法(实测踩坑)
DateTool 的默认输出格式由 format 属性控制,3.1 的正确写法是把属性直接写在 tool 元素上:
<tool class="org.apache.velocity.tools.generic.DateTool" key="date" format="yyyy/MM/dd"/>
配置后 $date 直接输出 2026/10/15(实测)。两个不生效的写法:<property name="format" value="yyyy/MM/dd"/> 子元素与 <property name="format">yyyy/MM/dd</property> 体写法,实测解析结果里都不出现 format 属性。验证方法是打印解析转储:
cd /root/velocity-tools-demo && java -cp 'target/classes:libs/*' com.example.DumpProbe tools-config.xml
DumpProbe 主类只有两行业务代码:
package com.example;
import org.apache.velocity.tools.config.ConfigurationUtils;
import org.apache.velocity.tools.config.FactoryConfiguration;
public class DumpProbe {
public static void main(String[] args) {
FactoryConfiguration fc = ConfigurationUtils.find(args[0]);
System.out.println(fc);
}
}
实测转储回显(节选:来源信息已折叠):
FactoryConfiguration from 2 sources including 2 data with 2 toolboxes:
Toolbox 'application' with 1 properties [scope -auto-> application; ] and 3 tools:
Unchecked Tool 'date' => org.apache.velocity.tools.generic.DateTool with 2 properties [format -auto-> yyyy/MM/dd; key -auto-> date; ]
Unchecked Tool 'math' => org.apache.velocity.tools.generic.MathTool with 1 properties [key -auto-> math; ]
Unchecked Tool 'number' => org.apache.velocity.tools.generic.NumberTool
Toolbox 'request' with 1 properties [scope -auto-> request; ] and 1 tools:
Unchecked Tool 'loop' => org.apache.velocity.tools.generic.LoopTool with 1 properties [key -auto-> loop; ]
Data 'appname' -auto-> Velocity Tools Demo
Data 'tools_count' -number-> 4
💡 技术拓展:转储里的 -auto-> 标记表示该值来自元素属性映射,Unchecked 表示尚未实例化校验;属性写没写进去,看这份转储比看渲染结果更快。
5.3 data 全局数据与完整示例
data 声明的键值会直接进入每个渲染上下文。完整示例 /src/main/resources/tools-config.xml:
<?xml version="1.0" encoding="UTF-8"?>
<tools>
<data key="appname" value="Velocity Tools Demo"/>
<data type="number" key="tools_count">4</data>
<toolbox scope="application">
<tool class="org.apache.velocity.tools.generic.DateTool" key="date" format="yyyy/MM/dd"/>
<tool class="org.apache.velocity.tools.generic.MathTool" key="math"/>
<tool class="org.apache.velocity.tools.generic.NumberTool"/>
</toolbox>
<toolbox scope="request">
<tool class="org.apache.velocity.tools.generic.LoopTool" key="loop"/>
</toolbox>
</tools>
配套模板 /src/main/resources/templates/config.vm:
appname=$appname tools_count=$tools_count
date-default: $date
date-explicit: $date.format('yyyy-MM-dd HH:mm:ss', $now)
number-default-key: $number.currency(9876.5)
loop: #foreach($i in $loop.watch($nums))$loop.count=$i #end
渲染主类 ConfigProbe 与 DeepProbe 结构一致,仅把模板换成 config.vm 并接受第一个参数作为配置文件名。实测回显:
appname=Velocity Tools Demo tools_count=4
date-default: 2026/10/15
date-explicit: 2026-10-15 13:07:39
number-default-key: $9,876.50
loop: 1=5 2=3 3=9 4=1 5=7
date-default 行证明 format 属性生效:默认 toString 已按 yyyy/MM/dd 输出。
六、properties 配置与系统属性自动加载
6.1 tools.properties 键式格式
properties 配置与 tools.xml 等价,键式规则(实测反推确认):
| 键模式 | 含义 |
|---|---|
| tools.toolbox | 作用域清单,逗号分隔 |
| tools.<作用域>.<键> = 全限定类名 | 声明工具 |
| tools.<作用域>.<键>.<属性名> = 值 | 工具属性 |
| tools.data.<键> = 值 | 全局数据 |
所有键必须带 tools. 前缀——加载器读取时先做 tools 前缀截取,缺前缀会解析成空配置且不报错;工具声明是平铺键值对,键里不能有点(写成 tools.application.date.class 会导致工具静默丢失)。/src/main/resources/tools.properties 完整内容:
tools.toolbox = application, request
tools.data.appname = Velocity Tools Properties Demo
tools.data.tools_count = 4
tools.application.date = org.apache.velocity.tools.generic.DateTool
tools.application.date.format = yyyy/MM/dd
tools.application.math = org.apache.velocity.tools.generic.MathTool
tools.application.number = org.apache.velocity.tools.generic.NumberTool
tools.request.loop = org.apache.velocity.tools.generic.LoopTool
加载方式与 xml 相同:
cd /root/velocity-tools-demo && java -cp 'target/classes:libs/*' com.example.ConfigProbe tools.properties
实测回显:
appname=Velocity Tools Properties Demo tools_count=4
date-default: 2026/10/15
date-explicit: 2026-10-15 13:07:39
number-default-key: $9,876.50
loop: 1=5 2=3 3=9 4=1 5=7
与 tools-config.xml 渲染结果一致。
💡 技术拓展:三种配置路线的选择——xml 适合结构复杂、工具多的场景;properties 适合运维改配置不重编译的场景;EasyFactoryConfiguration 适合配置随代码走、由框架统一组装的场景,见第七节。
6.2 系统属性自动加载
ToolManager(true) 的自动加载只认系统属性 org.apache.velocity.tools,属性值是配置文件路径:
cd /root/velocity-tools-demo && java -Dorg.apache.velocity.tools=/root/velocity-tools-demo/src/main/resources/tools.xml -cp 'target/classes:libs/*' com.example.SysPropProbe
SysPropProbe 主类:
package com.example;
import org.apache.velocity.tools.ToolManager;
import org.apache.velocity.tools.Toolbox;
public class SysPropProbe {
public static void main(String[] args) {
ToolManager manager = new ToolManager(true);
System.out.println("hasApplicationTools=" + manager.hasApplicationTools());
Toolbox app = manager.getApplicationToolbox();
if (app != null) {
System.out.println("keys=" + app.getKeys());
}
org.apache.velocity.tools.ToolContext ctx = manager.createContext();
Object date = ctx.get("date");
System.out.println("date=" + date);
}
}
实测回显:
hasApplicationTools=true
keys=[date, number, esc, field, sorter, display, math, compdate, class]
date=Oct 15, 2026, 1:07:40 PM
七、ToolboxFactory 编程式配置
7.1 EasyFactoryConfiguration 构建
/src/main/java/com/example/FactoryProbe.java:
package com.example;
import org.apache.velocity.tools.Toolbox;
import org.apache.velocity.tools.ToolboxFactory;
import org.apache.velocity.tools.config.EasyFactoryConfiguration;
import org.apache.velocity.tools.generic.EscapeTool;
import org.apache.velocity.tools.generic.MathTool;
public class FactoryProbe {
public static void main(String[] args) {
EasyFactoryConfiguration cfg = new EasyFactoryConfiguration(false);
cfg.data("appname", "Factory Demo");
cfg.toolbox("application")
.tool("math", MathTool.class)
.tool("esc", EscapeTool.class);
ToolboxFactory factory = new ToolboxFactory();
factory.configure(cfg);
System.out.println("hasTools(application)=" + factory.hasTools("application"));
Toolbox app = factory.createToolbox("application");
System.out.println("keys=" + app.getKeys());
MathTool math = (MathTool) app.get("math");
System.out.println("direct add(19, 23)=" + math.add(19, 23));
System.out.println("data.appname=" + factory.getData().get("appname"));
}
}
实测回显:
hasTools(application)=true
keys=[esc, appname, math]
direct add(19, 23)=42
data.appname=Factory Demo
ToolboxFactory 是 ToolManager 的底层件:createToolbox(作用域) 返回的 Toolbox 可直接 get 取出工具实例在 Java 侧调用,不必经过模板。
7.2 内置默认工具集
EasyFactoryConfiguration 的 addDefaultTools 一次性注册官方默认工具集,在 FactoryProbe 里追加以下片段:
EasyFactoryConfiguration defCfg = new EasyFactoryConfiguration(false);
defCfg.addDefaultTools();
ToolboxFactory defFactory = new ToolboxFactory();
defFactory.configure(defCfg);
System.out.println("default application keys=" + defFactory.createToolbox("application").getKeys());
System.out.println("default request keys=" + defFactory.createToolbox("request").getKeys());
实测回显:
default application keys=[date, sorter, log, TOOLS_VERSION, display, collection, convert, number, esc, field, alternator, math, text, GENERIC_TOOLS_AVAILABLE, class]
default request keys=[import, loop, xml, context, link, json, render]
默认集把 application 与 request 作用域的常用工具全部注册,键名即模板引用名;TOOLS_VERSION 与 GENERIC_TOOLS_AVAILABLE 是随默认集带入的 data 数据。
八、配置陷阱与排查清单
以下九条全部为本文实测复现的坑,按发生频率排列:
new ToolManager()后未调 configure,所有工具原样输出——ToolManager 默认不加载任何配置。- 自动加载只认系统属性 org.apache.velocity.tools,期望 ToolManager(true) 自动扫 classpath 下的 tools.xml 不会发生。
- request 默认作用域工具(LoopTool、LinkTool、JsonTool、XmlTool 等)放进 application 工具箱,configure 抛 InvalidScopeException: Toolbox with scope 'application' may not contain a ...。
- tools.properties 缺 tools. 前缀或把工具写成 tools.作用域.键.class 三段式,解析出空工具箱且无任何报错,必须用 DumpProbe 转储核对。
- tools.xml 里 tool 的属性写成 property 子元素(value 属性式或体写式)均不生效,属性必须作为 tool 元素的 XML 属性。
- DateTool.get('year') 把 year 当格式模式解析,抛 Illegal pattern character 'e';字段取值应使用 get('yyyy') 这类模式串。
- MathTool.roundTo 与 DateTool.toDate 的参数序是反直觉的:roundTo(小数位数, 数值)、toDate(格式, 值),写反分别得到错误数值与静默 null。
- LinkTool 的 set(Object, Object) 是双参方法,单参路径要用 path 或 uri,写错整条链式调用原样输出。
- exec:java 偶发插件类加载异常(缺 org.apache.velocity.util.StringUtils),与业务代码无关,用 dependency:copy-dependencies 加 java -cp 直跑替代。
九、参考资料
- Velocity Tools 3.1 用户指南:https://velocity.apache.org/tools/3.1/
- Maven Central 构件页:https://central.sonatype.com/artifact/org.apache.velocity.tools/velocity-tools-generic/3.1
- 本文实测工程:《Apache Velocity Tools 3.1 快速入门与集成示例》第三、四节提供完整 pom 与 tools.xml。
- 专栏前置文档:《Apache Velocity 快速入门与集成示例》《Apache Velocity 配置参数详解》。