Thymeleaf引擎支持Multi Prefix
最近团队的一个项目在重构,希望引入Thymeleaf减少页面端的代码复杂性。在重构过程中,发现html文件需要保存在多个不同的目录中,但Thymeleaf缺省的实现不支持这种方式。
1 背景
Maven项目,前端使用SpringMVC,没有使用任何模板引擎。所有的页面内容,都是通过静态HTML+AJAX+JSON形式实现。
1.1 项目结构
Html文件,通过mvc:resource 定义路径。
1.1.1 Html保存路径
/hardess_finance/src/main/webapp/WEB-INF/htmls
在该目录放一个Demo文件/demo/hello.html
1.1.2 Spring配置文件
<mvc:resources mapping="/**/**.html" location="/WEB-INF/htmls/"/>
项目启动后,浏览器访问 http://localhost:8080/demo.hello.html,就可以访问到demo文件。
1.2 添加Thymeleaf支持
Spring Boot 项目缺省使用Thymeleaf模板,但普通SpringMVC项目,需要手工添加支持。大致步骤包括:
1.2.1 Pom.xml增加thymeleaf dependency
|
<dependency> <groupId>org.thymeleaf</groupId> <artifactId>thymeleaf</artifactId> <version>3.0.6.RELEASE</version> </dependency> <dependency> <groupId>org.thymeleaf</groupId> <artifactId>thymeleaf-spring3</artifactId> <version>3.0.6.RELEASE</version> </dependency> |
1.2.2 修改Spring Config文件
|
<bean id="templateResolver" class="org.thymeleaf.spring3.templateresolver.SpringResourceTemplateResolver"> <property name="prefix"><value>/WEB-INF/html/</value></property> <property name="suffix"><value>.html</value></property> <property name="templateMode"><value>HTML</value></property> <property name="characterEncoding"><value>UTF-8</value></property> <property name="cacheable" value="false"/> </bean> <bean id="templateEngine" class="org.thymeleaf.spring3.SpringTemplateEngine"> <property name="templateResolver" ref="templateResolver" /> </bean> <bean class="org.thymeleaf.spring3.view.ThymeleafViewResolver"> <property name="templateEngine" ref="templateEngine" /> <property name="characterEncoding" value="UTF-8"/> </bean> |
<property name="prefix"><value>/WEB-INF/html/</value></property>这个参数,指定的就是html文件的保存路径。
1.2.3 添加Thymeleaf Controller
在代码中增加一个Controller
|
@Controller public class ThymeleafCommonController { @RequestMapping(value = { "/**/**.html" }) public ModelAndView index(HttpServletRequest request) { return new ModelAndView(); } } |
至此,项目重新启动后,所有html的访问,都已经通过Thymeleaf引擎,html中能够使用 th:text 等各种thymeleaf语法。
1.3 项目重构希望添加另一个html保存路径
在重构过程中,希望将html文件保存到新的目录 src/main/resources/templates目录,原因有二:
1、 这是Spring Boot项目的缺省模板路径,适应将来可能升级到SpringBoot的需求。
2、 重构后的代码,希望同原目录有所区分,简化开发复杂度。
1.3.1 简单尝试
在 mvc:resources标签中,location可以是用逗号隔开的多个路径,如
|
<mvc:resources mapping="/scripts/**" location="/WEB-INF/scripts/, classpath:/static/scripts/"/> |
因此,尝试在spring config配置文件中,尝试修改配置
|
<property name="prefix"> <value>/WEB-INF/html/,classpath:/templates/</value> </property> |
重启后测试,发现项目无法工作,原有的界面都无法加载了。
1.3.2 原因
Debug了一下thymeleaf的相关源码,发现它使用下面的语句生成最终的完整路径名,并没有判断 prefix 是否是逗号分隔的数组。
AbstractConfigurableTemplateResolver.computeResourceName(…)
|
return prefix + unaliasedName + suffix; |
2 Thymeleaf源码解读
解读Thymeleaf的源代码,发现几个相关类
2.1 相关类结构

