Apache Velocity 配置参数详解

解析 Apache Velocity 2.4.1 核心配置参数:覆盖资源加载器、编码、指令、宏、严格模式与日志六类配置,给出 Properties 与代码两种修改方式、新旧键名映射、逐项实测回显与可落地的生产配置模板。

Apache VelocityApache Velocity2026/10/12

Apache Velocity 配置参数详解

一、环境信息

项目值
操作系统Ubuntu 22.04.5 LTS(CentOS 8 实测行为一致)
登录账号root
JDKOpenJDK 17.0.20.1
Maven3.6.3
组件velocity-engine-core 2.4.1
辅助依赖slf4j-simple 1.7.36、velocity-tools-generic 3.1
测试工程/root/velocity-demo(Maven 工程)

二、前置条件

Velocity 无独立配置文件服务,配置通过 VelocityEngine 的 Properties 或 setProperty 传入。本篇基于以下测试工程展开。

工程 /root/velocity-demo/pom.xml(关键字段):

<dependencies>
    <dependency>
        <groupId>org.apache.velocity</groupId>
        <artifactId>velocity-engine-core</artifactId>
        <version>2.4.1</version>
    </dependency>
    <dependency>
        <groupId>org.apache.velocity.tools</groupId>
        <artifactId>velocity-tools-generic</artifactId>
        <version>3.1</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-simple</artifactId>
        <version>1.7.36</version>
    </dependency>
</dependencies>

测试模板与配置文件:

mkdir -p /root/velocity-demo/src/main/resources/templates
mkdir -p /etc/velocity-demo /opt/velocity-tpl /var/log/velocity

依次创建模板文件。/root/velocity-demo/src/main/resources/templates/macros.vm:

#macro(greet $who)
你好,$who!欢迎使用 Apache Velocity。
#end

/root/velocity-demo/src/main/resources/templates/strict-if.vm:

#if($undefinedRef)
条件为真
#else
未定义引用在条件指令中按 false 处理
#end

/root/velocity-demo/src/main/resources/templates/strict-ref.vm:

直接输出未定义引用:[$undefinedRef]
静默引用:[$!undefinedQuiet]

/root/velocity-demo/src/main/resources/templates/strict-quiet.vm:

静默引用(严格模式下):[$!undefinedQuiet]

/root/velocity-demo/src/main/resources/templates/items.vm:

#foreach($item in $items)
第 $foreach.count 件:$item.name
#end

/opt/velocity-tpl/hello-file.vm(FileResourceLoader 测试用,磁盘目录模板):

你好,张三!这是来自磁盘模板目录 /opt/velocity-tpl 的中文渲染。

三、参数查看与修改方式

3.1 代码方式(setProperty)

引擎初始化前逐项设置,适合配置项少的场景:

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

3.2 文件方式(Properties)

将配置写入独立文件,运行时加载。本篇测试程序从 /etc/velocity-demo/velocity-demo.properties 读取:

Properties props = new Properties();
try (FileInputStream in = new FileInputStream("/etc/velocity-demo/velocity-demo.properties")) {
    props.load(in);
}
VelocityEngine engine = new VelocityEngine();
engine.setProperties(props);
engine.init();

3.3 配置验证程序

完整验证程序 /root/velocity-demo/src/main/java/com/example/ConfigDemo.java,依次验证严格模式、循环上限、文件加载器、错误路径与日志行为:

package com.example;

import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.exception.VelocityException;

import java.io.File;
import java.io.FileInputStream;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

public class ConfigDemo {

    private static VelocityEngine classpathEngine(Properties props) {
        VelocityEngine engine = new VelocityEngine();
        engine.setProperties(props);
        engine.init();
        return engine;
    }

    private static Map<String, Object> item(String name) {
        Map<String, Object> item = new HashMap<>();
        item.put("name", name);
        return item;
    }

