springboot中Thymeleaf的使用

一. 什么是Thymeleaf

Thymeleaf是面向Web和独立环境的现代服务器端Java模板引擎。
Thymeleaf的主要目标是为您的开发工作流程带来优雅的自然模板 - 可以正确显示在浏览器中的HTML,也可以作为静态原型工作,从而在开发团队中进行更强大的协作。
随着Spring框架的模块,与您最喜欢的工具的集成,以及插入自己的功能的能力,Thymeleaf是现代HTML5 JVM Web开发的理想选择,尽管它可以做的更多。

好吧,我承认刚才那段是Thymeleaf官方的说明,我只不过机翻了一下。下面咱们说点人话。Thymeleaf就是jsp的高端升级版。

二. 什么情况适合使用Thymeleaf

Thymeleaf显然是一个开发页面的技术,现在各种前端技术层出不穷,比如现在主流的Vue、React、AngularJS等。很多人可能会要问,这个Thymeleaf相对于这些前端框架到底有啥优势。
其实,Thymeleaf跟那些前端框架根本不是一个类型的东西,也没有啥可比性。

Thymeleaf和老牌的jsp属于非前后分离的思路来开发的。后端通过数据渲染html的模板,渲染后模板就是个完整的html页面,将页面返回给请求方。

主流的前端框架是基于前后端分离的思路来开发的,前端页面通过ajax来调用后端的rest接口来获取数据,再通过js进行渲染页面(不管什么前端技术其实都是对js进行了封装,js依然是底层核心)。

使用前后分离主要有下面几个好处

  1. 因为每次请求服务器获取的数据从整个页面变成了仅仅是核心数据,加载速度明显提升。
  2. 前端人员和后端人员可以互相独立开发,最后在通过接口联调即可。以前是不分前端工程师、后端工程师的,现在前后分离后,才出现了这样的分类。而且现在前端技术也越来越先进。前后分离以后可以方便两条技术路线的人员各自钻研自己的技术。
  3. 前端页面脱离后端服务器后,可以和后端分开部署。这时就可以对前端页面的服务器进行一些专门的网络优化进一步提高访问速度。
  4. 后端只需要一套rest接口就可以同时服务于电脑页面、IOS客户端、安卓客户端。甚至现在还有些前端技术可以直接把前端页面打包成IOS、安卓的客户端。
  5. ......

说了这么多前后分离的好处,你可能就要问了,那我们为什么还要用那个看起来那么low的模板引擎呢?

为了速度。前后分离方式,前端页面通过ajax来调用后端的rest接口来获取数据,再通过js进行渲染页面。获取数据和通过js渲染页面的代码,很多时候比页面本身要多的多,而且通过js来操作dom进行渲染,稍微复杂些的页面往往就会把渲染逻辑搞的错综复杂。相信从jsp时代一路走到现在的老程序员都深知工作量是成倍的往上翻。

固然刚才列举了前后分离的种种好处,但这些好处大多数都是集中在app开发上,其他某些场景下这些好处并不明显。最典型的一个场景就是管理后台。管理后台往往对页面的花哨性要求不高,并发量也不大,而且功能往往还不少。这种场景下,前后分离技术上导致的工作量大幅度增加,人员上分离导致额外的联调成本都成了不少的负担。Thymeleaf作为模板引擎这时候优势就非常大。只需要在html原型的页面上稍微加几个标签,即可完成渲染。而且加上的标签并不影响原型页面直接通过html打开。

说了这么多,总结一下,Thymeleaf是一个供后端人员使用的,为快速开发页面而生的Java模板引擎。

三. 如何在Springboot中引入Thymeleaf

Thymeleaf作为spring官方推荐的模板引擎,在spring体系中使用异常方便。这里以gradle构建的项目为例来说明。
首先,你要先修改build.gradle引入Springboot对Thymeleaf提供的依赖包。在dependencies中增加如下配置。

compile('org.springframework.boot:spring-boot-starter-thymeleaf')

等待gradle帮你自己下载完依赖包后,你可以看到引入的Thymeleaf的版本。