2.2 final computeTemplateResource()
这个函数会读取配置的prefix,并调用后续方法生成 resource name。注意,这个方式是 final ,无法重载。
|
@Override protected final ITemplateResource computeTemplateResource( final IEngineConfiguration configuration, final String ownerTemplate, final String template, final Map<String, Object> templateResolutionAttributes) { final String resourceName = computeResourceName(configuration, ownerTemplate, template, this.prefix, this.suffix, this.forceSuffix, this.templateAliases, templateResolutionAttributes); return computeTemplateResource(configuration, ownerTemplate, template, resourceName, this.characterEncoding, templateResolutionAttributes); } |
标红部分读取prefix参数值。
2.3 computeResourceName()
实际生成resource name(代码有删减,只保留核心部分)
|
protected String computeResourceName( final IEngineConfiguration configuration, final String ownerTemplate, final String template, final String prefix, final String suffix, final boolean forceSuffix, final Map<String, String> templateAliases, final Map<String, Object> attributes) { … String unaliasedName = templateAliases.get(template); if (unaliasedName == null) { unaliasedName = template; } … // hasPrefix && shouldApplySuffix return prefix + unaliasedName + suffix; } |
2.4 computeResolvable()
判断资源文件是否可用。
|
if (this.resolvablePatternSpec.isEmpty()) { return true; } return this.resolvablePatternSpec.matches(template); |
这个代码,实际没有校验html文件是否存在,只要语法不出错即可。当系统定义了多个ITemplateResolver时,引擎回依次调用每个实例的computeResolvable()方法,如果返回null,则依次检查下一个resolver,直到得到一个非空值。
3 解决方案
基于前面的代码分析,要解决我们的需求,首先我们需要解决的是判断资源文件是否真实存在。
3.1 判断文件是否存在
通过Spring项目的ApplicationContext判断文件是否存在的代码片段。
|
resolvable = false; Resource resource = applicationContext.getResource(location); if (resource != null && resource.exists()) { resolvable = true; } |
为了验证解决方案的可行性,增加了一个新的html文件在 src/main/resources/templates/demo/world.html
3.2 方案一:定义多个 TemplateResolver
3.2.1 Custom TemplateResolver
Spring提供的实现类SpringResourceTemplateResolver,代码比较简单,我选择直接替换该类,而不是从它继承而来。
|
public class CodeStoryTemplateResolver extends AbstractConfigurableTemplateResolver implements ApplicationContextAware { private ApplicationContext applicationContext = null; public CodeStoryTemplateResolver() { super(); } public void setApplicationContext(final ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } @Override protected boolean computeResolvable( IEngineConfiguration configuration, String ownerTemplate, String template, Map<String, Object> templateResolutionAttributes) { boolean resolvable = super.computeResolvable(configuration, ownerTemplate, template, templateResolutionAttributes); if (resolvable) { // 判断文件是否存在 resolvable = false; String pathName = getPrefix() + template + getSuffix(); Resource resource = applicationContext.getResource(pathName); if (resource != null && resource.exists()) { resolvable = true; } } return resolvable; } @Override protected ITemplateResource computeTemplateResource( final IEngineConfiguration configuration, final String ownerTemplate, final String template, final String resourceName, final String characterEncoding, final Map<String, Object> templateResolutionAttributes) { return new SpringResourceTemplateResource( this.applicationContext, resourceName, characterEncoding); } } |
3.2.2 修改Spring配置
|
<bean id="webinfoTemplateResolver" class="....CodeStoryTemplateResolver"> <property name="prefix"> <value>/WEB-INF/</value> </property> <property name="suffix"> <value>.html</value> </property> <property name="templateMode"> <value>HTML</value> </property> <property name="characterEncoding"> <value>UTF-8</value> </property> <property name="cacheable" value="false"/> </bean> <bean id="webinfoTemplateResolver" class="....CodeStoryTemplateResolver"> <property name="prefix"> <value>classpath:/templates/</value> </property> <property name="suffix"> <value>.html</value> </property> <property name="templateMode"> <value>HTML</value> </property> <property name="characterEncoding"> <value>UTF-8</value> </property> <property name="cacheable" value="false"/> </bean> <bean id="templateEngine" class="org.thymeleaf.spring3.SpringTemplateEngine"> <property name="templateResolvers"> <set> <ref bean="webinfoTemplateResolver" /> <ref bean="classpathTemplateResolver" /> </set> </property> </bean> <bean class="org.thymeleaf.spring3.view.ThymeleafViewResolver"> <property name="templateEngine" ref="templateEngine" /> <property name="characterEncoding" value="UTF-8"/> </bean> |
重启系统测试,/demo/hello.html和/demo/world.html都能正常访问。
3.3 方案二:一个TemplateResolver支持prefixes
3.3.1 Custom TemplateResolver
理想的方案,是重载函数computeTemplateResource(),但这个函数被定义为final,无法重载,只好退而求其次选择重载computeResourceName()。在这个函数中判断是否定义了prefixes参数,如果是一次调用父类的computeResourceName()并判断资源是否存在。
|
public class CodeStoryTemplateResolver extends AbstractConfigurableTemplateResolver implements ApplicationContextAware { private ApplicationContext applicationContext = null; public void setApplicationContext(final ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } private String prefixes = null; public CodeStoryTemplateResolver() { super(); } public final String getPrefixes() { return this.prefixes; } public final void setPrefixes(final String prefixes) { this.prefixes = prefixes; } protected String computeResourceName(final IEngineConfiguration configuration, final String ownerTemplate, final String template, final String prefix, final String suffix, final boolean forceSuffix, final Map<String, String> templateAliases, final Map<String, Object> templateResolutionAttributes) { String resourceName = null; String[] prefixes = null; if (!StringUtils.isEmptyOrWhitespace(getPrefixes())) { prefixes = getPrefixes().split(","); } else if (!StringUtils.isEmptyOrWhitespace(getPrefix())) { prefixes = new String[] { getPrefix() }; } else { prefixes = new String[] { "" }; } for (String onePrefix : prefixes) { onePrefix = StringUtil.trimLeft(StringUtil.trimRight(onePrefix)); resourceName = super.computeResourceName(configuration, ownerTemplate, template, onePrefix, suffix, forceSuffix, templateAliases, templateResolutionAttributes); Resource resource = applicationContext.getResource(resourceName); if (resource != null && resource.exists()) { break; } else { resourceName = null; } } return resourceName; } @Override protected ITemplateResource computeTemplateResource( final IEngineConfiguration configuration, final String ownerTemplate, final String template, final String resourceName,final String characterEncoding, final Map<String, Object> templateResolutionAttributes) { return new SpringResourceTemplateResource(this.applicationContext, resourceName, characterEncoding); } } |
3.3.2 修改Spring配置
|
<bean id="multiTemplateResolver" class="....CodeStoryTemplateResolver"> <property name="prefixes"> <value>/WEB-INF/,classpath:/templates/</value> </property> <property name="suffix"> <value>.html</value> </property> <property name="templateMode"> <value>HTML</value> </property> <property name="characterEncoding"> <value>UTF-8</value> </property> <property name="cacheable" value="false"/> </bean> <bean id="templateEngine" class="org.thymeleaf.spring3.SpringTemplateEngine"> <property name="templateResolvers"> <set> <ref bean="multiTemplateResolver" /> </set> </property> </bean> <bean class="org.thymeleaf.spring3.view.ThymeleafViewResolver"> <property name="templateEngine" ref="templateEngine" /> <property name="characterEncoding" value="UTF-8"/> </bean> |
重启系统测试,/demo/hello.html和/demo/world.html都能正常访问。
3.4 啰嗦几句
两种方案区别不大,都是尝试增加文件判断,便于Thymeleaf找到真正的html所在路径。配置上,第二种方案相对简单一点。
性能方面,没有做仔细测试,估计比Spring缺省的TemplateResolver会慢一些。
当然,这个方案有点多次一举,最简单的处理方式,把目录src/main/webapps/WEB-INF/htmls 移动到 src/main/resources/templates即可。
Thymeleaf引擎支持Multi Prefix的更多相关文章
- spring mvc中添加对Thymeleaf的支持
一.下载Thymeleaf 官方下载地址:https://dl.bintray.com/thymeleaf/downloads/thymeleaf/ 我下载的是最新的3.0.11版本 把包里的jar包 ...
- Atitti 存储引擎支持的国内点与特性attilax总结
Atitti 存储引擎支持的国内点与特性attilax总结 存储引擎处理的事情: · 并发性:某些应用程序比其他应用程序具有很多的颗粒级锁定要求(如行级锁定). · 事务支持:并非所有的应用程序都需要 ...
- FBReader阅读引擎支持的功能
"三十年河东,三十年河西"是一句民间谚语,它的来源是:从前黄河河道不固定,经常会改道(历史上无数次发生).某个地方原来在河的东面,若干年后,因黄河水流改道,这个地方会变为在河的西面 ...
- (转)支持Multi Range Read索引优化
支持Multi Range Read索引优化 原文:http://book.51cto.com/art/201701/529465.htm http://book.51cto.com/art/2016 ...
- MyISAM、InnoDB、Memory这3个常用引擎支持的索引类型
表格对比了MyISAM.InnoDB.Memory这3个常用引擎支持的索引类型: 索引 MyISAM引擎 InnoDB引擎 Memory引擎 B-Tree索引 支持 支持 支持 HASH索引 不支持 ...
- springboot框架中集成thymeleaf引擎,使用form表单提交数据,debug结果后台获取不到数据
springboot框架中集成thymeleaf引擎,使用form表单提交数据,debug结果后台获取不到数据 表单html: <form class="form-horizontal ...
- Flutter 多引擎支持 PlatformView 以及线程合并解决方案
作者:字节移动技术-李皓骅 摘要 本文介绍了 Flutter 多引擎下,使用 PlatformView 场景时不能绕开的一个线程合并问题,以及它最终的解决方案.最终 Pull Request 已经 m ...
- Spring Boot thymeleaf模版支持,css,js等静态文件添加
Thymeleaf引入 Thymeleaf是一个Java模板引擎开发库,可以处理和生成HTML.XML.JavaScript.CSS和文本,在Web和非Web环境下都可以正常工作. 1.添加依赖包 & ...
- SpringBoot入门篇--Thymeleaf引擎模板的基本使用方法
我们在使用SpringBoot框架的时候在前面已经介绍了Thymelea引擎模板,因为SpringBoot对JSP惨不忍睹的支持.那我们在使用引擎模板对前端页面进行渲染能够返回的情况下我们怎么才能在静 ...
随机推荐
- 微信小程序与Java后台的通信
一.写在前面 最近接触了小程序的开发,后端选择Java,因为小程序的代码运行在腾讯的服务器上,而我们自己编写的Java代码运行在我们自己部署的服务器上,所以一开始不是很明白小程序如何与后台进行通信的, ...
- scrapy初试水 day01
1.安装pip install Scrapy#一定要以管理员身份运行dos窗口conda install scrapy2.创建项目scrapy startproject hello3.在hello/s ...
- POST和GET有什么区别?
1. GET主要用于从服务器查询数据,POST用于向服务器提交数据 2. GET通过URL传递数据,POST通过http请求体传递数据 3. GET传输数据量有限制,不能大于2kb,POST传递的数据 ...
- angularjs 利用$http 请求出现 400 Bad Request
1. 出现400错误-代表错误的请求,说明我们的参数有问题 说明此时传入的参数存在问题,我们看下此时参数的格式是什么: 此时的参数是对象格式,查了一下,如果利用ajax格式传输数据的话,参数必须是js ...
- System.ServiceModel.CommunicationException: 已超过传入消息(65536)的最大消息大小配额。若要增加配额,请使用相应绑定元素上的 MaxReceivedMessageSize 属性。
- loadrunner 参数存储在data.ws、paralist、globals.h 中区别(参数与变量额区别于使用)
1.如果变量数据只有一个值,可以直接放在data.ws 中 2.如果变量要根据循环取随机值.序列值等(参数存在一组值),放在paralist中 3.如果是申明全局变量,且要在代码中用到参 ...
- Hadoop之SequenceFile
Hadoop序列化文件SequenceFile能够用于解决大量小文件(所谓小文件:泛指小于black大小的文件)问题,SequenceFile是Hadoop API提供的一种二进制文件支持.这样的二进 ...
- 中颖内带LED资源驱动代码
//上一篇写了LCD驱动,本篇写下LED驱动 //DISPCON 最高位为1时, 选择LED驱动,LCD驱动无效 最高位为0时, 选择LCD驱动.LED驱动无效 void Sh79fLed_Init( ...
- backbone入门学习一
初识backbone 1.Backbone是什么? Backbone是一个非常轻量级的javaScript库,可以打造为模型(Model)-视图(View)-控制器(Controller)即MVC类结 ...
- git上传到github
一. Git创建 1. git init 命令来初始化一个Git仓库 2. 添加文件到Git仓库,分两步 1) 使用命令 git add <file>,可以反复多次添加,添加多个文件 2) ...