6

我目前正在迁移 Spring MVC Webapp(xml-config 到 java-config,tomcat 通过 spring-boot 到嵌入式 tomcat)。

webapp 使用 freemarker 作为模板引擎和 JSP Taglibs。现在,当我调用 freemarker 页面时,出现以下错误:

freemarker.ext.jsp.TaglibFactory$TaglibGettingException: 
No TLD was found for the "http://www.springframework.org/tags/form" JSP taglib URI. (TLD-s are searched according the JSP 2.2 specification. In development- and embedded-servlet-container setups you may also need the "MetaInfTldSources" and "ClasspathTlds" freemarker.ext.servlet.FreemarkerServlet init-params or the similar system properites.)

freemarker-header.ftl 以以下代码段开头:

<#assign form=JspTaglibs["http://www.springframework.org/tags/form"]>
<#assign core=JspTaglibs["http://java.sun.com/jstl/core"]>
<#assign spring=JspTaglibs["http://www.springframework.org/tags"]>
<#assign osc=JspTaglibs["/WEB-INF/osc.tld"]>

我没有找到 MetaInfTldSources 和 ClasspathTlds 的任何可用搜索结果。以前有人解决过这个问题吗?

KR哈比卜

4

5 回答 5

2

这真的应该是内置的。

FreeMarkerAutoConfiguration首先,禁用您的内置Application

@SpringBootApplication
@EnableAutoConfiguration(exclude = {FreeMarkerAutoConfiguration.class})
public class Application extends WebMvcConfigurerAdapter {
    ...
]

然后添加这个自定义配置:

(改编自https://github.com/isopov/fan/blob/master/fan-web/src/main/java/com/sopovs/moradanen/fan/WebApplicationConfiguration.java;添加了一个ObjectWrapperTaglibFactory删除了addResourceHandlers()覆盖)

import freemarker.cache.ClassTemplateLoader;
import freemarker.ext.jsp.TaglibFactory;
import freemarker.template.TemplateException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
import org.springframework.web.servlet.i18n.SessionLocaleResolver;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfig;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;
import org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver;

import javax.servlet.ServletContext;
import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Locale;
import java.util.Properties;

@Configuration
public class CustomFreemarkerConfiguration extends WebMvcConfigurerAdapter {


    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        LocaleChangeInterceptor localeChangeInterceptor = new LocaleChangeInterceptor();
        localeChangeInterceptor.setParamName("lang");
        registry.addInterceptor(localeChangeInterceptor);
    }

    @Bean
    public ReloadableResourceBundleMessageSource messageSource() {
        ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
        messageSource.setBasename("classpath:messages");
        messageSource.setFallbackToSystemLocale(false);
        messageSource.setDefaultEncoding("UTF-8");
        return messageSource;
    }

    @Bean
    public SessionLocaleResolver localeResolver() {
        SessionLocaleResolver localeResolver = new SessionLocaleResolver();
        localeResolver.setDefaultLocale(Locale.ENGLISH);
        return localeResolver;
    }

    @Bean
    @Autowired
    public freemarker.template.Configuration freeMarkerConfig(ServletContext servletContext) throws IOException,
            TemplateException {
        FreeMarkerConfigurer freemarkerConfig = configFreeMarkerConfigurer(servletContext);
        return freemarkerConfig.getConfiguration();
    }

    @Bean
    @Autowired
    public TaglibFactory taglibFactory(ServletContext servletContext) throws IOException, TemplateException {
        FreeMarkerConfigurer freemarkerConfig = configFreeMarkerConfigurer(servletContext);
        TaglibFactory taglibFactory = freemarkerConfig.getTaglibFactory();
        taglibFactory.setObjectWrapper(freemarker.template.Configuration.getDefaultObjectWrapper(freemarker.template.Configuration.getVersion()));
        return taglibFactory;
    }

    @Autowired
    @Bean
    public FreeMarkerConfig springFreeMarkerConfig(ServletContext servletContext) throws IOException, TemplateException {
        return new MyFreeMarkerConfig(freeMarkerConfig(servletContext), taglibFactory(servletContext));
    }

    private static FreeMarkerConfigurer configFreeMarkerConfigurer(ServletContext servletContext) throws IOException,
            TemplateException {
        FreeMarkerConfigurer freemarkerConfig = new FreeMarkerConfigurer();
        freemarkerConfig
                .setPreTemplateLoaders(new ClassTemplateLoader(CustomFreemarkerConfiguration.class, "/templates/"));
        ServletContext servletContextProxy = (ServletContext) Proxy.newProxyInstance(
                ServletContextResourceHandler.class.getClassLoader(),
                new Class<?>[] { ServletContext.class },
                new ServletContextResourceHandler(servletContext));
        freemarkerConfig.setServletContext(servletContextProxy);
        Properties settings = new Properties();
        settings.put("default_encoding", "UTF-8");
        freemarkerConfig.setFreemarkerSettings(settings);
        freemarkerConfig.afterPropertiesSet();
        return freemarkerConfig;
    }

    @Bean
    public FreeMarkerViewResolver viewResolver() {
        FreeMarkerViewResolver viewResolver = new FreeMarkerViewResolver();
        viewResolver.setCache(false);
        viewResolver.setSuffix(".ftl");
        viewResolver.setContentType("text/html;charset=UTF-8");
        return viewResolver;
    }


    private static class ServletContextResourceHandler implements InvocationHandler
    {

        private final ServletContext target;

        private ServletContextResourceHandler(ServletContext target) {
            this.target = target;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if ("getResourceAsStream".equals(method.getName())) {
                Object result = method.invoke(target, args);
                if (result == null) {
                    result = CustomFreemarkerConfiguration.class.getResourceAsStream((String) args[0]);
                }
                return result;
            } else if ("getResource".equals(method.getName())) {
                Object result = method.invoke(target, args);
                if (result == null) {
                    result = CustomFreemarkerConfiguration.class.getResource((String) args[0]);
                }
                return result;
            }

            return method.invoke(target, args);
        }
    }

    private static class MyFreeMarkerConfig implements FreeMarkerConfig {

        private final freemarker.template.Configuration configuration;
        private final TaglibFactory taglibFactory;

        private MyFreeMarkerConfig(freemarker.template.Configuration configuration, TaglibFactory taglibFactory) {
            this.configuration = configuration;
            this.taglibFactory = taglibFactory;
        }

        @Override
        public freemarker.template.Configuration getConfiguration() {
            return configuration;
        }

        @Override
        public TaglibFactory getTaglibFactory() {
            return taglibFactory;
        }
    }
}

