如何使用 java 将 spring 标记库公开给 Freemaker 配置并处理 Freemarker 模板类?
Posted
技术标签:
【中文标题】如何使用 java 将 spring 标记库公开给 Freemaker 配置并处理 Freemarker 模板类?【英文标题】:How to expose spring tag lib to Freemaker configuration using java and to process Freemarker Template class? 【发布时间】:2018-04-22 11:17:17 【问题描述】:我一直在尝试使用 freemarker.template.Template 类来处理给定的 .ftl 模板。基本上它没有找到弹簧标签库,并且在遇到第一个 标签时失败。但是,它可以很好地读取 spring 宏指令。
在 spring 中附加我的 freemarker 配置和错误消息:
我正在使用带有 freemarker 2.3.23 的 spring 4。在 tomcat 7 上运行我的 Spring MVC 应用程序时,它抱怨以下内容:
---- FTL 堆栈跟踪(“~”表示嵌套相关): - 失败:@spring.url "/imports/app/vendor/boot... [在模板 "config/pages/default-page.ftl" 第 5 行第 54 列] ----] 根本原因 FreeMarker 模板错误:以下已评估为 null 或缺失: ==> spring [in template "config/pages/default-page.ftl" at line 5, column 56]
Java 堆栈跟踪(针对程序员): ---- freemarker.core.InvalidReferenceException: [... 异常信息已被打印;见上面...]在 freemarker.core.InvalidReferenceException.getInstance(InvalidReferenceException.java:131) 在 freemarker.core.UnexpectedTypeException.newDescriptionBuilder(UnexpectedTypeException.java:77) 在 freemarker.core.UnexpectedTypeException.(UnexpectedTypeException.java:40) 在 freemarker.core.NonHashException.(NonHashException.java:46) 在 freemarker.core.Dot._eval(Dot.java:45) 在 freemarker.core.Expression.eval(Expression.java:78) 在 freemarker.core.UnifiedCall.accept(UnifiedCall.java:74) 在 freemarker.core.Environment.visit(Environment.java:324) 在 freemarker.core.MixedContent.accept(MixedContent.java:54) 在 freemarker.core.Environment.visit(Environment.java:324) 在 freemarker.core.Environment.process(Environment.java:302) 在 freemarker.template.Template.process(Template.java:325) 在 com.isys.ghp.server.services.web.impls.htmlRenditionComposer.composeRendition(HtmlRenditionComposer.java:43) 在 com.isys.ghp.controllers.app.RootControllerApp.welcome(RootControllerApp.java:48) 在 com.isys.ghp.controllers.app.RootControllerApp$$FastClassBySpringCGLIB$$cebaa583.invoke() 在 org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204) 在 org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:717) 在 org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157) 在 org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:85) 在 com.isys.ghp.server.aspects.MethodLogger.around(MethodLogger.java:21) 在 sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 在 sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) 在 sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 在 java.lang.reflect.Method.invoke(Method.java:606) 在 org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:621) 在 org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:610) 在 org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:68) 在 org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:168) 在 org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92) 在 org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) 在 org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:653) 在 com.isys.ghp.controllers.app.RootControllerApp$$EnhancerBySpringCGLIB$$55180b14.welcome() 在 sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 在 sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) 在 sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 在 java.lang.reflect.Method.invoke(Method.java:606) 在 org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:221) 在 org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:137) 在 org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:110) 在 org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandleMethod(RequestMappingHandlerAdapter.java:776) 在 org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:705) 在 org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85) 在 org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:959) 在 org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:893) 在 org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:967) 在 org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:858) 在 javax.servlet.http.HttpServlet.service(HttpServlet.java:620) 在 org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:843) 在 javax.servlet.http.HttpServlet.service(HttpServlet.java:727) 在 org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:303) 在 org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208) 在 org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:85) 在 org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) 在 org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241) 在 org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208) 在 org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52) 在 org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241) 在 org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208) 在 org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:316) 在 org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:126) 在 org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:90) 在 org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330) 在 org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:114) 在 org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330) 在 org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:122) 在 org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330) 在 org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:111) 在 org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330) 在 org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:169) 在 org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330) 在 org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:48) 在 org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330) 在 org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:205) 在 org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330) 在 org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:120) 在 org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330) 在 org.springframework.security.web.csrf.CsrfFilter.doFilterInternal(CsrfFilter.java:96) 在 org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) 在 org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330) 在 org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:64) 在 org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) 在 org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330) 在 org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:91) 在 org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330) 在 org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:53) 在 org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) 在 org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330) 在 org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:213) 在 org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:176) 在 org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:344) 在 org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:261) 在 org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241) 在 org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208) 在 org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:220) 在 org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:122) 在 org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:170) 在 org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:98) 在 org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:950) 在 org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116) 在 org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:408) 在 org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1040) 在 org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:607) 在 org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:315) 在 java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) 在 java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) 在 java.lang.Thread.run(Thread.java:745)
MVCConfiguration 的代码扩展了 WebMvcConfigurerAdapter
@Bean
public FreeMarkerViewResolver freemarkerViewResolver()
LOGGER.debug("Executing freemarkerViewResolver()");
FreeMarkerViewResolver resolver = new FreeMarkerViewResolver();
resolver.setExposeSpringMacroHelpers(true);
resolver.setExposeRequestAttributes(true);
resolver.setCache(true);
resolver.setPrefix("");
resolver.setSuffix(".ftl");
return resolver;
@Bean(name="fmAdvanceConfigFactoryBean")
public FreeMarkerConfigurer getFreemarkerConfig() throws IOException, TemplateException
FreeMarkerConfigurationFactoryBean freeMarkerConfigurationFactoryBean = new FreeMarkerConfigurationFactoryBean();
FreeMarkerConfigurer result = new FreeMarkerConfigurer();
result.setTemplateLoaderPath("/WEB-INF/views");
result.setConfiguration(freeMarkerConfigurationFactoryBean.createConfiguration());
return result;
模板处理块:
Template pageHTML = fmAdvanceConfigFactoryBean.getTemplate("config/pages/default-page.ftl");
StringWriter pageWriter = new StringWriter();
Map<String, Object> map = new HashMap<>();
pageHTML.process(map, pageWriter);
我一直在寻找很多提供高级配置的页面,但没有一个能够解决这个问题。我不确定我在这里缺少什么。任何帮助都将是有用的和感激的!
谢谢!
更新:你是对的,ddekany!就像你建议的那样,不同的配置文件中有另一种方法。我已经评论了该方法并检查了没有其他调用点。但是,现在它抱怨找不到模板。请看下面我的回答。你是对的。在不同的配置文件中还有另一种方法。我已经评论了该方法并检查了没有其他调用点。但是,现在它抱怨找不到模板:
freemarker.template.TemplateNotFoundException:找不到模板 名称“/config/pages/default-page.ftl”。该名称被解释为 这个模板加载器: LegacyDefaultFileTemplateLoader(baseDir="/Users/mdani/Data_MD/Developer_MD/Workspace/Tools/GH-Platform/STS.app/Contents/MacOS", canonicalBasePath="/Users/mdani/Data_MD/Developer_MD/Workspace/Tools/GH-Platform/STS.app/Contents/MacOS/")。 警告:未设置“template_loader”FreeMarker 设置 (Configuration.setTemplateLoader),并且使用默认值最多 当然不是故意和危险的,并且可能是造成这种情况的原因 错误。在 freemarker.template.Configuration.getTemplate(Configuration.java:2701) 在 freemarker.template.Configuration.getTemplate(Configuration.java:2503)
简化类:
@Loggable
@Bean(name="fmAdvanceConfigFactoryBean")
public FreeMarkerConfigurer getFreemarkerConfig() throws IOException, TemplateException
Properties settings = new Properties();
settings.setProperty("auto_import", "/spring.ftl as spring");
FreeMarkerConfigurer result = new FreeMarkerConfigurer();
result.setTemplateLoaderPaths("/WEB-INF/views","classpath:/");
result.setFreemarkerSettings(settings);
return result;
新错误:
严重:Servlet.service() 用于 servlet [dispatcher] 在上下文中 路径 [/GHPWebApp] 抛出异常 [请求处理失败;嵌套的 异常是freemarker.core.InvalidReferenceException:以下 已评估为 null 或缺失: ==> springMacroRequestContext [在模板“spring.ftl”中,第 89 行,第 134 列]
---- 提示:如果已知失败的表达式合法地引用了有时为空或缺失的内容,请指定默认值 像 myOptionalVar!myDefault 这样的值,或使用 when-presentwhen-missing。 (这些只有 覆盖表达式的最后一步;覆盖整个表达式,
使用括号:(myOptionalVar.foo)!myDefault, (myOptionalVar.foo)??
---- FTL 堆栈跟踪(“~”表示嵌套相关): - 失败:$springMacroRequestContext.getContex... [在第 89 行第 132 列的宏“url”中的模板“spring.ftl”中] - 通过:@spring.url "/imports/app/vendor/boot... [in template "config/pages/default-page.ftl" at 第 4 行,第 54 列] ----] 根本原因 FreeMarker 模板错误:以下已评估为 null 或缺失: ==> springMacroRequestContext [在模板“spring.ftl”中,第 89 行,第 134 列]
---- 提示:如果已知失败的表达式合法地引用了有时为空或缺失的内容,请指定默认值 像 myOptionalVar!myDefault 这样的值,或使用 when-presentwhen-missing。 (这些只有 覆盖表达式的最后一步;覆盖整个表达式,
使用括号:(myOptionalVar.foo)!myDefault, (myOptionalVar.foo)??
---- FTL 堆栈跟踪(“~”表示嵌套相关): - 失败于:$springMacroRequestContext.getContex...[在第 89 行第 132 列的宏“url”中的模板“spring.ftl”中]
- 通过:@spring.url "/imports/app/vendor/boot... [in template "config/pages/default-page.ftl" at 第 4 行,第 54 列]
Java 堆栈跟踪(针对程序员): ---- freemarker.core.InvalidReferenceException: [... 异常信息已被打印;见上面...]在 freemarker.core.InvalidReferenceException.getInstance(InvalidReferenceException.java:134) 在 freemarker.core.UnexpectedTypeException.newDescriptionBuilder(UnexpectedTypeException.java:85) 在 freemarker.core.UnexpectedTypeException.(UnexpectedTypeException.java:48) 在 freemarker.core.NonHashException.(NonHashException.java:49)
【问题讨论】:
【参考方案1】:我猜spring
变量丢失了,因为您的模板不是以<#import "/spring.ftl" as spring>
开头的,您也没有为此配置自动导入。最后一种是更方便的方式。类似于Properties settings = new Properties(); settings.setProperty("auto_import", "/spring.ftl as spring"); configurer.setFreemarkerSettings(settings);
。
(顺便说一句,将 FreeMarker 升级到最新的 2.x.x... 2.3.23 已旧。)
【讨论】:
您好 ddekany,感谢您的快速回复。我已将依赖项升级到 2.3.26-incubating,并尝试了 auto_import。它仍然给出相同的异常 - 它发现弹簧变量为空!以下已评估为 null 或缺失:==> spring [in template "config/pages/default-page.ftl" at line 5, column 56] 还有什么想法吗? 这意味着甚至没有尝试自动导入。 (如果尝试过但没有找到文件,则模板在自动导入时失败,而不是稍后。)也许setFreemarkerSettings
也会在其他地方调用,并替换您的值。您可以覆盖 postProcessConfiguration(Configuration config)
方法来记录 config.getAutoImports()
返回的内容。
你是对的。在不同的配置文件中还有另一种方法。我已经评论了该方法并检查了没有其他调用点。但是,现在它抱怨找不到模板。请看下面我的回答。你是对的。在不同的配置文件中还有另一种方法。我已经评论了该方法并检查了没有其他调用点。但是,现在它抱怨找不到模板。请在上面查看我的更新!
虽然使用纯 FreeMarker API 设置某些东西很简单(即使在那里,您首先设置 setClassForTemplateLoading
,然后设置 setServletContextForTemplateLoading
,这将覆盖之前的内容),而是尝试使用 @987654329 @API 你可以,因为它也直接使用 FreeMarker API,而你正在干扰它。有FreeMarkerConfigurer.setTemplateLoaderPaths
和setTemplateLoaders
。另请注意,查找spring.ftl
所需的TemplateLoader
是由FreeMarkerConfigurer
自动添加的;你不必处理那个。
另外,确实有一些东西在那里,因为你最终得到一个没有TemplateLoader
集的Configuration
(如错误消息所述)。虽然您的代码在其他方面是错误的,但它会产生不同的错误消息。因此,就好像您使用的是不同的 Configuration
实例,其中从未设置过 templateLoader
设置,或者某处某处明确将该设置重置为其默认值。以上是关于如何使用 java 将 spring 标记库公开给 Freemaker 配置并处理 Freemarker 模板类?的主要内容,如果未能解决你的问题,请参考以下文章
如何通过 JMX 将 Spring Boot 应用程序中的 Kafka 指标公开给 Prometheus?
在不使用 spring-boot 执行器的情况下将来自 spring 应用程序的指标公开给 prometheus
如何禁用 Spring Data REST 存储库的默认公开?