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 VelocityApache Velocity2026/10/15

Apache Velocity Tools 3.1 通用工具与工具箱配置详解

目标:逐项实测 velocity-tools-generic 3.1 常用工具的方法调用与回显,覆盖 tools.xml、tools.properties、ToolboxFactory 三种配置路线及全部配置陷阱,命令与输出均可照抄复现。

一、环境信息

项目值
操作系统Ubuntu 24.04.5 LTS
登录账号root
JDKOpenJDK 17.0.20.1(Temurin 发行版)
Maven3.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、NumberToolapplication
日期DateTool、ComparisonDateToolapplication
显示与转义DisplayTool、EscapeToolapplication
集合处理SortTool、CollectionTool、AlternatorTool、ConversionToolapplication
反射与字段FieldTool、ClassToolapplication
资源与日志ResourceTool、LogToolapplication
迭代与循环LoopToolrequest
链接LinkToolrequest
结构化数据JsonTool、XmlToolrequest
渲染与导入RenderTool、ImportToolrequest
上下文与解析ContextTool、ValueParserrequest(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: &lt;a href=&quot;x&quot;&gt;a &amp; b&lt;/a&gt;
url/unurl: quzuo+net | quzuo net
xml: &lt;root&gt;1 &lt; 2&lt;/root&gt;
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&amp;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 可对集合作合计与均值。两个参数序注意点:

  1. roundTo 第一个参数是小数位数、第二个是数值,实测 $math.roundTo(2, 3.14159) 输出 3.14,写反会得到 2.0。
  2. 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 三组高频方法实测结论:

  1. format 第一个参数是 java.text.SimpleDateFormat 模式,第二个是日期对象。
  2. get(String) 只接受格式模式串,不接受字段名:$date.get('yyyy') 输出 2026,而 $date.get('year') 会因模式非法抛 IllegalArgumentException: Illegal pattern character 'e'(实测踩坑)。
  3. 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

链接构造工具,链式方法实测:

  1. path 设置路径、uri 设置完整 URI、param 追加查询参数、anchor 追加锚点,输出 /docs/index.vm?id=42&amp;tag=velocity#top。
  2. 默认 XHTML 模式开启,查询参数分隔符 & 输出为 &,直接放 HTML 属性里无需再转义;需要裸 & 时调用 setXHTML(false) 或改用 append。
  3. 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,下级元素三类:

元素位置说明
toolboxtools 下scope 属性取 application 或 request,一个文件可写多个
tooltoolbox 下class 必填全限定类名;key 可省略,省略时取工具类默认键
datatools 下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 数据。

八、配置陷阱与排查清单

以下九条全部为本文实测复现的坑,按发生频率排列:

  1. new ToolManager() 后未调 configure,所有工具原样输出——ToolManager 默认不加载任何配置。
  2. 自动加载只认系统属性 org.apache.velocity.tools,期望 ToolManager(true) 自动扫 classpath 下的 tools.xml 不会发生。
  3. request 默认作用域工具(LoopTool、LinkTool、JsonTool、XmlTool 等)放进 application 工具箱,configure 抛 InvalidScopeException: Toolbox with scope 'application' may not contain a ...。
  4. tools.properties 缺 tools. 前缀或把工具写成 tools.作用域.键.class 三段式,解析出空工具箱且无任何报错,必须用 DumpProbe 转储核对。
  5. tools.xml 里 tool 的属性写成 property 子元素(value 属性式或体写式)均不生效,属性必须作为 tool 元素的 XML 属性。
  6. DateTool.get('year') 把 year 当格式模式解析,抛 Illegal pattern character 'e';字段取值应使用 get('yyyy') 这类模式串。
  7. MathTool.roundTo 与 DateTool.toDate 的参数序是反直觉的:roundTo(小数位数, 数值)、toDate(格式, 值),写反分别得到错误数值与静默 null。
  8. LinkTool 的 set(Object, Object) 是双参方法,单参路径要用 path 或 uri,写错整条链式调用原样输出。
  9. exec:java 偶发插件类加载异常(缺 org.apache.velocity.util.StringUtils),与业务代码无关,用 dependency:copy-dependencies 加 java -cp 直跑替代。

九、参考资料

  1. Velocity Tools 3.1 用户指南:https://velocity.apache.org/tools/3.1/
  2. Maven Central 构件页:https://central.sonatype.com/artifact/org.apache.velocity.tools/velocity-tools-generic/3.1
  3. 本文实测工程:《Apache Velocity Tools 3.1 快速入门与集成示例》第三、四节提供完整 pom 与 tools.xml。
  4. 专栏前置文档:《Apache Velocity 快速入门与集成示例》《Apache Velocity 配置参数详解》。