Thymeleaf依赖包

嗯?springboot1.5.7默认引用的Thymeleaf依赖包居然还是2.1.5版本。最新的Thymeleaf不是已经更新3.x版本了么。如果我想使用最新版的Thymeleaf要怎么办呢。
在build.gradle文件中,buildscript下增加配置,完整的配置如下图

ext['thymeleaf.version'] = '3.0.7.RELEASE'
ext['thymeleaf-layout-dialect.version'] = '2.2.2'
完整gradle配置

等待gradle下载完成,你可以看到引入的Thymeleaf已经是最新版本了。

四. 快速入门

接下来要在项目中使用Thymeleaf了,这里用一个简单的单表查询来举个栗子。
一般来说,开发一个需要渲染数据的页面,分为三个步骤。

  1. 开发静态页面,即常说的模型。
  2. 获取数据。
  3. 使用数据对静态页面进行渲染。

这里我们先做第一个步骤,开发静态页面。为了简单,就不做任何css了,下面是页面的源码。

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>标题</title>
</head>
<body>
  <strong>标题</strong>
  <form action="list.html" method="post">
    <input type="hidden" name="pageNumber">
    用户名:<input type="text" name="username">
    <br/>
    姓名:<input type="text" name="name">
    <br/>
    <button type="submit">提交</button> <button type="reset">重置</button>
  </form>
  <table>
    <thead>
      <tr>
        <th class="am-hide-sm-only">id</th>
        <th>用户名</th>
        <th>姓名</th>
        <th class="am-hide-sm-only">电话</th>
        <th class="am-hide-sm-only">邮箱</th>
        <th class="am-hide-sm-only">是否可用</th>
        <th>操作</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>id</td>
        <td>用户名</td>
        <td>姓名</td>
        <td>电话</td>
        <td>邮箱</td>
        <td>
          <span>可用</span><span>不可用</span>
        </td>
        <td><button>修改</button><button>删除</button></td>
      </tr>
    </tbody>
  </table>
</body>
</html>

直接使用浏览器打开该页面,长成这样。

静态页面

现在有了静态页面,该获取数据了。下面是controller层的代码。

/**
 * 用户管理
 */
@Controller
@RequestMapping("/users")
public class UserController
{
    @Autowired
    private UserRepository userRepository;

    @Value("${pageSize}")
    private Integer pageSize;

    /**
     * 分页查询信息
     */
    @RequestMapping(method = {RequestMethod.GET, RequestMethod.POST})
    public String list(Model model, User user, @RequestParam(value = "pageNumber", required = false, defaultValue = "0") Integer pageNumber)
    {
        ExampleMatcher matcher = ExampleMatcher.matching().withStringMatcher(ExampleMatcher.StringMatcher.CONTAINING);
        PageRequest pageRequest = new PageRequest(pageNumber, pageSize);

        Page<User> page = userRepository.findAll(Example.of(user, matcher), pageRequest);

        //分页查询数据
        model.addAttribute("page", page);
        //查询条件
        model.addAttribute("user", user);
        //页面标题
        model.addAttribute("title", "用户管理");
        //转到待渲染模板,所有模板都在templates文件夹下,users/list指templates文件夹下的users文件夹下的list.html页面。
        return "users/list";
    }
}

这里使用spring-data-jpa从数据库里查询到了记录并和查询条件、页面标题一起转到待渲染的模板。这里,我们将刚才的静态页面文件复制到对应的位置。如下图。

Thymeleaf模板摆放位置

下面,我们要对该文件进行适当的改造,使之成为一个Thymeleaf模板文件。先贴上改造后的文件。

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
  <title th:text="${title}">标题</title>
  <meta charset="UTF-8">
