最近团队的一个项目在重构,希望引入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的更多相关文章

  1. spring mvc中添加对Thymeleaf的支持

    一.下载Thymeleaf 官方下载地址:https://dl.bintray.com/thymeleaf/downloads/thymeleaf/ 我下载的是最新的3.0.11版本 把包里的jar包 ...

  2. Atitti 存储引擎支持的国内点与特性attilax总结

    Atitti 存储引擎支持的国内点与特性attilax总结 存储引擎处理的事情: · 并发性:某些应用程序比其他应用程序具有很多的颗粒级锁定要求(如行级锁定). · 事务支持:并非所有的应用程序都需要 ...

  3. FBReader阅读引擎支持的功能

    "三十年河东,三十年河西"是一句民间谚语,它的来源是:从前黄河河道不固定,经常会改道(历史上无数次发生).某个地方原来在河的东面,若干年后,因黄河水流改道,这个地方会变为在河的西面 ...

  4. (转)支持Multi Range Read索引优化

    支持Multi Range Read索引优化 原文:http://book.51cto.com/art/201701/529465.htm http://book.51cto.com/art/2016 ...

  5. MyISAM、InnoDB、Memory这3个常用引擎支持的索引类型

    表格对比了MyISAM.InnoDB.Memory这3个常用引擎支持的索引类型: 索引 MyISAM引擎 InnoDB引擎 Memory引擎 B-Tree索引 支持 支持 支持 HASH索引 不支持 ...

  6. springboot框架中集成thymeleaf引擎,使用form表单提交数据,debug结果后台获取不到数据

    springboot框架中集成thymeleaf引擎,使用form表单提交数据,debug结果后台获取不到数据 表单html: <form class="form-horizontal ...

  7. Flutter 多引擎支持 PlatformView 以及线程合并解决方案

    作者:字节移动技术-李皓骅 摘要 本文介绍了 Flutter 多引擎下,使用 PlatformView 场景时不能绕开的一个线程合并问题,以及它最终的解决方案.最终 Pull Request 已经 m ...

  8. Spring Boot thymeleaf模版支持,css,js等静态文件添加

    Thymeleaf引入 Thymeleaf是一个Java模板引擎开发库,可以处理和生成HTML.XML.JavaScript.CSS和文本,在Web和非Web环境下都可以正常工作. 1.添加依赖包 & ...

  9. SpringBoot入门篇--Thymeleaf引擎模板的基本使用方法

    我们在使用SpringBoot框架的时候在前面已经介绍了Thymelea引擎模板,因为SpringBoot对JSP惨不忍睹的支持.那我们在使用引擎模板对前端页面进行渲染能够返回的情况下我们怎么才能在静 ...

随机推荐

  1. iOS 中 .a 和 .framework 静态库的创建与 .bundle 资源包的使用

    iOS 中 .a 和 .framework 静态库的创建与 .bundle 资源包的使用 前言 开发中经常使用三方库去实现某特定功能,而这些三方库通常又分为开源库和闭源库.开源库可以直接拿到源码,和自 ...

  2. NSNotification

    1.什么是NSNotification 每个运行中的application都有一个NSNotificationCenter的成员变量,它的功能就类似与公共栏,对象在这里注册关注每个确定Notifica ...

  3. vue搭建环境

    大早起的,没想自己起来那么早,既然起来了,就写点东西吧~最近在看Vue的东西,发现网上也是好多的资源,包括博客和视频 , 我是看的慕课网上的vue ,名字忘记了,价格148的,看了,也整理了笔记,看了 ...

  4. P2024食物链

    题目描述 动物王国中有三类动物 A,B,C,这三类动物的食物链构成了有趣的环形.A 吃 B,B 吃 C,C 吃 A. 现有 N 个动物,以 1 - N 编号.每个动物都是 A,B,C 中的一种,但是我 ...

  5. JavaWeb框架_Struts2_(七)----->文件的上传和下载

    这个章节是Struts2框架应用最广泛的三个版块(上传下载.国际化.校验输入)之一,所以这一版块的学习还蛮重要的. 1. 章节目录 Struts2文件上传 单文件上传 拦截器实现文件过滤 文件上传常量 ...

  6. 51Nod 2006 飞行员配对(二分图最大匹配)

    链接:http://www.51nod.com/onlineJudge/questionCode.html#!problemId=2006 思路: 二分匹配 注意n m的关系 代码: #include ...

  7. Nginx如何配置虚拟主机?

    注意,该环境是依赖于http://www.php20.com/forum.php?m ... &extra=page%3D1 基础上进行配置.默认不具备这些文件 .下面是增加一个mytest点 ...

  8. 数据库索引------Btree索引的使用限制

    1.如果不是按照索引最左列开始查找,则无法使用索引. 比如说id+name   那么是name+id 的话  ,这个索引则无法使用. 2.使用索引时不能跳过索引中的列.   如果是id+name+ag ...

  9. 以css伪类为基础,引发的选择器讨论 [新手向]

    作为第一篇技术干货,来写哪个方面的内容,我着实考虑了很久. 经过了整整30秒的深思熟虑,我决定就我第一次发现新大陆一样的内容,来进行一次讨论. 伪类:伪类对元素进行分类是基于特征(characteri ...

  10. 基于Java使用Snmp4j进行监控与采集(snmptrap、snmpwalk、snmpget)

    之前有在弄监控服务器这块的工作,今天来整体总结下.因为有些服务器(路由器.交换机等都是基于snmp协议的)必须使用snmp协议去监控采集和接收信息,所以必须去了解snmp相关内容,以及如何在基于jav ...