关于Thymeleaf的真相

Thymeleaf 一直以来都是个使用小众的模板引擎,在2.0以前,最为人吐槽的是性能跌到无底线。甚至朋友的项目因为Thymeleaf性能慢,影响到整个项目慢。Stackoveflow 社区也有很多人吐槽影响了自己的系统性能。
总所周知,系统单纯某一方面性能慢很难影响整个系统性能,如果系统慢,最有可能的是数据存取出问题,然而Thymeleaf能导致系统奇慢无比,确实是开源软件头一遭。
如果你不相信Thymeleaf2.0性能慢,可以参考 mbosecke/template-benchmark,或者国内的一个评测 https://my.oschina.net/smile622/blog/339884 (顺便提一下,这俩个性能基准测试,beetl都是最高的)

[图片上传失败...(image-171e2f-1512133068978)]

据说Thymeleaf3.0性能成倍的提高了,在我用上面的基准测试汇总,Thymeleaf3.0仍然比最慢的Freemaker还慢很多。希望3.0不会影响到系统性能

输入图片说明

关于性能这一块,并不是我打算揭示的真相,毕竟这一块,早有定论。我要揭示的Thymeleaf真相,是它宣称的浏览器能直接打开,适合前端开发这个特点,还有他宣称所谓的优雅语法,以及谣言它是Spring Boot 默认模板引擎

浏览器直接能打开模板?

Thymeleaf 网站首页重点介绍的是使用Thymeleaf编写的模板能直接用浏览器打开,所以适合前端开发使用。这是因果关系。可我认为,这个因也错了,果也错了。

Thymeleaf编写的模板并不是所有都可以用浏览器打开,简单的模板页面可以。这点Beetl也能做到。复杂的模板页面,Thymeleaf编写的模板用浏览器打开照样没有效果。

Thymeleaf官网首页忽悠出来的第一个例子举例子吧

<table>
  <thead>
    <tr>
      <th th:text="#{msgs.headers.name}">Name</th>
      <th th:text="#{msgs.headers.price}">Price</th>
    </tr>
  </thead>
  <tbody>
    <tr th:each="prod: ${allProducts}">
      <td th:text="${prod.name}">Oranges</td>
      <td th:text="${#numbers.formatDecimal(prod.price, 1, 2)}">0.99</td>
    </tr>
  </tbody>
</table>

这个模板给人的感觉似乎确实能让浏览器打开。因为他使用了th:text 属性,浏览器会忽略这个属性。所以浏览器打开,看的像一个静态的html页面。

如果所有的模板页面都是这么简单,那么beetl也能做到这点,比如定义beetl的定界符为

<!--:    -->

类似beetl的模板是这样

<table>
  <thead>
    <tr>
      <th >${local("headers.name")}</th>
      <th >${local("headers.price")}</th>
    </tr>
  </thead>
  <tbody>
    <!--: for(prod in allProducts){ -->
    <tr >
      <td >${prod.name}</td>
      <td >${prod.price,".##"}</td>
    </tr>
  <!--: } -->
  </tbody>
</table>

这段beetl改写的代码照样能用浏览器打开,你也许会认为,beetl模板打开后出现了占位符,貌似比Thymeleaf差一点,但我却认为,如果浏览器打开Thymeleaf模板的都是静态文本,你都不清楚哪儿是静态文本,哪儿是动态文本需要关注,这点还真不如Beetl更适合。

Beetl当然不是为了所谓能浏览器打开的模板设计的模板语言,只是尽力做到了浏览器能打开,比如Beetl的include标签实际上就努力尝试在重用模板的时候,又可以尽量让前端人员理解include内容,比如

<!--: include("/common/header.btl"){ -->
<script src="common.js" />
<script src="ext.js" />
<!--: } -->

这就是内置的include,为什么会带有{},从beetl设计刚开始的愿景就是能让前端团队尽量理解模板引擎。
然而,我知道,光靠浏览器能打开并不能做到这一点,况且,我认为Thymeleaf在这个功能宣传上过于夸大,并不能做到。

比如,还是上面这个例子,我需要根据条件判断以确定<td></td> 里显示的内容,那么我可能这么写

  <tr>
      <th th:text="#{msgs.headers.name}">Name</th>
      <th th:text="#{msgs.headers.price}">Price</th>
    </tr>
  </thead>
  <tbody>
    <tr th:each="prod: ${allProducts}">
        <td th:text="'****'"  >Oranges</td>
       <td th:text="${prod.name}" th:if="${admin==true}>Oranges</td>
      <td th:text="${#numbers.formatDecimal(prod.price, 1, 2)}">0.99</td>
    </tr>
  </tbody>