    public static void main(String[] args) throws Exception {
        String propsPath = args.length > 0 ? args[0] : "/etc/velocity-demo/velocity-demo.properties";
        Properties props = new Properties();
        try (FileInputStream in = new FileInputStream(propsPath)) {
            props.load(in);
        }
        System.out.println("已加载配置(" + propsPath + "):directive.foreach.max_loops="
                + props.getProperty("directive.foreach.max_loops", props.getProperty("directive.foreach.maxloops"))
                + ",runtime.strict_mode.enable=" + props.getProperty("runtime.strict_mode.enable",
                        props.getProperty("runtime.references.strict")));

        VelocityContext ctx = new VelocityContext();

        render(classpathEngine(props), ctx, "templates/strict-if.vm");

        try {
            render(classpathEngine(props), ctx, "templates/strict-ref.vm");
        } catch (VelocityException e) {
            System.out.println("[捕获异常] " + e.getClass().getName());
            System.out.println("[异常消息] " + e.getMessage().split("\n")[0]);
        }

        try {
            render(classpathEngine(props), ctx, "templates/strict-quiet.vm");
        } catch (VelocityException e) {
            System.out.println("[捕获异常] " + e.getClass().getName());
        }

        Properties noStrict = new Properties();
        noStrict.putAll(props);
        noStrict.setProperty("runtime.strict_mode.enable", "false");
        VelocityContext listCtx = new VelocityContext();
        List<Map<String, Object>> items = new ArrayList<>();
        items.add(item("商品1"));
        items.add(item("商品2"));
        items.add(item("商品3"));
        listCtx.put("items", items);
        render(classpathEngine(noStrict), listCtx, "templates/items.vm");

        VelocityEngine fileEngine = new VelocityEngine();
        fileEngine.setProperty("resource.loaders", "file");
        fileEngine.setProperty("resource.loader.file.class",
                "org.apache.velocity.runtime.resource.loader.FileResourceLoader");
        fileEngine.setProperty("resource.loader.file.path", "/opt/velocity-tpl");
        fileEngine.setProperty("resource.default_encoding", "UTF-8");
        fileEngine.init();
        render(fileEngine, ctx, "hello-file.vm");

        VelocityEngine badEngine = new VelocityEngine();
        badEngine.setProperty("resource.loaders", "file");
        badEngine.setProperty("resource.loader.file.class",
                "org.apache.velocity.runtime.resource.loader.FileResourceLoader");
        badEngine.setProperty("resource.loader.file.path", "/no/such/dir");
        badEngine.init();
        try {
            render(badEngine, ctx, "hello-file.vm");
        } catch (VelocityException e) {
            System.out.println("[捕获异常] " + e.getClass().getName());
            System.out.println("[异常消息] " + e.getMessage().split("\n")[0]);
        }

        File logFile = new File("/var/log/velocity/velocity-demo.log");
        if (logFile.exists()) {
            System.out.println("---- /var/log/velocity/velocity-demo.log 已生成,前 5 行 ----");
            Files.readAllLines(logFile.toPath(), StandardCharsets.UTF_8)
                    .stream().limit(5).forEach(System.out::println);
        } else {
            System.out.println("runtime.log 文件未生成:classpath 存在 SLF4J 绑定时,"
                    + "velocity-engine-core 2.4.1 日志由 SLF4J 输出,runtime.log 被忽略");
        }
    }

    private static void render(VelocityEngine engine, VelocityContext ctx, String path) {
        Template template = engine.getTemplate(path, "UTF-8");
        StringWriter writer = new StringWriter();
        template.merge(ctx, writer);
        System.out.println("---- " + path + " 渲染结果 ----");
        System.out.println(writer);
    }
}

编译并运行:

cd /root/velocity-demo && mvn -q clean compile
cd /root/velocity-demo && LANG=C.UTF-8 mvn -q exec:java -Dexec.mainClass=com.example.ConfigDemo -Dexec.args=/etc/velocity-demo/velocity-demo-new2.properties

四、参数总览

参数(2.4.1 现行名)默认值作用
resource.loadersfile启用的资源加载器列表,可多值逗号分隔
resource.loader.classpath.class无指定加载器实现类,classpath 名可自定
resource.loader.file.path当前目录FileResourceLoader 的模板根目录
resource.default_encodingUTF-8 依赖平台模板读取与输出统一编码
velocimacro.library.pathVM_global_library.vm启动时加载的宏库路径
directive.foreach.max_loops无限制foreach 单次渲染最大循环次数
runtime.strict_mode.enablefalse严格引用模式开关
runtime.log已废弃2.x 日志由 SLF4J 接管,该键无效
input.encoding、output.encoding已废弃由 resource.default_encoding 取代
velocimacro.library已废弃由 velocimacro.library.path 取代
directive.foreach.maxloops已废弃由 directive.foreach.max_loops 取代
runtime.references.strict已废弃由 runtime.strict_mode.enable 取代

五、资源加载器参数详解

5.1 classpath 加载器

从应用 classpath 读取模板,模板文件放在 src/main/resources 下随代码一起编译打包:

engine.setProperty("resource.loaders", "classpath");
engine.setProperty("resource.loader.classpath.class",
        "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");

