Spring-在嵌入式 Tomcat 中使用 JSP 标签库启动
Spring-Boot with JSP Tag Libs in embedded Tomcat
我目前正在迁移 Spring MVC Webapp(xml-config 到 java-config,tomcat 到嵌入式 tomcat 通过 spring-boot).
Web 应用程序使用 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 搜索结果。以前有人解决过这个问题吗?
韩国
哈比
Spring Boot 不支持开箱即用的 JSP 标签库和 Freemarker。有一个您可能感兴趣的 open enhancement request。它包含一个 link 到一个可能的解决方法,您可以在其中配置 FreemarkerConfigurer
的标签库工厂,其中包含一些要从类路径加载的额外 TLD:
freeMarkerConfigurer.getTaglibFactory().setClasspathTlds(…);
这确实应该是 built-in。
首先,禁用 Application
上的内置 FreeMarkerAutoConfiguration
:
@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;向 TaglibFactory
添加了 ObjectWrapper
并删除了 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>
在渲染模板时,freemarker调用TaglibFactory,它通过四种方式搜索TLD:
- addTldLocationsFromClasspathTlds
- addTldLocationsFromWebXml
- addTldLocationsFromWebInfTlds
- addTldLocationsFromMetaInfTlds
所有这些方法都在 freemarker jar 的 TablibFactory class 中。最后一个,扫描 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 视图 'servers' 下打开服务器配置,在服务器选项下取消选中所有复选框,但 'Module auto reload by default'.
如果你知道怎么做,这其实是一件容易的事。所有你需要的都已经嵌入到 FreeMarker 中,例如它是 TaglibFactory.ClasspathMetaInfTldSource
class。我花了几个小时来调查那个问题,所以我想分享一个解决方案。
我将其实现为 BeanPostProcessor
因为现在无法在 FreeMarkerConfigurer
bean 初始化之前设置 TaglibFactory
。
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 标签。所有标准 spring/spring-security TLD 都有它。而且这些文件必须位于 class 路径的 META-INF
文件夹内,例如 META-INF/mytaglib.tld
。所有标准 spring/spring-security TLD 也遵循此约定。
注释行只是一个例子,说明如果由于某种原因您不能将它们放入标准位置(可能是一些外部 jar,它不会'遵循惯例)。它可以扩展到某种 class 路径扫描,搜索所有 *.tld
文件并将它们添加到 classpathTlds
中。但如果您的 TLD 遵循 JSP 约定放置在 META-INF
目录中,通常不需要这样做。
我已经在我的 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") 起作用,它必须是 src/main/resources/META-INF/some.tld
中的 *.tld
文件,并且必须包含 <uri>
xml标签,例如 <uri>http://my-custom-tag-library/tags</uri>
。到时候会被FreeMarker找到。
我希望它可以帮助某人节省几个小时来找到 "right" 解决此问题的方法。
使用 spring-boot v2.0 测试。5.RELEASE
None 这些解决方案对我有用,但在分析了 original ticket 中的解决方法后,我找到了一个可行的解决方案:
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 ClassPathTldsLoader
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"]>
我希望这对其他人有用。
我目前正在迁移 Spring MVC Webapp(xml-config 到 java-config,tomcat 到嵌入式 tomcat 通过 spring-boot).
Web 应用程序使用 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 搜索结果。以前有人解决过这个问题吗?
韩国 哈比
Spring Boot 不支持开箱即用的 JSP 标签库和 Freemarker。有一个您可能感兴趣的 open enhancement request。它包含一个 link 到一个可能的解决方法,您可以在其中配置 FreemarkerConfigurer
的标签库工厂,其中包含一些要从类路径加载的额外 TLD:
freeMarkerConfigurer.getTaglibFactory().setClasspathTlds(…);
这确实应该是 built-in。
首先,禁用 Application
上的内置 FreeMarkerAutoConfiguration
:
@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;向 TaglibFactory
添加了 ObjectWrapper
并删除了 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>
在渲染模板时,freemarker调用TaglibFactory,它通过四种方式搜索TLD:
- addTldLocationsFromClasspathTlds
- addTldLocationsFromWebXml
- addTldLocationsFromWebInfTlds
- addTldLocationsFromMetaInfTlds
所有这些方法都在 freemarker jar 的 TablibFactory class 中。最后一个,扫描 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 视图 'servers' 下打开服务器配置,在服务器选项下取消选中所有复选框,但 'Module auto reload by default'.
如果你知道怎么做,这其实是一件容易的事。所有你需要的都已经嵌入到 FreeMarker 中,例如它是 TaglibFactory.ClasspathMetaInfTldSource
class。我花了几个小时来调查那个问题,所以我想分享一个解决方案。
我将其实现为 BeanPostProcessor
因为现在无法在 FreeMarkerConfigurer
bean 初始化之前设置 TaglibFactory
。
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 标签。所有标准 spring/spring-security TLD 都有它。而且这些文件必须位于 class 路径的 META-INF
文件夹内,例如 META-INF/mytaglib.tld
。所有标准 spring/spring-security TLD 也遵循此约定。
注释行只是一个例子,说明如果由于某种原因您不能将它们放入标准位置(可能是一些外部 jar,它不会'遵循惯例)。它可以扩展到某种 class 路径扫描,搜索所有 *.tld
文件并将它们添加到 classpathTlds
中。但如果您的 TLD 遵循 JSP 约定放置在 META-INF
目录中,通常不需要这样做。
我已经在我的 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") 起作用,它必须是 src/main/resources/META-INF/some.tld
中的 *.tld
文件,并且必须包含 <uri>
xml标签,例如 <uri>http://my-custom-tag-library/tags</uri>
。到时候会被FreeMarker找到。
我希望它可以帮助某人节省几个小时来找到 "right" 解决此问题的方法。
使用 spring-boot v2.0 测试。5.RELEASE
None 这些解决方案对我有用,但在分析了 original ticket 中的解决方法后,我找到了一个可行的解决方案:
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 ClassPathTldsLoader
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"]>
我希望这对其他人有用。