上述增加了一个if语句,比如admin情况下,可以显示名称。这样,一个tr下会出现三个td,这显然与th不对应,这样的Thymeleaf会显示成什么样呢?显然不是Thymeleaf所宣称的那个样子

再以Thymeleaf官网文档switch例子说明,如下一个switch语句用法,你真希望在浏览器显示3个段落吗?显然不是。

<div th:switch="${user.role}">
  <p th:case="'admin'">User is an administrator</p>
  <p th:case="#{roles.manager}">User is a manager</p>
  <p th:case="*">User is some other thing</p>
</div>

尽管Thymeleaf做了最大的努力,但遇到复杂的模板,我的意思是,在我们项目,哪怕最简单的一个项目,Thymeleaf模板都不可能实现他所谓的浏览器打开,这是个美好的愿望,Beetl 从7年前开始研发哪一天也有这样的愿望,但我深知是做不到的,Thymeleaf在努力做,花费了巨大的代价(庞大的奇怪的语法,会在后面提到),但不可能做到。

浏览器能预览模板又能怎么样?

Thymeleaf一直宣传的优势是能浏览器打开模板,前面已经说道,不可能实现。那么问题来了,就算简单的模板,浏览器打开了,对前端开发人员有多大意义(这里潜台词是前后分离方案)

我在Thymeleaf官网没有看到Thymeleaf对前后端分离的具体措施,也没有看到互联网上任何文档说明如何使用Thymeleaf做前后分离,反到是Beetl,提供了充分的前后端分离方案。

作为前端人员,如果他真的用Thymeleaf编写模板,他最大的需要并不是Thymeleaf反复提及的能在浏览器打开模板就行,这并不能保证模板是万无一失,可以放心交给后端人员集成了。他最需要的是能模拟后端各种数据,对模板做各种测试,好像模板真的经过后端渲染一样。这才是分离开发,这才前端人员真正需要的。
Beetl才能真正做到这一点,Beetl使用前端人员熟悉的JS语法和HTML扩展标签编写模板,Beetl的WebSimulate 插件能让前端人员去模拟后台数据,模拟出各种数据,各种分支情况,像真的后台调用一样。这时候,前端人员,打开浏览器访问,这才是真正的终极“浏览器打开模板”,而不是Thymeleaf那种假的“浏览器能预览模板”

Thymeleaf为这种假的“浏览器能预览模板” 付出了巨大的语法代价,如果你浏览Thymeleaf技术文档,你会发现,不可能让前端人员掌握Thymeleaf语法,你看看我的一个Thymeleaf部分语法贴图你就知道了