要点:getTemplate 的路径相对 classpath 根,子目录要写全,例如模板位于 resources/templates/main.vm 时应写 templates/main.vm;模板内部的 #include 与 #parse 同样按全路径引用。

5.2 file 加载器

从磁盘目录读取模板,适合模板需要热更新或不打包进应用的场景:

engine.setProperty("resource.loaders", "file");
engine.setProperty("resource.loader.file.class",
        "org.apache.velocity.runtime.resource.loader.FileResourceLoader");
engine.setProperty("resource.loader.file.path", "/opt/velocity-tpl");

实测渲染 /opt/velocity-tpl/hello-file.vm 回显:

---- hello-file.vm 渲染结果 ----
你好,张三!这是来自磁盘模板目录 /opt/velocity-tpl 的中文渲染。

5.3 路径错误行为

file.path 指向不存在目录时,引擎启动不报错,渲染时抛资源未找到异常。实测 file.path 配为 /no/such/dir 的回显:

[main] ERROR org.apache.velocity.loader - ResourceManager: unable to find resource 'hello-file.vm' in any resource loader.
[捕获异常] org.apache.velocity.exception.ResourceNotFoundException
[异常消息] Unable to find resource 'hello-file.vm'

六、编码参数详解

6.1 resource.default_encoding

统一控制模板读取与输出编码,取代 1.x 与 2.x 早期的 input.encoding、output.encoding:

resource.default_encoding = UTF-8

6.2 与系统 locale 的关系

resource.default_encoding 只管模板内容,程序自身打印中文还取决于 JVM 的 file.encoding。POSIX locale 环境实测 file.encoding 为 ANSI_X3.4-1968,控制台中文会变问号,需将系统 locale 设为 C.UTF-8:

LANG 未设置:file.encoding = ANSI_X3.4-1968
LANG=C.UTF-8:file.encoding = UTF-8

七、指令参数详解

7.1 directive.foreach.max_loops

限制单次 foreach 渲染的最大循环次数,防御脏数据导致的超长循环。配置文件中设为 2,数据模型放 3 条记录,实测渲染只输出前 2 条:

---- templates/items.vm 渲染结果 ----
第 1 件:商品1
第 2 件:商品2

第 3 条被静默截断,不抛异常,符合上限保护的设计预期。

八、宏参数详解

8.1 velocimacro.library.path

指定启动时加载的宏库文件,多个文件用逗号分隔。宏库放在 classpath 加载器根路径下时按相对路径写:

velocimacro.library.path = templates/macros.vm

配置生效后,模板内直接调用宏,实测输出:

你好,张三!欢迎使用 Apache Velocity。
你好,李四!欢迎使用 Apache Velocity。

不配置该键时,调用未定义宏不会报错,宏调用语句按原样输出到渲染结果,这是排查“模板出现未执行指令”时的高频原因。

九、严格模式参数详解

9.1 runtime.strict_mode.enable

开启后引用未定义变量将抛异常,用于上线前暴露模板拼写错误:

runtime.strict_mode.enable = true

9.2 条件指令中的未定义引用

#if 条件中的未定义引用不抛异常,按 false 处理,走 #else 分支,实测回显:

---- templates/strict-if.vm 渲染结果 ----
未定义引用在条件指令中按 false 处理

9.3 输出位置的未定义引用

直接输出未定义引用抛 MethodInvocationException,实测回显:

[main] ERROR org.apache.velocity.rendering - Variable $undefinedRef has not been set at templates/strict-ref.vm[line 1, column 12]
[捕获异常] org.apache.velocity.exception.MethodInvocationException
[异常消息] Variable $undefinedRef has not been set at templates/strict-ref.vm[line 1, column 12]

9.4 静默引用的行为

注意 2.4.1 实测行为:即使写成静默引用形式,严格模式下同样抛异常,并不会静默返回空:

[main] ERROR org.apache.velocity.rendering - Variable $undefinedQuiet has not been set at templates/strict-quiet.vm[line 1, column 14]
[捕获异常] org.apache.velocity.exception.MethodInvocationException

因此严格模式适合在测试环境开启,与脏数据共存的线上模板慎用。

十、日志参数详解

10.1 runtime.log 已废弃

2.4.1 的日志强制走 SLF4J:classpath 缺少 slf4j-api 时引擎初始化直接失败,实测异常:

Exception in thread "main" java.lang.NoClassDefFoundError: org/slf4j/LoggerFactory
        at org.apache.velocity.runtime.RuntimeInstance.<init>(RuntimeInstance.java:121)

classpath 只有 slf4j-api 没有绑定时,日志进入 SLF4J 空实现并被吞掉,配置 runtime.log 也不会生成文件,实测日志目录始终为空。

