thymeleaf模板引擎使用
一、在pom文件中引入必要依赖
主要增加 spring-boot-starter-thymeleaf
和 nekohtml
这两个依赖
spring-boot-starter-thymeleaf
:Thymeleaf 自动配置nekohtml
:允许使用非严格的 HTML 语法
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--允许使用非严格的 HTML 语法-->
<dependency>
<groupId>net.sourceforge.nekohtml</groupId>
<artifactId>nekohtml</artifactId>
<version>1.9.22</version>
</dependency>
其中,spring-boot-starter-thymeleaf
依赖利用springboot创建向导创建时选择了thymeleaf后会自动生成,而nekohtml
依赖则必须手动添加。
二、在application.yml中配置Thymeleaf
spring:
thymeleaf:
cache: false # 开发时关闭缓存,不然没法看到实时页面
mode: HTML # 用非严格的 HTML
encoding: UTF-8
servlet:
content-type: text/html
三、创建html页面
需要在头部引入命名空间
<!DOCTYPE html SYSTEM "http://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-spring4-4.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
完整的html页面:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Hello Thymeleaf</title>
</head>
<body>
<div>
<!--直接获取对象的属性-->
<span>访问 Model:</span><span th:text="${person.name}"></span>
</div>
<div>
<span>访问列表</span>
<table>
<thead>
<tr>
<th>姓名</th>
<th>年龄</th>
</tr>
</thead>
<tbody>
<tr th:each="human : ${people}"><!--遍历集合people,每一个对象用human表示-->
<td th:text="${human.name}"></td><!--获取human的name属性-->
<td th:text="${human.age}"></td><!--获取human的age属性-->
</tr>
</tbody>
</table>
</div>
</body>
</html>
四、编写JavaBean
package com.zzr.hello.entity;
import java.io.Serializable;
public class PersonBean implements Serializable {
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
五、编写Controller层测试
package com.zzr.hello.controller;
import com.zzr.hello.entity.PersonBean;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import java.util.ArrayList;
import java.util.List;
@Controller
@RequestMapping(value = "thymeleaf")//访问路径都需要加上
public class IndexController {
@RequestMapping(value = "index", method = RequestMethod.GET)
//get请求,路径为 /thymeleaf/index
public String index(Model model) {//将对象返回到前端页面
PersonBean person = new PersonBean();
person.setName("张三");
person.setAge(22);
List<PersonBean> people = new ArrayList<>();
PersonBean p1 = new PersonBean();
p1.setName("李四");
p1.setAge(23);
people.add(p1);
PersonBean p2 = new PersonBean();
p2.setName("王五");
p2.setAge(24);
people.add(p2);
PersonBean p3 = new PersonBean();
p3.setName("赵六");
p3.setAge(25);
people.add(p3);
model.addAttribute("person", person);//对象
model.addAttribute("people", people);//集合
return "index";
}
}
Thymeleaf 常用语法
修改 html 标签用于引入 thymeleaf 引擎,这样才可以在其他标签里使用 th:*
语法,这是下面语法的前提。
<!DOCTYPE html SYSTEM "http://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-spring4-4.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
获取变量值
<p th:text="'Hello!, ' + ${name} + '!'" >name</p>
可以看出获取变量值用 $
符号,对于javaBean的话使用 变量名.属性名
方式获取,这点和 EL
表达式一样.
另外 $
表达式只能写在th标签内部,不然不会生效,上面例子就是使用 th:text
标签的值替换 p
标签里面的值,至于 p
里面的原有的值只是为了给前端开发时做展示用的.这样的话很好的做到了前后端分离.
引入 URL
Thymeleaf 对于 URL 的处理是通过语法 @{…}
来处理的
<a th:href="@{http://www.baidu.com}">绝对路径</a>
<a th:href="@{/}">相对路径</a>
<a th:href="@{css/bootstrap.min.css}">Content路径,默认访问static下的css文件夹</a>
类似的标签有:th:href
和 th:src
字符串替换
很多时候可能我们只需要对一大段文字中的某一处地方进行替换,可以通过字符串拼接操作完成:
<span th:text="'Welcome to our application, ' + ${user.name} + '!'">
一种更简洁的方式是:
<span th:text="|Welcome to our application, ${user.name}!|">
当然这种形式限制比较多,|…|中只能包含变量表达式${…},不能包含其他常量、条件表达式等。
运算符
在表达式中可以使用各类算术运算符,例如+, -, *, /, %
th:with="isEven=(${prodStat.count} % 2 == 0)"
逻辑运算符>, <, <=,>=,==,!=都可以使用,唯一需要注意的是使用<,>时需要用它的HTML转义符:
th:if="${prodStat.count} > 1"
th:text="'Execution mode is ' + ( (${execMode} == 'dev')? 'Development' : 'Production')"
条件
if/unless
Thymeleaf 中使用 th:if
和 th:unless
属性进行条件判断,下面的例子中,标签只有在 th:if
中条件成立时才显示:
<a th:href="@{/login}" th:unless=${session.user != null}>Login</a>
th:unless
于 th:if
恰好相反,只有表达式中的条件不成立,才会显示其内容。
switch
Thymeleaf 同样支持多路选择 Switch 结构:
<div th:switch="${user.role}">
<p th:case="'admin'">User is an administrator</p>
<p th:case="#{roles.manager}">User is a manager</p>
</div>
默认属性 default 可以用 * 表示:
<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>
循环
渲染列表数据是一种非常常见的场景,例如现在有 n 条记录需要渲染成一个表格,该数据集合必须是可以遍历的,使用 th:each
标签:
<body>
<h1>Product list</h1>
<table>
<tr>
<th>NAME</th>
<th>PRICE</th>
<th>IN STOCK</th>
</tr>
<tr th:each="prod : ${prods}">
<td th:text="${prod.name}">Onions</td>
<td th:text="${prod.price}">2.41</td>
<td th:text="${prod.inStock}? #{true} : #{false}">yes</td>
</tr>
</table>
<p>
<a href="../home.html" th:href="@{/}">Return to home</a>
</p>
</body>
可以看到,需要在被循环渲染的元素(这里是)中加入 th:each
标签,其中 th:each="prod : ${prods}"
意味着对集合变量 prods
进行遍历,循环变量是 prod
在循环体中可以通过表达式访问。
Thymeleaf 参考手册
使用文本
语法 | 说明 |
---|---|
{home.welcome} | 使用国际化文本,国际化传参直接追加 (value…) |
${user.name} | 使用会话属性 |
@{} 表达式中使用超链接 | `` |
- | - |
${} | 表达式中基本对象 |
param | 获取请求参数,比如 ${param.name} , http://localhost:8080?name=jeff |
session | 获取 session 的属性 |
application | 获取 application 的属性 |
execInfo | 有两个属性 templateName 和 now (是 java 的 Calendar 对象) |
ctx | |
vars | |
locale | |
httpServletRequest | |
httpSession | |
- | - |
th 扩展标签 | |
th:text | 普通字符串 |
th:utext | 转义文本 |
th:href | 链接 |
th:attr 设置元素属性 | `` |
th:with | 定义常量 |
th:attrappend | 追加属性 |
th:classappend | 追加类样式 |
th:styleappend | 追加样式 |
其他标签
语法 | 说明 |
---|---|
th:abbr | |
th:accept | |
th:accept-charset | |
th:accesskey | |
th:action | |
th:align | |
th:alt | |
th:archive | |
th:audio | |
th:autocomplete | |
th:axis | |
th:background | |
th:bgcolor | |
th:border | |
th:cellpadding | |
th:cellspacing | |
th:challenge | |
th:charset | |
th:cite | |
th:class | |
th:classid | |
th:codebase | |
th:codetype | |
th:cols | |
th:colspan | |
th:compact | |
th:content | |
th:contenteditable | |
th:contextmenu | |
th:data | |
th:datetime | |
th:dir | |
th:draggable | |
th:dropzone | |
th:enctype | |
th:for | |
th:form | |
th:formaction | |
th:formenctype | |
th:formmethod | |
th:formtarget | |
th:frame | |
th:frameborder | |
th:headers | |
th:height | |
th:high | |
th:href | |
th:hreflang | |
th:hspace | |
th:http-equiv | |
th:icon | |
th:id | |
th:keytype | |
th:kind | |
th:label | |
th:lang | |
th:list | |
th:longdesc | |
th:low | |
th:manifest | |
th:marginheight | |
th:marginwidth | |
th:max | |
th:maxlength | |
th:media | |
th:method | |
th:min | |
th:name | |
th:optimum | |
th:pattern | |
th:placeholder | |
th:poster | |
th:preload | |
th:radiogroup | |
th:rel | |
th:rev | |
th:rows | |
th:rowspan | |
th:rules | |
th:sandbox | |
th:scheme | |
th:scope | |
th:scrolling | |
th:size | |
th:sizes | |
th:span | |
th:spellcheck | |
th:src | |
th:srclang | |
th:standby | |
th:start | |
th:step | |
th:style | |
th:summary | |
th:tabindex | |
th:target | |
th:title | |
th:type | |
th:usemap | |
th:value | |
th:valuetype | |
th:vspace | |
th:width | |
th:wrap | |
th:xmlbase | |
th:xmllang | |
th:xmlspace | |
th:alt-title | |
th:lang-xmllang |
循环
<tr th:each="prod : ${prods}">
<td th:text="${prod.name}">Onions</td>
<td th:text="${prod.price}">2.41</td>
<td th:text="${prod.inStock}? #{true} : #{false}">yes</td>
</tr>
<table>
<tr>
<th>NAME</th>
<th>PRICE</th>
<th>IN STOCK</th>
</tr>
<tr th:each="prod,iterStat : ${prods}" th:class="${iterStat.odd}? 'odd'">
<td th:text="${prod.name}">Onions</td>
<td th:text="${prod.price}">2.41</td>
<td th:text="${prod.inStock}? #{true} : #{false}">yes</td>
</tr>
</table>
迭代器的状态:
- index: 当前的索引,从0开始
- count: 当前的索引,从1开始
- size:总数
- current:
- even/odd:
- first
- last
判断
if
<a href="comments.html" th:href="@{/product/comments(prodId=${prod.id})}" th:if="${not #lists.isEmpty(prod.comments)}">view</a>
unless
<a href="comments.html" th:href="@{/comments(prodId=${prod.id})}" th:unless="${#lists.isEmpty(prod.comments)}">view</a>
switch
<div th:switch="${user.role}">
<p th:case="'admin'">User is an administrator</p> <p th:case="#{roles.manager}">User is a manager</p>
</div>
<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>
th:block
<table>
<th:block th:each="user : ${users}">
<tr>
<td th:text="${user.login}">...</td> <td th:text="${user.name}">...</td>
</tr>
<tr>
<td colspan="2" th:text="${user.address}">...</td>
</tr>
</th:block>
</table>
推荐下面写法(编译前看不见)
<table>
<tr>
<td th:text="${user.login}">...</td>
<td th:text="${user.name}">...</td> </tr>
<tr>
<td colspan="2" th:text="${user.address}">...</td>
</tr>
<!--/*/ </th:block> /*/-->
</table>
th:inline(行内写法)
th:inline
用法
th:inline 可以等于 text , javascript(dart) , none
text: [[…]]
<p th:inline="text">Hello, [[#{test}]]</p>
javascript: /[[…]]/
<script th:inline="javascript">
var username = /*[[
#{test}
]]*/;
var name = /*[[
${param.name[0]}+${execInfo.templateName}+'-'+${#dates.createNow()}+'-'+${#locale}
]]*/;
</script>
<script th:inline="javascript">
/*<![CDATA[*/
var username = [[#{test}]];
var name = [[${param.name[0]}+${execInfo.templateName}+'-'+${#dates.createNow()}+'-'+${#locale}]];
/*]]>*/
</script>
adding code: /* [+…+]*/
var x = 23;
/*[+
var msg = 'Hello, ' + [[${session.user.name}]]; +]*/
var f = function() {
...
removind code: /[- / and /* -]*/
var x = 23;
/*[- */
var msg = 'This is a non-working template'; /* -]*/
var f = function() {
...
Thymeleaf 表达式语法
Message 表达式
{...}
<p th:utext="#{home.welcome(${session.user.name})}"> Welcome to our grocery store, Sebastian Pepper!</p>
<p th:utext="#{${welcomeMsgKey}(${session.user.name})}"> Welcome to our grocery store, Sebastian Pepper!</p>
变量表达式
${}
ongl 标准语法,方法也可以被调用
选择变量表达式
*{}
<div th:object="${session.user}">
<p>Name: <span th:text="*{firstName}">Sebastian</span>.</p>
<p>Surname: <span th:text="*{lastName}">Pepper</span>.</p>
<p>Nationality: <span th:text={age}">Saturn</span>.</p>
</div>
等价于
<div>
<p>Name: <span th:text="${session.user.firstName}">Sebastian</span>.</p>
<p>Surname: <span th:text="${session.user.lastName}">Pepper</span>.</p>
<p>Nationality: <span th:text="${session.user.age}">Saturn</span>.</p>
</div>
当然了,这两者可以混合使用
还有一种方式
<div>
<p>Name: <span th:text="*{session.user.name}">Sebastian</span>.</p>
<p>Surname: <span th:text="*{session.user.surname}">Pepper</span>.</p>
<p>Nationality: <span th:text="*{session.user.age}">Saturn</span>.</p>
</div>
链接 URL 表达式
@{}
<!-- Will produce 'http://localhost:8080/gtvg/order/details?orderId=3' (plus rewriting) --> <a href="details.html"
th:href="@{http://localhost:8080/gtvg/order/details(orderId=${o.id})}">view</a> <!-- Will produce '/gtvg/order/details?orderId=3' (plus rewriting) -->
<a href="details.html" th:href="@{/order/details(orderId=${o.id})}">view</a>
<!-- Will produce '/gtvg/order/3/details' (plus rewriting) -->
<a href="details.html" th:href="@{/order/{orderId}/details(orderId=${o.id})}">view</a>
变量
分类 | 实例 |
---|---|
文本 | one text ,Another one! ,… |
数字 | 0 , 34 , 3.0 , 12.3 ,… |
真假 | true , false |
文字符号 | one , sometext , main ,… |
算数运算
分类 | 实例 |
---|---|
+, -, *, /, % | 二元运算符 |
- | 减号(一元运算符) |
真假运算
分类 | 实例 |
---|---|
and , or | 二元运算符 |
! , not | 否定(一元运算符) |
比较运算
分类 | 实例 |
---|---|
>, <, >=, <= (gt, lt, ge, le) | 比较 |
== , != ( eq , ne ) | 平等 |
条件运算
分类 | 实例 |
---|---|
if-then | (if) ? (then) |
if-then-else | (if) ? (then) : (else) |
Default | (value) ?: (defaultvalue) |
Thymeleaf 模板布局
模板模块导入
首先定义一个 /resources/templates/footer.html
文件:
<!DOCTYPE html SYSTEM "http://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-4.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<body>
<div th:fragment="copy"><!--用于公用的片段名称-->
© 2018 Copyright by zzr.
</div>
</body>
</html>
上面的代码定义了一个片段称为 copy
,我们可以很容易地使用 th:include
或者 th:replace
属性包含在我们的主页上
<body>
...
<div th:include="footer :: copy"></div>
</body>
三种引入公共片段的th属性:
th:insert:将公共片段整个插入到声明引入的元素中
th:replace:将声明引入的元素替换为公共片段
th:include:将被引入的片段的内容包含进这个标签中
include
的表达式想当简洁。这里有三种写法:
templatename::domselector
或者templatename::[domselector]
引入模板页面中的某个模块templatename
引入模板页面::domselector
或者this::domselector
引入自身模板的模块
上面所有的 templatename
和 domselector
的写法都支持表达式写法:
<div th:include="footer :: (${user.isAdmin}? #{footer.admin} : #{footer.normaluser})"></div>
不使用 th:fragment
来引用模块
<div id="copy-section">
© 2018 Copyright by zzr.
</div>
我们可以用 CSS 的选择器写法来引入
<body>
...
<div th:include="footer :: #copy-section"></div>
</body>
th:include
与 th:replace
与th:insert
的区别
<!--需要抽取公共片段(多个页面都包含的部门)-->
<footer th:fragment="copy">
© 2018 Copyright by zzr.
</footer>
<!--2、通过不同的方法引入公共片段-->
<div th:insert="footer :: copy"></div>
<div th:replace="footer :: copy"></div>
<div th:include="footer :: copy"></div>
<!--不同引入方式所呈现的效果效果-->
<!--1 插入-->
<div>
<footer>
© 2018 Copyright by zzr.
</footer>
</div>
<!--2 替换-->
<footer>
© 2018 Copyright by zzr.
</footer>
<!--3 包含-->
<div>
© 2018 Copyright by zzr.
</div>