如何使用 java 将 spring 标签库公开给 Freemaker 配置并处理 Freemarker 模板 class?

How to expose spring tag lib to Freemaker configuration using java and to process Freemarker Template class?

我一直在尝试使用 freemarker.template.Template class 来处理给定的 .ftl 模板。 Basiclaly 它没有找到 spring 标签库,并且在遇到第一个 <@spring.url.../> 标签时失败。但是,它读取 spring 宏指令就好了。

在 spring 中附加我的 freemarker 配置和错误消息:

我正在使用 spring 4,freemarker 2.3.23。 运行 我的 Spring MVC 应用程序在 tomcat 7 上,它抱怨以下内容:

---- FTL stack trace ("~" means nesting-related): - Failed at: @spring.url "/imports/app/vendor/boot... [in template "config/pages/default-page.ftl" at line 5, column 54] ----] with root cause FreeMarker template error: The following has evaluated to null or missing: ==> spring [in template "config/pages/default-page.ftl" at line 5, column 56]

Java stack trace (for programmers): ---- freemarker.core.InvalidReferenceException: [... Exception message was already printed; see it above ...] at freemarker.core.InvalidReferenceException.getInstance(InvalidReferenceException.java:131) at freemarker.core.UnexpectedTypeException.newDesciptionBuilder(UnexpectedTypeException.java:77) at freemarker.core.UnexpectedTypeException.(UnexpectedTypeException.java:40) at freemarker.core.NonHashException.(NonHashException.java:46) at freemarker.core.Dot._eval(Dot.java:45) at freemarker.core.Expression.eval(Expression.java:78) at freemarker.core.UnifiedCall.accept(UnifiedCall.java:74) at freemarker.core.Environment.visit(Environment.java:324) at freemarker.core.MixedContent.accept(MixedContent.java:54) at freemarker.core.Environment.visit(Environment.java:324) at freemarker.core.Environment.process(Environment.java:302) at freemarker.template.Template.process(Template.java:325) at com.isys.ghp.server.services.web.impls.HtmlRenditionComposer.composeRendition(HtmlRenditionComposer.java:43) at com.isys.ghp.controllers.app.RootControllerApp.welcome(RootControllerApp.java:48) at com.isys.ghp.controllers.app.RootControllerApp$$FastClassBySpringCGLIB$$cebaa583.invoke() at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204) at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:717) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157) at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:85) at com.isys.ghp.server.aspects.MethodLogger.around(MethodLogger.java:21) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:606) at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:621) at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:610) at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:68) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:168) at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:653) at com.isys.ghp.controllers.app.RootControllerApp$$EnhancerBySpringCGLIB$180b14.welcome() at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:606) at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:221) at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:137) at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:110) at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandleMethod(RequestMappingHandlerAdapter.java:776) at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:705) at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85) at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:959) at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:893) at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:967) at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:858) at javax.servlet.http.HttpServlet.service(HttpServlet.java:620) at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:843) at javax.servlet.http.HttpServlet.service(HttpServlet.java:727) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:303) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208) at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:85) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208) at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:316) at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:126) at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:90) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330) at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:114) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330) at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:122) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330) at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:111) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330) at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:169) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330) at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:48) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330) at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:205) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330) at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:120) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330) at org.springframework.security.web.csrf.CsrfFilter.doFilterInternal(CsrfFilter.java:96) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330) at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:64) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330) at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:91) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330) at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:53) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330) at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:213) at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:176) at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:344) at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:261) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:220) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:122) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:170) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:98) at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:950) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:408) at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1040) at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:607) at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:315) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) at java.lang.Thread.run(Thread.java:745)

Code of MVCConfiguration extends 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 processing block:

Template pageHTML = fmAdvanceConfigFactoryBean.getTemplate("config/pages/default-page.ftl");  
StringWriter pageWriter = new StringWriter();
Map<String, Object> map = new HashMap<>();
pageHTML.process(map, pageWriter);

我一直在寻找很多提供高级配置的页面,但其中 none 能够解决这个问题。我不确定我在这里错过了什么。任何帮助都会有用和感激!

谢谢!

更新:你是对的,ddekany!正如您所建议的,不同的配置文件中还有另一种方法。我已经评论了该方法并检查了没有其他调用点。但是,现在它抱怨找不到模板。请参阅下面的答案。你是对的。不同的配置文件中还有另一种方法。我已经评论了该方法并检查了没有其他调用点。但是,现在它抱怨找不到模板:

freemarker.template.TemplateNotFoundException: Template not found for name "/config/pages/default-page.ftl". The name was interpreted by this TemplateLoader: 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/"). Warning: The "template_loader" FreeMarker setting wasn't set (Configuration.setTemplateLoader), and using the default value is most certainly not intended and dangerous, and can be the cause of this error. at freemarker.template.Configuration.getTemplate(Configuration.java:2701) at freemarker.template.Configuration.getTemplate(Configuration.java:2503)

简化class:

    @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;
}

新错误:

SEVERE: Servlet.service() for servlet [dispatcher] in context with path [/GHPWebApp] threw exception [Request processing failed; nested exception is freemarker.core.InvalidReferenceException: The following has evaluated to null or missing: ==> springMacroRequestContext [in template "spring.ftl" at line 89, column 134]

---- Tip: If the failing expression is known to legally refer to something that's sometimes null or missing, either specify a default value like myOptionalVar!myDefault, or use <#if myOptionalVar??>when-present<#else>when-missing. (These only cover the last step of the expression; to cover the whole expression,

use parenthesis: (myOptionalVar.foo)!myDefault, (myOptionalVar.foo)??

---- FTL stack trace ("~" means nesting-related): - Failed at: ${springMacroRequestContext.getContex... [in template "spring.ftl" in macro "url" at line 89, column 132] - Reached through: @spring.url "/imports/app/vendor/boot... [in template "config/pages/default-page.ftl" at line 4, column 54] ----] with root cause FreeMarker template error: The following has evaluated to null or missing: ==> springMacroRequestContext [in template "spring.ftl" at line 89, column 134]

---- Tip: If the failing expression is known to legally refer to something that's sometimes null or missing, either specify a default value like myOptionalVar!myDefault, or use <#if myOptionalVar??>when-present<#else>when-missing. (These only cover the last step of the expression; to cover the whole expression,

use parenthesis: (myOptionalVar.foo)!myDefault, (myOptionalVar.foo)??

---- FTL stack trace ("~" means nesting-related): - Failed at: ${springMacroRequestContext.getContex... [in template "spring.ftl" in macro "url" at line 89, column 132]

- Reached through: @spring.url "/imports/app/vendor/boot... [in template "config/pages/default-page.ftl" at line 4, column 54]

Java stack trace (for programmers): ---- freemarker.core.InvalidReferenceException: [... Exception message was already printed; see it above ...] at freemarker.core.InvalidReferenceException.getInstance(InvalidReferenceException.java:134) at freemarker.core.UnexpectedTypeException.newDesciptionBuilder(UnexpectedTypeException.java:85) at freemarker.core.UnexpectedTypeException.(UnexpectedTypeException.java:48) at freemarker.core.NonHashException.(NonHashException.java:49)

我猜 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 是旧版本。)