一. 数据准备
1. 想要使用thymeleaf,需要先在pom.xml中添加相关依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>
2. 接下来准备再一个简单的页面index.html,在resources下的templates文件下,新建index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>我是孙著杰的同桌</h1> </body> </html>
3. 最后编写Controller
package io.zhangjia.springboot3.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; @Controller public class WebController { @GetMapping("/index") public String index(){ return "index"; } }
4. 接下来访问http://localhost:8888/index,即可显示:我是孙著杰的同桌,可能细心的同学已经注意到,我们并没有配置视图解析器为return “index”添加前缀和后缀,但是为什么依旧能够成功访问index.html呢?原因是因为SpringBoot已经默认为我们配置了视图解析器,打开External中的Maven:org.springframework.boot:spring-boot-autoconfigure:2.1.6.RELEASE,找到thymeleaf—>ThymeleafAutoConfiguration,点击其中ThymeleafProperties.class,即可看到以下内容:
@ConfigurationProperties( prefix = "spring.thymeleaf" ) public class ThymeleafProperties { private static final Charset DEFAULT_ENCODING; public static final String DEFAULT_PREFIX = "classpath:/templates/"; public static final String DEFAULT_SUFFIX = ".html"; .... private boolean cache; .... }
所以我们只需要将html页面放在类路径的templates文件下,页面便可正常被转发。
5. 接下来再次修改html页面, 添加以下内容:
<h1>孙著杰是我的同桌</h1>
右击选择Recompile ' index. html' (快捷键 Ctrl+Shift+F9,相当于之前的packge),此时回到http://localhost:8888/index,并刷新页面,新添加的h1标签的内容却没有显示,这是因为缓存默认被关闭,在配置文件中将其开启即可:
spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/demo2?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC username: root password: zhangjia type: com.alibaba.druid.pool.DruidDataSource thymeleaf: cache: false
三. th的简单使用
修改Controller,添加某个数据到Request作用域
@Controller public class WebController { @GetMapping("/index") public String index(Model model){ String name = "zhangjia"; model.addAttribute("name",name); return "index"; } }
因为此时的视图页面不再是.jsp,而是.html,所以我们可以使用thymeleaf来显示数据,使用thymeleaf可以实现静动分离,举个简单的例子,在之前的项目练习中,我们都是先从网上找到相应的前端模板,然后将其后缀从html修改为jsp,假如该模板中有一个h2标签用于显示用户姓名:
<h2>姓名</h2>
则我们需要先将姓名而在删除,将其修改为:
<h2>${name}</h2>
而如果使用thymeleaf,我们便可以不删除姓名,而是采用下面的方式:
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.w3.org/1999/xhtml"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h2 th:text="${name}">姓名</h2> </body> </html>
此时访问http://localhost:8888/index,页面却不是姓名二字,而是显示zhangjia
同理,如果是其他属性,也可以直接在th后面修改,比如修改input:
<input th:type="password" placeholder="请输入密码" th:class="jia-input"/>
但是值得注意的是,a标签的href,或者图片的src属性等在thymeleaf中不能通过上述方式直接使用th编写,而需要使用@{xxx}将地址包裹:
<a href="#" th:href="@{https://www.google.com/}">谷歌</a>
同理,css、js等文件的引入如果使用的是在线资源, 是不需要动态获取的,但是如果使用的是本地资源,则必须动态获取
<head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/4.1.0/css/bootstrap.min.css"> <script src="https://cdn.staticfile.org/jquery/3.2.1/jquery.min.js"></script> <!--如果使用本地的css文件,则这样写: <link rel="stylesheet" href=".../static/css/bootstrap.min.css"> 只这样写,是不能动态获取的,只有静态获取才能成功 需要改成下面的样式 <link rel="stylesheet" th:href="@{css/bootstrap.min.css}" href=".../static/css/bootstrap.min.css"> --> </head>
注意,动态获取的时候资源地址不需要添加../static/,原因如下:
我们可以打开External中的Maven:org.springframework.boot:spring-boot-autoconfigure:2.1.6.RELEASE,找到web—>Servlet—>ResourceProperties,即可看到以下内容:
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = new String[]{"classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/"};
也就是说,我们的静态资源的存放位置有上面的四种方式,所以在动态获取的时候,不需要再添加../static
三. 循环迭代
假如我们有以下内容的JSON字符串
[{"address":{"create_time":1563861382000,"user_id":4001,"name":"张三","tel":"13666666666","addr":"山东省济宁市","is_default":1,"aid":6001,"status":1},"create_time":1563931245000,"total_price":19964.0,"user_id":4001,"order_id":7001,"status":1,"products":[{"quantity":2,"img_url":"59ec3325N906f107e.jpg","price":8588.0,"dbid":8001,"product_id":3010,"name":"Apple 苹果 iPhone X 全面屏手机 深空灰色 全网通 64GB","order_id":7001},{"quantity":1,"img_url":"5abb0fe7N5f2b7ed2.jpg","price":2788.0,"dbid":8002,"product_id":3022,"name":"Apple iPad 平板电脑 2018年新款9.7英寸(128G WLAN版/A10芯片/Retina屏 MR7J2CH/A)深空灰色","order_id":7001}]},{"address":{"create_time":1563861427000,"user_id":4001,"name":"李四","tel":"13888889999","addr":"山东省青岛市","is_default":0,"aid":6002,"status":1},"create_time":1563935813000,"total_price":2599.0,"user_id":4001,"order_id":7002,"status":1,"products":[{"quantity":1,"img_url":"597ae1efNc6dd8fe4.jpg","price":2599.0,"dbid":8003,"product_id":3012,"name":"小米6 全网通 6GB+128GB 陶瓷黑尊享版 移动联通电信4G手机 双卡双待","order_id":7002}]}]
将该json字符串格式化后显示如下:
[ { "address":{ "create_time":1563861382000, "user_id":4001, "name":"张三", "tel":"13666666666", "addr":"山东省济宁市", "is_default":1, "aid":6001, "status":1 }, "create_time":1563931245000, "total_price":19964, "user_id":4001, "order_id":7001, "status":1, "products":[ { "quantity":2, "img_url":"59ec3325N906f107e.jpg", "price":8588, "dbid":8001, "product_id":3010, "name":"Apple 苹果 iPhone X 全面屏手机 深空灰色 全网通 64GB", "order_id":7001 }, { "quantity":1, "img_url":"5abb0fe7N5f2b7ed2.jpg", "price":2788, "dbid":8002, "product_id":3022, "name":"Apple iPad 平板电脑 2018年新款9.7英寸(128G WLAN版/A10芯片/Retina屏 MR7J2CH/A)深空灰色", "order_id":7001 } ] }, { "address":{ "create_time":1563861427000, "user_id":4001, "name":"李四", "tel":"13888889999", "addr":"山东省青岛市", "is_default":0, "aid":6002, "status":1 }, "create_time":1563935813000, "total_price":2599, "user_id":4001, "order_id":7002, "status":1, "products":[ { "quantity":1, "img_url":"597ae1efNc6dd8fe4.jpg", "price":2599, "dbid":8003, "product_id":3012, "name":"小米6 全网通 6GB+128GB 陶瓷黑尊享版 移动联通电信4G手机 双卡双待", "order_id":7002 } ] } ]
可以看到该JSON字符串中存放了一个用户的两个订单信息,其中第一个订单中有两个商品,第二个订单中有一个商品,接下来在Controller中将字符串转换为Java对象,并将其存入request作用域,并在视图中显示相关的数据,如果是JSP页面,一般我们会采用JSTL和EL来遍历处理这类数据,而thymeleaf也提供了相应的循环遍历的语法,即:th:each:
<div th:each="order : ${orders}" class="card"> <div class="card-header"> <!-- 方法一: --> <!-- <span th:text="${'下单时间:' + order.create_time}">下单时间:2019-6-7 16:13:28</span>--> <!-- 方法二: --> <span th:text="${'下单时间:' + #dates.format(order.create_time, 'yyyy-MM-dd sHH:mm:ss')}">下单时间:2019-6-7 16:13:28</span> <span th:text="${'订单号:' + order.order_id}">订单号:14</span> </div> <div class="card-body"> <table class="table table-sm table-bordered"> <tr th:each="product,status : ${order.products}"> <td class="w-100px"> <img class="img-thumbnail" width="70px" height="70px" src="http://iph.href.lu/80x80?text=1" alt=""> </td> <td th:text="${product.name}"> Apple iPhone XR (A2108) 128GB 黑色 移动联通电信4G手机 双卡双待 </td> <td class="w-50px text-center" th:text="${product.quantity}">1</td> <td class="w-100px text-center" th:text="${#numbers.formatCurrency(product.price)}">¥6699.00</td> <td class="w-120px" th:if="${status.index == 0}" th:rowspan="${status.size}" rowspan="2" > <span th:text="${order.address.name}">张三<br></span> <span th:text="${order.address.tel}">186XXXX2865<br></span> <span th:text="${order.address.addr}">山东省济宁市高新区XX路XX大厦XXX室</span> </td> <td class="w-120px" th:if="${status.index == 0}" th:rowspan="${status.size}" rowspan="2"> 总金额:<br> <span th:text="${#numbers.formatCurrency(order.total_price)}">¥31,495.00</span> </td> </tr> </table> </div> </div>
在上⾯代码中,th:each=”order : ${orders}” 的功能其实类似于foreach:即for(var order: orders) ,也就是说orders是被迭代的变量,而order为迭代变量,orders中存放了两个订单中的所有数据,而每个order中存放的都是一个订单的数据。
同理,<tr th:each=”product,status : ${order.products}”> 中的order.products则存放是的一个订单中的所有商品,而product则是每个商品的数据。
另外,product后面的status存放的是迭代相关的信息:
-
index:第几次循环,下标从0开始
-
count:第几次循环,下标从1开始
-
size:一共循环几次
-
current:当前正在循环的迭代变量
另外th:if=”${status.index == 0}”相当于判断,如果status.index == 0,则会显示该标签
四. 引入页面中公共部分
假如我们的网站中有一百个页面,这一百个页面肯定会有一些公共的部分内容是相同的,比如菜单、网站底部信息等等,在jsp中我们可以使用静态导入或者动态导入的方法来解决这个问题,相应的,thymeleaf也为我们提供了插入或者替换两种方式来将页面中的公共部分引入
这里以任意界面的菜单为例:
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>首页 - 电子商城</title> </head> <body> <header > <div class="container"> <nav class="navbar navbar-expand-lg navbar-light bg-light rounded"> <a class="navbar-brand" href="#">电子商城1</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarsExample09" aria-controls="navbarsExample09" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarsExample09"> <ul class="navbar-nav mr-auto"> <li class="nav-item active"> <a class="nav-link" href="#">首页<span class="sr-only">(current)</span></a> </li> <li class="nav-item"> <a class="nav-link" href="#">电脑</a> </li> <li class="nav-item"> <a class="nav-link" href="#">手机</a> </li> <li class="nav-item"> <a class="nav-link" href="#">平板</a> </li> <li class="nav-item dropdown"> <a class="nav-link dropdown-toggle" href="#" id="dropdown09" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">张小三</a> <div class="dropdown-menu" aria-labelledby="dropdown09"> <a class="dropdown-item" href="#">个人中心</a> <a class="dropdown-item" href="#">购物车</a> <a class="dropdown-item" href="#">收货地址</a> <a class="dropdown-item" href="#">我的订单</a> <a class="dropdown-item" href="#">退出登录</a> </div> </li> </ul> <form class="form-inline my-2 my-md-0"> <input class="form-control" type="text" placeholder="Search" aria-label="Search"> <input type="submit" class="btn btn-outline-secondary" value="搜索"> </form> </div> </nav> </div> </header> <main role="main" class="container"> ...... </main> <footer class="fdb-block footer-small"> ...... </footer> </body> </html>
在上述代码中,<header>标签的代码即为网站的菜单,也就是每个页面都会拥有的公平部分,我们可以新建一个专门用于存放公共部分的页面的_common.html,然后将header标签中的内容存入:
_common.html:
<header th:fragment="head"> <div class="container"> <nav class="navbar navbar-expand-lg navbar-light bg-light rounded"> <a class="navbar-brand" href="#">电子商城</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarsExample09" aria-controls="navbarsExample09" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarsExample09"> <ul class="navbar-nav mr-auto"> <li class="nav-item active"> <a class="nav-link" href="#">首页<span class="sr-only">(current)</span></a> </li> <li class="nav-item"> <a class="nav-link" href="#">电脑</a> </li> <li class="nav-item"> <a class="nav-link" href="#">手机</a> </li> <li class="nav-item"> <a class="nav-link" href="#">平板</a> </li> <li class="nav-item dropdown"> <a class="nav-link dropdown-toggle" href="#" id="dropdown09" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">张小三</a> <div class="dropdown-menu" aria-labelledby="dropdown09"> <a class="dropdown-item" href="#">个人中心</a> <a class="dropdown-item" href="#">购物车</a> <a class="dropdown-item" href="#">收货地址</a> <a class="dropdown-item" href="#">我的订单</a> <a class="dropdown-item" href="#">退出登录</a> </div> </li> </ul> <form class="form-inline my-2 my-md-0"> <input class="form-control" type="text" placeholder="Search" aria-label="Search"> <input type="submit" class="btn btn-outline-secondary" value="搜索"> </form> </div> </nav> </div> </header>
其中th:fragment=”head” 相当于为该部分代码起了一个别名,用来唯一标识。同样的,如果需要定义页面中的其他公共部分,只需要在_common.html中添加多个th:fragment=”xxx”即可,无需为每公共部分单独定义一个文件。
接下来演示使用插入和替换两种方式在每个页面使用这些公共部分
1. 插入
<header th:insert="_common :: head"> <!-- header中的原内容也可以不删除--> </header>
2. 替换
<header th:replace="_common :: head"> <!-- header中的原内容也可以不删除--> </header>
其中_common即为_common.html的文件名,而head即为要导入的公平部分的唯一标识。
另外,如果采用插入的方式,那么在_common.html中,需要将
<header th:fragment="head"> <div class="container"> .... </div> </header> 修改为: <div th:fragment="head" class="container"> .... </div>
这样的做的原因是因为插入会将_common.html中的代码原封不等的插入进header标签内,所以如果不修改,则页面的代码会变成:
<header > <header> <div class="container"> ..... </div> </header> </header>
而替换则不需要考虑上述问题。另外还有th:include这种导入方式,但是官方建议在3.0之后不推荐使用
请登录之后再进行评论