Spring5升级后Apache Velocity1.7在Linux部署时无法找到模板
我们将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

