Using Velocity's WebappResourceLoader with Spring

血红的双手。 提交于 2020-01-02 07:53:08

问题


I'm trying to use Velocity to create an email template that is mailed by Spring's JavaMailSender class. The resource loader that I decided to use to find the Velocity template in my web app is WebappResourceLoader which is located in the Velocity tool jar.

However, depending on how I use the WebappResourceLoader, I get either a NPE when the web app starts up or the template can't be found.

If I specify the init properties for the Velocity engine in my Spring application context I get the NPE. My configuration is as follows:

<bean id="velocityEngine" class="org.springframework.ui.velocity.VelocityEngineFactoryBean">
  <property name="velocityProperties">
        <props>
        <prop key="resource.loader">webapp</prop>
        <prop key="webapp.resource.loader.class">org.apache.velocity.tools.view.WebappResourceLoader</prop>
        <prop key="webapp.resource.loader.path">/WEB-INF/velocity/</prop>
        </props>
   </property>
</bean>

The stack trace I get when the app is starting up is:

java.lang.NullPointerException
at org.apache.velocity.tools.view.WebappResourceLoader.getResourceStream(WebappResourceLoader.java:145)
at org.apache.velocity.runtime.resource.loader.ResourceLoader.resourceExists(ResourceLoader.java:224)
at org.apache.velocity.runtime.resource.ResourceManagerImpl.getLoaderForResource(ResourceManagerImpl.java:641)
at org.apache.velocity.runtime.resource.ResourceManagerImpl.getLoaderNameForResource(ResourceManagerImpl.java:624)
at org.apache.velocity.runtime.RuntimeInstance.getLoaderNameForResource(RuntimeInstance.java:1464)
at org.apache.velocity.runtime.VelocimacroFactory.initVelocimacro(VelocimacroFactory.java:159)
at org.apache.velocity.runtime.RuntimeInstance.init(RuntimeInstance.java:261)
at org.apache.velocity.app.VelocityEngine.init(VelocityEngine.java:107)
at org.springframework.ui.velocity.VelocityEngineFactory.createVelocityEngine(VelocityEngineFactory.java:251)
at org.springframework.ui.velocity.VelocityEngineFactoryBean.afterPropertiesSet(VelocityEngineFactoryBean.java:57)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1460)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1398)

To fix this I changed my application context to as below:

<bean id="velocityEngine" class="org.springframework.ui.velocity.VelocityEngineFactoryBean" />

And then in the service class, right before the code to merge application data with the template (which is at /WEB-INF/velocity and named regemail.vm) I added the following code:

    velocityEngine.addProperty("resource.loader", "webapp");
    velocityEngine.addProperty("webapp.resource.loader.class", "org.apache.velocity.tools.view.WebappResourceLoader");
    velocityEngine.addProperty("webapp.resource.loader.path", "/WEB-INF/velocity/");
    velocityEngine.setApplicationAttribute("javax.servlet.ServletContext", "localhost:8080");

The app starts fine but when the email is going to be sent, I get the following error:

SEVERE: Servlet.service() for servlet default threw exception
org.apache.velocity.exception.ResourceNotFoundException: Unable to find resource 'regmail.vm'
at org.apache.velocity.runtime.resource.ResourceManagerImpl.loadResource(ResourceManagerImpl.java:483)
at org.apache.velocity.runtime.resource.ResourceManagerImpl.getResource(ResourceManagerImpl.java:354)
at org.apache.velocity.runtime.RuntimeInstance.getTemplate(RuntimeInstance.java:1400)
at org.apache.velocity.app.VelocityEngine.mergeTemplate(VelocityEngine.java:370)
at org.apache.velocity.app.VelocityEngine.mergeTemplate(VelocityEngine.java:345)
at org.springframework.ui.velocity.VelocityEngineUtils.mergeTemplate(VelocityEngineUtils.java:58)
at org.springframework.ui.velocity.VelocityEngineUtils.mergeTemplateIntoString(VelocityEngineUtils.java:122)
at com.mywebapp.web.service.RegistrationServiceImpl$1.prepare(RegistrationServiceImpl.java:60)
at org.springframework.mail.javamail.JavaMailSenderImpl.send(JavaMailSenderImpl.java:353)
at org.springframework.mail.javamail.JavaMailSenderImpl.send(JavaMailSenderImpl.java:345)
at com.mywebapp.web.service.RegistrationServiceImpl.sendRegEmail(RegistrationServiceImpl.java:65)
at com.mywebapp.web.controller.SignUpController.onSubmit(SignUpController.java:97)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