</head>
<body>
  <strong th:text="${title}">标题</strong>
  <form th:action="@{/users}" th:object="${user}" method="post">
    <input type="hidden" name="pageNumber" th:value="${page.number}">
    用户名:<input type="text" name="username" th:value="*{username}">
    <br/>
    姓名:<input type="text" name="name" th:value="*{name}">
    <br/>
    <button type="submit">提交</button> <button type="reset">重置</button>
  </form>
  <table>
    <thead>
      <tr>
        <th class="am-hide-sm-only">id</th>
        <th>用户名</th>
        <th>姓名</th>
        <th class="am-hide-sm-only">电话</th>
        <th class="am-hide-sm-only">邮箱</th>
        <th class="am-hide-sm-only">是否可用</th>
        <th>操作</th>
      </tr>
    </thead>
    <tbody>
      <tr th:each="user : ${page.content}">
        <td th:text="${user.id}">id</td>
        <td th:text="${user.username}">用户名</td>
        <td th:text="${user.name}">姓名</td>
        <td th:text="${user.phone}">电话</td>
        <td th:text="${user.email}">邮箱</td>
        <td th:switch="${user.enabled}">
          <span th:case="true">可用</span><span th:case="false">不可用</span>
        </td>
        <td><button>修改</button><button>删除</button></td>
      </tr>
    </tbody>
  </table>
</body>
</html>

可以看到,我们对html代码的结构丝毫未改动,只是在一些标签里面添加了th:xxx="yyy"的属性。
我们重新使用浏览器打开Thymeleaf改造过的html文件。发现虽然我们添加了那么多th:xxx="yyy"的标签,但是,页面居然和之前一模一样。

Thymeleaf改造后页面

下面我们启动服务,通过controller定义的那个url来访问渲染后的页面。

渲染后的页面

同一个文件,浏览器直接打开就是原型,服务器渲染后打开就是真实的功能页面。
这里我们就可以看出Thymeleaf的一个核心功能,就是将其逻辑注入到模板文件中,不会影响模板被用作设计原型。做到了原型即页面。

Thymeleaf的核心语法就是th:xxx="yyy",即设置html标签中xxx属性的值为yyy对应的值。

五. 设置属性

我们先来说一说th:xxx的部分,即设置属性。

Thymeleaf的核心功能就是通过在html标签里面追加属性。可以设置的属性非常多,详细的可以参考 Thymeleaf所有属性的还没发文档。这里我们就挑选些常用的举几个例子,其他大家可以举一反三推断出用法。

  • th:object="yyy" 将对象作为一个范围内可用的变量。一般和选择表达式*{zzz}配合使用,选择表达式后面会讲到。

  • th:text="yyy" 这个属性可以添加到几乎所有分为头尾两部分<></>的html标签中,如<title></title><td><td/>等。th:text="yyy"的作用是把表达式yyy对应的值添加到标签的中间。
    <td th:text="user">用户名</td>渲染后就是<td>user<td/>

  • th:value="${title}" 这个属性一般和<input />标签搭配使用,用来设置<input />标签的value值。
    <input th:value="username" />渲染后就是<input value="username" />

六. 表达式

下面我们再来对这些th:xxx="yyy""yyy"的部分进行讲解。这个yyy我们一般称之为表达式。
Thymeleaf里面表达式主要有以下几种。

  • ${yyy} 变量表达式,用来获取上下文对象里面的值(controller返回的model)。还是以上面的例子来说明,如果我想要取到page对象中的number属性,使用${page.number}即可。
变量表达式
  • #{yyy} 消息表达式,根据消息的key来获取消息内容。一般是用来做国际化用的。
  • *{yyy} 选择表达式,跟变量表达式用法差不多,但变量表达式是获取上下文里的对象,选择表达式是获取一个选择的对象。
    选择表达式一般和th:object=标签配合使用,还是以上面的例子来说明。
    先用th:object="${user}"选择了上下文中的user对象,下面想使用user对象的username属性时,直接使用*{username}就可以了。
    你可能想要问,我直接使用${user.username}不是一样可以找到user对象的username属性么,为什么还要再搞个选择表达式?
    因为${user.username}是先从下上文找到user,对象,再从user对象里找到username属性;而*{username}是直接从user对象里找到username属性。当需要从一个对象里获取很多属性的时候,使用选择表达式可以提高效率。