![![输入图片说明](http://upload-images.jianshu.io/upload_images/3589798-4c87976b1a7ff163.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240 "在这里输入图片标题")](https://static.oschina.net/uploads/img/201708/09224916_VhrM.png "在这里输入图片标题")
![![输入图片说明](http://upload-images.jianshu.io/upload_images/3589798-4c87976b1a7ff163.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240 "在这里输入图片标题")](https://static.oschina.net/uploads/img/201708/09224916_VhrM.png "在这里输入图片标题")

这部分语法只占Thymeleaf整体语法的5-10%。Thymeleaf的推崇者,你们确定以及肯定前端人员会去使用吗?Beetl不仅仅能前段后端分离,语法比这少多了,而且,都是前端人员熟悉的类似JS的语法。孰优孰劣,一看就知道。

所谓Thymeleaf的优雅语法

国内的Thymeleaf推崇者认为它的语法优雅。恕我直言,我实在欣赏不来Thymeleaf语法,我会列举出官网文档的语法例子,各位看官凭直觉看一看

Variable Expressions: ${...}
Selection Variable Expressions: *{...}
Message Expressions: #{...}
Link URL Expressions: @{...}
Fragment Expressions: ~{...}

以上表达式共同特点都是有{}符号,但不同的是前面有不同的符号,表示不同的意思,我这里懒得解释,因为Thymeleaf我也是新手..... 不小心非常容易出错,Beetl则只有一个标准的${} 引用。

如果你认为{} 符号很明确的话,那你就大错特错了,

{}
{{}}

{{}} 又完全是另外一个意思,这不符合程序员直觉,因为所有语言里,{} 和 {{}} 效果是一样的,Thymeleaf却表达了不同的含义。

Thymeleaf显然喜欢组合不同的符号来完成特定的模板渲染,其他模板语言则通常使用更为常见的函数调用,格式化函数来完成。一个模板渲染引擎,真没有必要搞那么复杂语法。

如果这只是恶心到你,但并没有让你混乱,那我们接着看看Thymeleaf更多的语法

<div th:if="${user.isAdmin()} == false"> 

<div th:if="${user.isAdmin() == false}"> ..

这俩种是不同写法,但是等价的,前提是前者使OGNL引擎,后者使用了SpringEL引擎。在Spring上下文里,Thymeleaf的表现有很大不同,所以你要非常清楚,不同的引擎,Thymeleaf应该怎么去写。

你也许有点混乱了,不知道下面的写法是不是更为混乱(都是官网的例子)

<span th:text="${onevar} + ' ' + |${twovar}, ${twovar}|">

<div th:with="isEven=(${prodStat.count} % 2 == 0)">

第一段如果你没看懂它的意思,我也不会解释。 至于第二段代码,意义较为明确,但如果我想扩展这个表达式,增加一个变量,那我应该是下面那种写法呢?

<div th:with="isEven=(${prodStat.count+rate} % 2 == 0)">

或者

<div th:with="isEven=( (${prodStat.count}+${rate} )% 2 == 0)">

我并不清楚,Thymeleaf怎么写,更体会不到Thymeleaf的优雅了。
如果是beetl模板,就很明确,就是一个类似js的表达式,在占位符里${ } 随意书写

<div style= "${ (prodStat.count+rate)%2==0?"event":"odd" } >

如你所见,Thymeleaf语法体系非常混乱,而且语法庞大,我上面的那个贴图不过Thymeleaf的语法5%左右,还有更多的Thymeleaf等待那些所谓的推崇者去挖掘,去写博客介绍,去介绍“回字的四种写法”(来自鲁迅的《孔乙己》)

Beetl很少有使用者写博客介绍Beetl,这是因为Beetl足够简单,普通功能真没有写博客介绍的必要。

Spring Boot 所谓内置Thymeleaf

国内有些Thymeleaf推崇者试图以Spring Boot 所谓内置Thymeleaf来表明使用Thymeleaf是一条很正确的道路。实际上并不是这样

首先,Spring Boot 每一类型技术,都会集成几种技术,比如Cache, 集成了自家的Redis,也有EhCache,Hazelcast,Spring Boot 并没有明确说出来,Spring Boot 推荐使用哪种技术。Spring Boot提供了多种选择。这非常公平

其次,Spring Boot 默认配置并不是代表最好。以REST Template为例子,Spring Boot默认使用的是JDK URL Connection,这难道比其他可选的HTTPClient,OKHttp更好吗?显然不是,还有Spring Boot提供的数据库连接池,是Tomcat的一款,这难道会比Druid,HikariCP更好,显然也不是

最后,Spring Boot为什么会在模板引擎里集成除了Freemaker,Groovy外,还会集成Thymeleaf呢,我想最大的原因是Thymeleaf深度使用了Spring技术,比如上一节提到的Spring SpEL(相当于其核心使用了SpEL),还有未提到,Thymeleaf官网说的Conversion Service 。正是因为这种如此深度集成,才使得Spring Boot 会选择Thymeleaf作为其中的一个模板引擎候选。而Velocity作为Apache体系技术,且7年不维护(恰好今天2.0发布了),当然不会作为候选。

Beetl是我7年前开始研发的模板语言,广泛在国内使用,客户有一流的互联网公司,国内大型企业,也有小型的创业公司,个人用户。Beetl是真正经得起考验的技术。很长时间来,我在模板引擎领域积累的经验足以使我对Thymeleaf有一个基本判断。 我都在犹豫是否要写一篇这样的文章来让更多的人了解Thymeleaf真相,毕竟有“利益”冲突,怕难以服众和被嘲笑。真心希望看了此文的开发人员支持我的化能点赞留言支持,不支持我的化请说出你的明确观点。

最后,想对Thymeleaf说一句们我们行业内的一句经常调侃的话,“老外的东西,只有到了中国好使,那才是真正的好使”。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,200评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,526评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,321评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,601评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,446评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,345评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,753评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,405评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,712评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,743评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,529评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,369评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,770评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,026评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,301评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,732评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,927评论 2 336

推荐阅读更多精彩内容