将以下内容添加到您的pom.xml:

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
    </dependency>
    <dependency>
        <groupId>javax.servlet.jsp</groupId>
        <artifactId>jsp-api</artifactId>
        <version>2.0</version>
    </dependency>

然后你可以加载你的模板:

<#assign s=JspTaglibs["/META-INF/spring.tld"] />

<a href="${s.mvcUrl("IC#index").build()}">Home</a>
于 2016-01-24T10:23:46.563 回答
2

如果你知道怎么做,这实际上是一件容易的事。你需要的一切都已经嵌入到 FreeMarker 中,例如它是TaglibFactory.ClasspathMetaInfTldSource类。我花了几个小时来调查这个问题,所以我想分享一个解决方案。

我实现它是BeanPostProcessor因为现在无法在bean 初始化TaglibFactory之前进行设置。FreeMarkerConfigurer

import freemarker.ext.jsp.TaglibFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;

import java.util.Arrays;
import java.util.regex.Pattern;

/**
 * A {@link BeanPostProcessor} that enhances {@link FreeMarkerConfigurer} bean, adding
 * {@link freemarker.ext.jsp.TaglibFactory.ClasspathMetaInfTldSource} to {@code metaInfTldSources}
 * of {@link TaglibFactory}, containing in corresponding {@link FreeMarkerConfigurer} bean.
 *
 * <p>
 * This allows JSP Taglibs ({@code *.tld} files) to be found in classpath ({@code /META-INF/*.tld}) in opposition
 * to default FreeMarker behaviour, where it searches them only in ServletContext, which doesn't work
 * when we run in embedded servlet container like {@code tomcat-embed}.
 *
 * @author Ruslan Stelmachenko
 * @since 20.02.2019
 */
@Component
public class JspTagLibsFreeMarkerConfigurerBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof FreeMarkerConfigurer) {
            FreeMarkerConfigurer freeMarkerConfigurer = (FreeMarkerConfigurer) bean;
            TaglibFactory taglibFactory = freeMarkerConfigurer.getTaglibFactory();

            TaglibFactory.ClasspathMetaInfTldSource classpathMetaInfTldSource =
                    new TaglibFactory.ClasspathMetaInfTldSource(Pattern.compile(".*"));

            taglibFactory.setMetaInfTldSources(Arrays.asList(classpathMetaInfTldSource));
//            taglibFactory.setClasspathTlds(Arrays.asList("/META-INF/tld/common.tld"));
        }
        return bean;
    }
}

唯一的限制是*.tld文件里面必须有<uri>xml 标签。所有标准的弹簧/弹簧安全 TLD 都有它。而且这些文件必须在META-INF类路径的文件夹中,比如META-INF/mytaglib.tld. 所有标准 spring/spring-security TLD 也都遵循此约定。

