写在前面:除摘要中的一些内容还有一些在使用Thymeleaf时可能遇到的小问题,由于写在一切可能篇幅过长内容过杂,因此这些小问题以及其部分解决方案放在下一篇文章中。最后,对于一些大牛来说这篇文章可能灌水严重,但本人是出于记录以及回忆之前知识的目的写下的此片文章,排版问题,内容有误请各位尽情指正
开门见山,先比较一下有/没有使用Thymeleaf的区别,好直观感受Thymeleaf能带给我们什么
没有添加Thymeleaf依赖的情况
在Spring Boot应用中,一般把网页存放在`src/main/resources/stati目录下 :
在Controller中,会自动加载static下的html内容,因此可以通过如下方式在网页中返回一个html页面:
import org.springframework.stereotype.Controller;
@Controller
public class HelloControl {
public String say(){
return "hello.html";
}
}
定义返回类型为Stringreturn "hello.html"返回的是html文件路径
当执行这段代码的时候,Spring Boot实际加载的是
src/main/resources/static/hello.html文件
return语句返回的是static目录下的文件,现在如果我们的html文件放在
则我们的代码应该是:
import org.springframework.stereotype.Controller;
@Controller
public class HelloControl {
public String say(){
return "html/hello.html";
}
}
注意文件路径使用的是
/进行分割
添加Thymeleaf依赖的情况
@Controller
public class SongListControl {
@Autowired
private SongListService songListService;
@RequestMapping("/songlist")
public String index(@RequestParam("id")String id,Model model){
SongList songList = songListService.get(id);
//传递歌单对象到模板当中
//第一个 songList 是模板中使用的变量名
// 第二个 songList 是当前的对象实例
model.addAttribute("songList",songList);
return "songList";
}
}
Spring MVC 中对于模板文件是有固定的位置存放位置,放在工程src/main/resources/templates
因此对于添加过Thymeleaf的工程:return "songList"其实会去查找src/main/resources/templates/songList.html文件
系统会自动匹配后缀,因此你不需要写成return "songList.html";
(仅为参考图片,图中文件名可能与上述描述不一致)
-
模板文件的后缀虽然也是 .html,大部分内容和html很像,但因为它放置在
`src/main/resources/templates目录下,而且里面可以写变量th:text="${...}",所以它其实不是 HTML 文件,而是 thymeleaf 模板 。放在
src/main/resources/static目录下的就不是模板,是静态文件➤ 另外需要注意:
xmlns:th="http://www.thymeleaf.org"写在html文件中的作用是能让软件识别thymeleaf语法
Thymeleaf用法
模板变量
读取变量:${variable symbol name}(固定格式)
一般情况下,模板的变量都是会存放在模板上下文中,所以我们如果想要调用变量,就需要先设置变量到模板上下文中去
即在方法中声明model.addAttribute("songList",songList);就可以完成 上下文变量的设置
- 第一个参数设置的就是上下文变量名(变量名是可以随便定义)
- 第二参数设置的是变量值(可以是任意的对象)
th是Thymeleaf 的缩写,所以你如果看到 th:开头的标签属性那么就代表使用的是Thymeleaf语法
-
变量
- 读取变量:
${variable symbol name}(固定格式)
一般情况下,如果想要调用变量,就需要先添加变量到模板上下文中去,即在方法中声明
model.addAttribute("songList",songList);就可以完成上下文变量的设置
第一个参数设置的就是上下文变量名(变量名是可以随便定义)
第二参数设置的是变量值(可以是任意的对象)th:text:动态替换掉html标签的内部文本内容
<span th:text="${msg}">Hello</span>这段代码的执行结果就是用变量msg的值替换了span标签内的Hello文本,若msg变量值为你好,则渲染后效果为: - 读取变量:
<span>你好</span>
如果变量名值为一个对象,其中属性值是可以用"
."一直点出来的,前提是这个对象得是POJO类
循环语句(可嵌套循环)
-
th:each<ul th:each="song : ${songs}"> <li th:text="${song.name}">歌曲名称</li> </ul>${songs}是从模板上下文中获取 songs 这个变量song 是
${songs}变量遍历后的每一个对象${song.name}就可以读取遍历中歌曲名称了@RequestMapping("/demo") public String index(Model model){ List<Song> songs = new ArrayList<>(); Song song = new Song(); song.setId("0001"); song.setName("朋友"); songs.add(song); song = new Song(); song.setId("0002"); song.setName("夜空中最亮的星"); songs.add(song); model.addAttribute("songs",songs); return "demo"; } <ul th:each="song,it: ${songs}"> <li> <span th:text="${it.count}"></span> <span th:text="${song.name}"></span> </li> </ul>打印列表索引值:
th:each="song,it: ${songs}",你会发现多了一个,it 这个it是作为可选参数出行,如果定义了就可以通过这个it对象来获取更多关于统计的需求;
此处it.xxxit为官方变量名,不可更改为其他变量(例如:song.index……)
-
it.index当前迭代对象的index(从0开始计算),如果想从0开始显示行数用这个就可以了
-
it.count当前迭代对象的index(从1开始计算),如果显示行数用这个就可以了
-
it.size被迭代对象的大小,如果想获取列表长度,用这个就可以了
-
it.current当前迭代变量,等同与上面的song
-
it.even/odd布尔值,当前循环是否是偶数/奇数(从0开始计算)
-
it.first/last布尔值,当前循环是否是第一
最后一个模板中的布尔值需要结合条件语句来处理
表达式
Thymelaeaf表达式主要用于两种场景
- 字符串处理
- 数据转化
字符串拼接
我们一般会看到视频这样的视频显示格式:00:00/45:00——代表这个视频从00:00开始,总共45分钟,在Thymeleaf中需要利用+来完成字符串的拼接
@RequestMapping("/demo")
public String index(Model model){
String totalTime = "45:00";
model.addAttribute("totalTime",totalTime);
return "demo";
}
<span th:text="'00:00/'+${totalTime}"></span>
需要注意的是'00:00/'
''的作用:将文本变为java字符串,此时两个字符串可以利用+来进行拼接
,因此当我们不使用''将00:00/包裹住时
<span th:text="00:00/+${totalTime}"></span>
程序会发生报错:
Wed Jan 08 14:40:15 CST 2020
There was an unexpected error (type=Internal Server Error, status=500).
Could not parse as expression: "00:00/+${totalTime}" (template: "demo" - line 7, col 9)
字符串拼接优化:
我们还可以使用||包裹住字符串,此时可免去+进行字符串的拼接
<span th:text="|00:00/${totalTime}|"></span>
数据转化
Thymeleaf 默认集成了大量的工具类可以方便的进行数据转化,一般我们使用最多的是 dates
如果你想处理 LocalDate 和 LocalDateTime 类,你可以在pom.xml添加如下依赖
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-java8time</artifactId>
<version>3.0.4.RELEASE</version>
</dependency>
这个库会自动添加一个新的工具类 temporals
工具类运用与变量的不同:变量使用 ${变量名} ,工具类使用 #{工具类}
dates/temporals
dates 和temporals 支持的方法是一样的,只是支持的类型不同,dates 支持的是Date类,temporals 支持的是 LocalDate和 LocalDateTime
java.util.Date 类和 LocalDateTime 类功能是一样的,不同的是 LocalDateTime是Java8才出现的,一些老的应用还是用Date
类的。具体可以搜索一下看看文档
@RequestMapping("/demo")
public String index(Model model){
Date dateVar = new Date();
model.addAttribute("dateVar",dateVar);
return "demo";
}
我们一般使用 dates/temporals 用于处理日期类型到字符串的转化,比如显示年月日
<p th:text="${#dates.format(dateVar, 'yyyy-MM-dd')}"></p>
<p th:text="${#dates.format(dateVar, 'yyyy年MM月dd日')}"></p>
显示时分秒
<p th:text="${#dates.format(dateVar, 'yyyy-MM-dd HH:mm:ss')}"></p>
<p th:text="${#dates.format(dateVar, 'yyyy年MM月dd日 HH时mm分ss秒')}"></p>
假如日期类型是LocalDate/LocalDateTime那么就把#dates换成#temporals
Strings
${#strings.toUpperCase(name)}
把字符串改成全大写
${#strings.toLowerCase(name)}
把字符串改成全小写
${#strings.arrayJoin(array,',')}
把字符串数组合并成一个字符串,并以,连接,比如
["a","b"]执行后会变成a,b
${#strings.arraySplit(str,',')}
把字符串分隔成一个数组,并以,作为分隔符,比如a,b执行后会变成
["a","b"];如果
abc没有匹配到,执行后会变成["abc"]
${#strings.trim(str)}
把字符串去空格,左右空格都会去掉
${#strings.length(str)}
得到字符串长度,也支持获取集合类长度
${#strings.equals(str1,str2)}
比较两个字符串是否相等
${#strings.equalsIgnoreCase(str1,str2)}
忽略大小写比较两个字符串是否相等
内敛表达式
[[变量]]支持我们在HTML中调用变量
使用内敛表达式可以免去在标签属性内写th:text:#{xxxxx}
@RequestMapping("/demo")
public String index(Model model){
String msg = "LEVI";
model.addAttribute("msg",msg);
return "demo";
}
<span>Hello [[${msg}]]</span>
<span>[[|Hello ${msg}|]]</span>
利用内敛表达式执行新语法
<p>[[ ${#dates.format(dateVar, 'yyyy-MM-dd')} ]]</p>
<p>[[${#dates.format(dateVar, 'yyyy年MM月dd日')}]]</p>
<p>[[${#dates.format(dateVar, 'yyyy-MM-dd HH:mm:ss')}]]</p>
<p>[[${#dates.format(dateVar, 'yyyy年MM月dd日 HH时mm分ss秒')}]]</p>
th:text 和[[]]两种写法都是允许的,每个团队可能规则不一样。但要了解的是[[]]是用来替代`th:text 的,不是替代所有 th: 标签的
条件语句
有时我们会遇到显示性别的情况
- user.sex值为male显示:男
- user.sex值为female显示:女
Thymeleaf 也支持 if/else 能力,同样也是以 th:开头的属性,这次我们使用的是 th:if,if表达式的值是 ture 的情况下就会执行渲染
<span th:if="${user.sex == 'male'}">男</span>
还可以使用th:unless代表否定条件,这个语句与if是相反的,代表的是sex不是male的情况下才进行渲染
<span th:unless="${user.sex == 'male'}">女</span>
th:if条件判断除了判断boolean值外,Thymeleaf还认为如下表达式为true
- 值非空
- 值是非0数字
- 值是字符串,但是不是
false,offorno - 值不是boolean值,数字,character或字符串
String 逻辑判断
有时我们还会借助#Strings这个内置对象来做逻辑判断和处理
- 检查字符串是否为空(或null),在检查之前会先进行trim()
${#strings.isEmpty(name)}
- 数组同样适用
${#strings.arrayIsEmpty(name)}
- 集合类同样适用
${#strings.listIsEmpty(name)}
- 检查字符串变量是否包含片段
${#strings.contains(name,'abc')}
${#strings.containsIgnoreCase(name,'abc')}
忽略大小写字母判断是否包含字符串
${#strings.startsWith(name,'abc')}
是否以字符串开头
${#strings.endsWith(name,'abc')}
是否以字符串结束
Thymeleaf布局
推荐使用th:include + th:replace方案完成布局开发
layout.html:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>布局</title>
<style>
.header {background-color: #f5f5f5;padding: 20px;}
.header a {padding: 0 20px;}
.container {padding: 20px;margin:20px auto;}
.footer {height: 40px;background-color: #f5f5f5;border-top: 1px solid #ddd;padding: 20px;}
</style>
</head>
<body>
<header class="header">
<div>
<a href="/book/list.html">图书管理</a>
<a href="/user/list.html">用户管理</a>
</div>
</header>
<div class="container" th:include="::content">页面正文内容</div>
<footer class="footer">
<div>
<p style="float: left">© youkeda.com 2017</p>
<p style="float: right">
Powered by 优课达
</p>
</div>
</footer>
</body>
user/list.html:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" th:replace="layout">
<div th:fragment="content">
<h2>用户列表</h2>
<div>
<a href="/user/add.html">添加用户</a>
</div>
<table>
<thead>
<tr>
<th>
用户名称
</th>
<th>
用户年龄
</th>
<th>
用户邮箱
</th>
</tr>
</thead>
<tbody>
<tr th:each="user : ${users}">
<td th:text="${user.name}"></td>
<td th:text="${user.age}"></td>
<td th:text="${user.email}"></td>
</tr>
</tbody>
</table>
</div>
</html>
th:include = "::content"(名称不必一定为content,可根据实际需求修改,只要和需引入的片段th:fragment = "xxxx"中的xxxx名字相同就好)
::content指的是选择器,这个选择器指的就是将th:fragment值为content的页面加载过来;当页面渲染的时候,布局会合并content这个fragment内容一起渲染
th:replace = "layout"
指定了布局的名称,一旦声明以后,页面会被替换成layout内容,这个
layout指的是html文件templates/layout.html;
th:fragment = "content"
当页面渲染的时候,可以通过选择器指定使用这个片段,在上面layout.html文件的
th:include = "::content"指定的就是这个值
<div th:fragment="content">
</div>
th:replace/include的另一种用法
th:replace 语法,不仅可以指定布局,也可以指定页面内容片段。
通常,在页面(例如首页)内的某个节点(不是最外层)上,使用 th:replace="player::player" 引入另一个内容片段。
首页 index.html 代码示例如下:
<div class="player" th:replace="player::player"></div>
:: 之前的 player 指的是内容片段 player.html 模板文件。:: 之后的 player 指的是片段模板文件的 th:fragment 的值:
player.html 文件代码示例如下:
<div th:fragment="player">
<div class="txt"> 下载豆瓣FM APP<br> 让好音乐继续 </div>
</div>
这样,就完成了在首页中引入 player.html 文件内容。
可以引入多个部分内容,将整个页面模块化,避免首页文件中的代码过于臃肿。
Any correciton will be appreciate!!!