You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring5升级后Apache Velocity1.7在Linux部署时无法找到模板

问题:Spring 5升级后Velocity模板在Linux Tomcat下无法加载

我们将Spring 4应用升级至Spring 5后,其余功能正常,但Apache Velocity生成邮件模板的功能出现异常。由于Spring 5移除了Velocity支持,我们改用Apache官方的Velocity 1.7 jar包。本地Windows 10环境的Tomcat服务器能正常读取WEB-INF/classes下的模板并生成邮件,但部署到Amazon Linux服务器的Tomcat时,Velocity始终找不到模板。


环境与配置详情

所有模板文件存放在WEB-INF/classes目录下,该目录下的velocity.properties已被正常识别(配置了日志输出到Log4j),内容如下:

runtime.log.logsystem.class=org.apache.velocity.runtime.log.SimpleLog4JLogSystem
runtime.log.logsystem.log4j.category=velocity

Spring配置文件(applicationContext-service.xml)

初始Velocity配置:

<util:properties id="velocityProperties">
    <prop key="resource.loader">class</prop>
    <prop key="class.resource.loader.class">org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader</prop>
    <prop key="input.encoding">UTF-8</prop>
    <prop key="output.encoding">UTF-8</prop>
</util:properties>

<bean id="velocityEngine" class="org.apache.velocity.app.VelocityEngine">
    <constructor-arg ref="velocityProperties" />
</bean>

尝试添加路径后的配置:

<util:properties id="velocityProperties">
    <prop key="resource.loader">class</prop>
    <prop key="class.resource.loader.class">org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader</prop>
    <prop key="class.resource.loader.path">/WEB-INF/classes</prop>
    <prop key="input.encoding">UTF-8</prop>
    <prop key="output.encoding">UTF-8</prop>
</util:properties>

关键错误堆栈

GATEWAY: ERROR 2023-06-06 21:00:00,619 [EisQuartzScheduler_Worker-2] ErrorLogger.schedulerError(2425) | Job (GATEWAY.reportProcessed.notifier.cron threw an exception.
org.quartz.SchedulerException: Job threw an unhandled exception.
    at org.quartz.core.JobRunShell.run(JobRunShell.java:213) ~[quartz-2.2.1.jar:?]
    at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:573) ~[quartz-2.2.1.jar:?]
Caused by: org.springframework.scheduling.quartz.JobMethodInvocationFailedException: Invocation of method 'sendNotifications' on target class [class gov.epa.eis.util.ReportRequestProcessedNotifier] failed; nested exception is java.lang.NullPointerException
    at org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean$MethodInvokingJob.executeInternal(MethodInvokingJobDetailFactoryBean.java:276) ~[spring-context-support-5.3.27.jar:5.3.27]
    at org.springframework.scheduling.quartz.QuartzJobBean.execute(QuartzJobBean.java:75) ~[spring-context-support-5.3.27.jar:5.3.27]
    at org.quartz.core.JobRunShell.run(JobRunShell.java:202) ~[quartz-2.2.1.jar:?]
    ... 1 more
Caused by: java.lang.NullPointerException
    at org.apache.velocity.runtime.RuntimeInstance.getTemplate(RuntimeInstance.java:1533) ~[velocity-1.7.jar:1.7]
    at org.apache.velocity.app.VelocityEngine.mergeTemplate(VelocityEngine.java:343) ~[velocity-1.7.jar:1.7]
    at gov.epa.eis.service.impl.MailServiceImpl.sendMessage(MailServiceImpl.java:60) ~[core-7.0.52-SNAPSHOT.jar:?]
    at gov.epa.eis.util.ReportRequestProcessedNotifier.sendNotifications(ReportRequestProcessedNotifier.java:83) ~[classes/:?]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_362]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_362]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_362]
    at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_362]
    at org.springframework.util.MethodInvoker.invoke(MethodInvoker.java:283) ~[spring-core-5.3.27.jar:5.3.27]
    at org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean$MethodInvokingJob.executeInternal(MethodInvokingJobDetailFactoryBean.java:267) ~[spring-context-support-5.3.27.jar:5.3.27]
    at org.springframework.scheduling.quartz.QuartzJobBean.execute(QuartzJobBean.java:75) ~[spring-context-support-5.3.27.jar:5.3.27]
    at org.quartz.core.JobRunShell.run(JobRunShell.java:202) ~[quartz-2.2.1.jar:?]
    ... 1 more

补充代码:sendMessage方法

public void setVelocityEngine(VelocityEngine velocityEngine) {
    this.velocityEngine = velocityEngine;
}

@Override
public void sendMessage(final SimpleMailMessage msg, final String templateName, final Map<String, Object> model) {
    try {
        VelocityContext context = new VelocityContext();
            
        for (String key : model.keySet()) {
            context.put(key, model.get(key));
        }
            
        StringWriter stringWriter = new StringWriter();
        velocityEngine.mergeTemplate(templateName, "UTF-8", context, stringWriter);
        final String text = stringWriter.toString();

        msg.setText(text);
        sendMessage(msg);
    } catch (VelocityException e) {
        LOG.error(e);
    }
}

解决建议

1. 修正文件名大小写问题

Linux系统是大小写敏感的,而Windows不是。确认调用mergeTemplate时传入的templateName与实际模板文件的文件名、目录路径大小写完全一致(比如EmailTemplate.vm不能写成emailtemplate.vm)。同时检查打包后的war包中,WEB-INF/classes下的模板文件结构是否与本地一致。

2. 移除错误的路径配置

ClasspathResourceLoader本身就是从类路径加载资源,而WEB-INF/classes已经是Tomcat的类路径根目录之一,不需要额外配置class.resource.loader.path。添加该配置会导致Velocity去类路径下查找/WEB-INF/classes子目录,反而无法找到直接放在类路径根目录的模板。恢复最初的Velocity配置即可。

3. 验证类路径资源可访问性

在sendMessage方法中添加调试代码,检查模板是否能通过类加载器获取:

ClassLoader classLoader = getClass().getClassLoader();
URL resourceUrl = classLoader.getResource(templateName);
if (resourceUrl == null) {
    LOG.error("模板文件不存在:{},当前类路径:{}", templateName, System.getProperty("java.class.path"));
} else {
    LOG.info("找到模板文件:{}", resourceUrl.getPath());
}

部署到Linux后查看日志,确认模板是否存在于类路径中。

4. 检查打包过程是否遗漏模板

确认Maven/Gradle等构建工具是否将src/main/resources下的.vm模板文件正确打包到WEB-INF/classes目录。检查构建配置文件(pom.xml/build.gradle),确保资源打包规则包含.vm文件,没有被过滤或忽略。

5. 查看Velocity详细日志

利用已配置的Log4j输出Velocity日志,查找是否有“resource not found”等具体加载失败提示,这能直接定位是哪个模板无法加载,或加载过程中出现了其他异常。

内容的提问来源于stack exchange,提问作者Paul

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 21:17:02