选择表达式
  • @{yyy} 链接表达式 设置超链接时用的表达式,一般和th:actionth:href配合使用。
链接表达式
  • ~{page :: fragment} 分段表达式,主要用作公共模块的复用,一般和th:insertth:replace搭配使用。以后讲模块复用的时候再细说。

  • yyy 文字。可以为字符串、数字、布尔、null。如<td th:text="user">用户名</td>渲染后为<td>user</td>

  • _ 无操作。下划线是thymeleaf表达式的特殊字符,如果表达式就一个下划线,则什么也不做。例如<td th:text="_">用户名</td>渲染后依然是<td>用户名</td>

七. 迭代器

我们在渲染页面时,经常需要对一个list进行循环处理,最典型的场景就是使用表格展示多条数据。这时,就需要使用到thymeleaf的迭代器th:each

<tr th:each="user : ${users}">
  <td th:text="${user.id}">id</td>
  <td th:text="${user.username}">用户名</td>
</tr>

在这个例子中,users是一个list,通过迭代器th:each对其进行遍历,每次迭代获取到的对象为user。在th:each属性的对应的标签之间<tr th:each="user : ${users}">...</tr>,为user对象的有效范围。

有时候,我们还想要知道迭代器的一些状态属性,如总数,当前索引等。可以通过如下方法获取。

<tr th:each="user,stat : ${users}">
<td th:text="${stat.index}">index</td>  
<td th:text="${user.id}">id</td>
  <td th:text="${user.username}">用户名</td>
</tr>

th:each=""的第二个变量stat,就是迭代器的状态变量,从这个状态变量里面可以获取到很多我们想要的属性,主要有下面这些。

  • stat.index 当前对象在list中的索引。从0开始。
  • stat.countindex差不多,也是当前对象在list中的索引,不过是从1开始。
  • stat.size 迭代器中元素的总数。
  • stat.current 当前迭代的对象。
  • stat.even 当前迭代的索引是否是奇数,索引指stat.index
  • stat.odd 当前迭代的索引是否是偶数,索引指stat.index
  • stat.first 当前迭代的对象是否是迭代器中的第一个。
  • stat.last 当前迭代的对象是否是迭代器中的最后一个。

八. 条件语句

th:if="boolean" th:if的表达式需为boolean值。如果为true,则标签显示,如果为false,则标签不显示。
th:unless="boolean" th:unlessth:if相反,表达式也需为boolean值。如果为true,则标签不显示,如果为false,则标签显示。

<span th:if="${stat.odd}">偶</span>
<span th:unless="${stat.odd}">奇</span>
条件判断

th:swtich 一般和 th:case结合使用 。和java语言中的swtich case语法用法类似。

<td th:switch="${user.enabled}">
  <span th:case="true">可用</span><span th:case="false">不可用</span>
</td>
switch

九. 工具类

Thymeleaf提供了一些工具类,这里举个简单的例子展示下用法,其他详细的可以查看Thymeleaf工具类官方文档

#lists 数组工具类

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

推荐阅读更多精彩内容

  • Thymeleaf简介 前面的例子我们使用的视图技术主要是JSP。JSP的优点是它是Java EE容器的一部分,几...
    乐百川阅读 8,975评论 2 56
  • Thymeleaf 简易教程 本文源码可以在这里下载: https://github.com/codergege/...
    codergege阅读 50,653评论 4 30
  • Thymeleaf 一直以来都是个使用小众的模板引擎,在2.0以前,最为人吐槽的是性能跌到无底线。甚至朋友的项目因...
    闲大赋阅读 7,066评论 1 12
  • 某人,你一定想不到的是风轻云淡是最撕心裂肺。我也没想到。 我应该是太迟钝了,像很多人一样,最不了解的还是自己。有人...
    智商堪忧阅读 119评论 0 0
  • 一朵芙蕖,开过尚盈盈,芊芊淑女,旗袍韵娉婷,炎炎夏日一清凉,您是否有兴趣来见证这场美丽邂逅?麒麟水乡 2017年“...
    麒麟区农旅投王倩阅读 185评论 0 0