10.2 用 SLF4J 绑定落盘日志

推荐引入 slf4j-simple,并用系统属性指定日志文件:

cd /root/velocity-demo && LANG=C.UTF-8 java -Dorg.slf4j.simpleLogger.logFile=/var/log/velocity/velocity-demo.log -cp 'target/classes:libs/*' com.example.ConfigDemo

实测日志文件生成(1718 字节),内容节选:

[main] WARN org.apache.velocity.deprecation - configuration key 'runtime.references.strict' has been deprecated in favor of 'runtime.strict_mode.enable'
[main] ERROR org.apache.velocity.rendering - Variable $undefinedRef has not been set at templates/strict-ref.vm[line 1, column 12]

生产环境也可换用 logback 等绑定,Velocity 日志会自动并入应用统一日志体系。

十一、弃用键名新旧映射

2.4.1 对旧键名逐项打印 WARN 且功能保持兼容。旧式配置文件实测回显:

[main] WARN org.apache.velocity.deprecation - configuration key 'runtime.references.strict' has been deprecated in favor of 'runtime.strict_mode.enable'
[main] WARN org.apache.velocity.deprecation - configuration key 'input.encoding' has been deprecated in favor of 'resource.default_encoding'
[main] WARN org.apache.velocity.deprecation - configuration key 'directive.foreach.maxloops' has been deprecated in favor of 'directive.foreach.max_loops'
[main] WARN org.apache.velocity.deprecation - configuration key 'velocimacro.library' has been deprecated in favor of 'velocimacro.library.path'

映射汇总:

旧键名新键名
input.encodingresource.default_encoding
output.encodingresource.default_encoding
velocimacro.libraryvelocimacro.library.path
runtime.references.strictruntime.strict_mode.enable
directive.foreach.maxloopsdirective.foreach.max_loops
runtime.log无对应,日志由 SLF4J 接管

将配置全部换成新键名后重新运行,实测弃用告警数量为 0。

十二、生产配置模板

以下模板适用于以 jar 包部署、模板随应用打包的渲染服务,内存按 256 至 512MB 起步(堆参数在启动命令 -Xms 与 -Xmx 上设置),磁盘仅需容纳模板文件与日志:

# 资源加载器:classpath 为主
resource.loaders = classpath
resource.loader.classpath.class = org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader

# 编码
resource.default_encoding = UTF-8

# 宏库
velocimacro.library.path = templates/macros.vm

# 循环保护:按业务最大清单量调整
directive.foreach.max_loops = 1000

# 严格模式:测试环境 true,按模板成熟度灰度
runtime.strict_mode.enable = false

模板需要热更新时增加 file 加载器:

resource.loaders = classpath,file
resource.loader.classpath.class = org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader
resource.loader.file.class = org.apache.velocity.runtime.resource.loader.FileResourceLoader
resource.loader.file.path = /opt/velocity-tpl

十三、常见问题

Q1:配置了 runtime.log 为什么始终没有日志文件? A:2.4.1 日志强制走 SLF4J,runtime.log 已废弃。classpath 有绑定时日志按绑定配置输出,可用 -Dorg.slf4j.simpleLogger.logFile 指定文件;完全没有 slf4j-api 时引擎初始化抛 NoClassDefFoundError。

Q2:模板里的宏调用被原样输出怎么办? A:宏库没有加载。检查 velocimacro.library.path 是否指向实际存在的宏库文件、路径是否相对正确的加载器根目录,修改后重新编译打包再验证。

Q3:foreach 数据量超过配置上限会报错吗? A:不会。实测 max_loops 设为 2 时 3 条数据只渲染前 2 条,超出部分被静默截断,业务上需确认截断是否可接受或调大上限。

Q4:出现 Lexical error, Encountered 之类解析异常是什么原因? A:模板正文里出现了形如 #if、#end 的裸指令词片段(例如在说明文字里写了指令名),词法分析器将其当作未闭合指令。修改正文措辞或用 #* 与 *# 注释包裹该字样即可。

十四、参考链接

  1. Velocity 引擎开发者文档(配置章节):https://velocity.apache.org/engine/2.4/developer-guide.html
  2. 2.x 默认配置键说明:https://velocity.apache.org/engine/2.4/configuration.html
  3. VTL 参考指南:https://velocity.apache.org/engine/2.4/vtl-reference.html
  4. velocity-engine-core 2.4.1(Maven Central):https://mvnrepository.com/artifact/org.apache.velocity/velocity-engine-core