注释行仅举例说明*.tld如果由于某种原因您不能将它们放置到标准位置(可能是一些不符合约定的外部 jar),您如何添加文件的“自定义”路径。它可以扩展到某种类路径扫描,搜索所有*.tld文件并将它们添加到classpathTlds. META-INF但通常,如果您的 TLD 遵循 JSP 约定放置在目录中,则不需要这样做。

我已经在我的 FreeMarker 模板中对此进行了测试,它可以工作:

<#assign common = JspTaglibs["http://my-custom-tag-library/tags"]>
<#assign security = JspTaglibs["http://www.springframework.org/security/tags"]>
<#assign form = JspTaglibs["http://www.springframework.org/tags/form"]>
<#assign spring = JspTaglibs["http://www.springframework.org/tags"]>

要使自定义标签(“ http://my-custom-tag-library/tags ”)起作用,它必须是*.tld文件,src/main/resources/META-INF/some.tld并且它必须包含<uri>xml 标签,例如<uri>http://my-custom-tag-library/tags</uri>. 然后它会被 FreeMarker 找到。

我希望它可以帮助某人节省几个小时来为这个问题找到“正确”的解决方案。

使用 spring-boot v2.0.5.RELEASE 测试

于 2019-02-20T02:10:00.243 回答
1

Spring Boot 不支持开箱即用的 Freemarker 使用 JSP 标签库。有一个您可能感兴趣的开放增强请求FreemarkerConfigurer。它包含一个可能的解决方法的链接,您可以在其中配置的标签库工厂,并从类路径加载一些额外的 TLD:

freeMarkerConfigurer.getTaglibFactory().setClasspathTlds(…);
于 2015-11-17T13:42:50.470 回答
0

这些解决方案都不适合我,但在分析了原始票证中的解决方法后,我发现了一个可行的解决方案:

1 - 在 pom.xml 中添加以下内容

    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-lang3</artifactId>
        <version>3.8.1</version>
    </dependency>
    <dependency>
        <groupId>javax.servlet.jsp</groupId>
        <artifactId>jsp-api</artifactId>
        <version>2.0</version>
    </dependency>

2 - 创建以下类

2.1 类路径TldsLoader

import org.apache.commons.lang3.ArrayUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;

import javax.annotation.PostConstruct;
import java.util.Arrays;
import java.util.List;

public class ClassPathTldsLoader  {

    private static final String SECURITY_TLD = "/META-INF/security.tld";

    final private List<String> classPathTlds;

    public ClassPathTldsLoader(String... classPathTlds) {
        super();
        if(ArrayUtils.isEmpty(classPathTlds)){
            this.classPathTlds = Arrays.asList(SECURITY_TLD);
        }else{
            this.classPathTlds = Arrays.asList(classPathTlds);
        }
    }

    @Autowired
    private FreeMarkerConfigurer freeMarkerConfigurer;

    @PostConstruct
    public void loadClassPathTlds() {
        freeMarkerConfigurer.getTaglibFactory().setClasspathTlds(classPathTlds);
    }


}

2.2 FreemarkerTaglibsConfig

import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FreemarkerTaglibsConfig {


    @Bean
    @ConditionalOnMissingBean(ClassPathTldsLoader.class)
    public ClassPathTldsLoader classPathTldsLoader(){
        return new ClassPathTldsLoader();
    }

}

3 - 现在您可以加载 ftl 文件,例如,安全库

<#assign spring = JspTaglibs["http://www.springframework.org/security/tags"]>

我希望这对其他人有用。

于 2019-05-12T01:54:06.563 回答
0

在渲染模板时,freemarker 调用 TaglibFactory,它以四种方式搜索 TLD:

  1. addTldLocationsFromClasspathTlds
  2. addTldLocationsFromWebXml
  3. addTldLocationsFromWebInfTlds
  4. addTldLocationsFromMetaInfTlds

所有这些方法都在 freemarker jar 的 TablibFactory 类中。最后一个,扫描 WEB-INF/lib 中的每个 jar,搜索 /META-INF/**/*.tld。如果启用了 freemarker 的调试模式,您可以看到此日志记录。

看看你的项目是如何部署的。在我的情况下,使用 eclipse、wtp、tomcat 和 maven,maven 依赖项在 Eclipse/Deployment 程序集中配置为 maven 依赖项,当然:),因此这些库不在 WEB-INF/lib 中,因此,未被找到addTldLocationsFromMetaInfTlds.

一种解决方法是强制部署将所有 maven 依赖项复制到 WEB-INF/lib。我在 Eclipse 视图“服务器”中打开服务器配置,在服务器选项下取消选中所有复选框,但“默认情况下模块自动重新加载”。

于 2016-05-07T18:35:41.487 回答