I'm using Velocity version 1.6.4 and Velocity tools 2.0. Any help much appreciated. Thanks!


回答1:


You need to pass ServletContext object:

velocityEngine.setApplicationAttribute("javax.servlet.ServletContext", request.getSession().getServletContext());

Another option would be using ClasspathResourceLoader instead and putting your templates within classpath.




回答2:


If you look at the documentation for VelocityEngineFactoryBean, you'll notice that there's a 'resourceLoaderPath' property that you can set. Given your configuration above, it looks like you've placed your Velocity templates under /WEB-INF/velocity/, so use that as the value for 'resourceLoaderPath' and the factory bean should load your templates just fine.




回答3:


Three years later, but here's another solution that does not require to adapt your Java code to pass the ServletContext to Velocity Engine.

Simply use VelocityConfigurer instead of VelocityEngineFactoryBean in your Spring Context file like this:

<bean id="velocityConfig" class="org.springframework.web.servlet.view.velocity.VelocityConfigurer">
    <property name="velocityPropertiesMap">
        <map>
            <entry key="runtime.log.invalid.reference"><value>true</value></entry>
            <entry key="runtime.log.logsystem.class"><value>org.apache.velocity.runtime.log.Log4JLogChute</value></entry>
            <entry key="runtime.log.logsystem.log4j.logger"><value>velocity</value></entry>
            <entry key="input.encoding"><value>UTF-8</value></entry>
            <entry key="output.encoding"><value>UTF-8</value></entry>
            <entry key="directive.include.output.errormsg.start"><value></value></entry>
            <entry key="directive.parse.max.depth"><value>10</value></entry>
            <entry key="directive.set.null.allowed"><value>true</value></entry>
            <entry key="velocimacro.library.autoreload"><value>true</value></entry>
            <entry key="velocimacro.permissions.allow.inline"><value>true</value></entry>
            <entry key="velocimacro.permissions.allow.inline.to.replace.global"><value>false</value></entry>
            <entry key="velocimacro.permissions.allow.inline.local.scope"><value>false</value></entry>
            <entry key="velocimacro.context.localscope"><value>false</value></entry>
            <entry key="runtime.interpolate.string.literals"><value>true</value></entry>
            <entry key="resource.manager.class"><value>org.apache.velocity.runtime.resource.ResourceManagerImpl</value></entry>
            <entry key="resource.manager.cache.class"><value>org.apache.velocity.runtime.resource.ResourceCacheImpl</value></entry>
            <entry key="resource.loader"><value>webapp, class, ds</value></entry>
            <entry key="class.resource.loader.description"><value>Velocity Classpath Resource Loader</value></entry>
            <entry key="class.resource.loader.class"><value>org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader</value></entry>
            <entry key="webapp.resource.loader.class"><value>org.apache.velocity.tools.view.WebappResourceLoader</value></entry>
            <entry key="webapp.resource.loader.path"><value>/WEB-INF/views/</value></entry>
            <entry key="webapp.resource.loader.cache"><value>false</value></entry>
            <entry key="webapp.resource.loader.modificationCheckInterval"><value>2</value></entry>
            <entry key="ds.resource.loader.instance"><ref bean="templateLoader"/></entry>
            <entry key="ds.resource.loader.resource.table"><value>templates</value></entry>
            <entry key="ds.resource.loader.resource.keycolumn"><value>code</value></entry>
            <entry key="ds.resource.loader.resource.templatecolumn"><value>content</value></entry>
            <entry key="ds.resource.loader.resource.timestampcolumn"><value>updated</value></entry>
            <entry key="ds.resource.loader.cache"><value>false</value></entry>
        </map>
    </property>
</bean>

<bean id="templateLoader" 
    class="org.apache.velocity.runtime.resource.loader.DataSourceResourceLoader">
    <property name="dataSource" ref="yourDataSource"></property>
</bean>


来源:https://stackoverflow.com/questions/3125994/using-velocitys-webappresourceloader-with-spring

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!