课程表

Spring Boot课程

工具箱
速查手册

Boot Thymeleaf高级教程

当前位置:免费教程 » Java相关 » Spring Boot
注意:本页面内容为W3xue原创,未经授权禁止转载,违者必究!
来源:W3xue  发布时间:2019/9/29 12:31:24

上一节中我们已经简单介绍了Spring Boot 中 Thymeleaf的基本配置和使用,本节让我们来看看其更加详尽的用法。本节课程主要分为以下几部分:

一、标准表达式语法

二、表达式支持的语法

三、常用th标签

四、Thymeleaf 的布局


一、标准表达式语法

标准表达式分一共为四类:

1.变量表达式($)

2.选择或星号表达式(*)

3.文字国际化表达式(#)

4.URL 表达式(@)

下面我们分别来介绍。

1.变量表达式

变量表达式即 OGNL 表达式或 Spring EL 表达式(在 Spring 术语中也叫 model attributes)。如下所示:

${session.user.name}

其中点号,表明它是对象的成员,是上下级关系。我们来看一个例子。在“lucky.html”底部,增加2个标签:

<p th:text="${student1.name}"></p>
<li th:each="stu : ${students}"><span th:text="${stu.name}"></span></li>

当然,这里的student1和students都是预设的标签名字,我们还没有在后端绑定数据给它。此外,这里出现了一个新语法“th:each”,我们后面也会讲到。我们现在就来给这2个标签绑定数据,首先需要先在MainController类的顶部,添加自动装配的代码:

@Autowired
private MainDao mainDao;

然后我们在MainController类中的getlucky路由(也是方法名)中,添加如下的绑定:

MainBean mbstudent1=mainDao.getOne(1); //取得ID为1的学生对象
mv.addObject("student1",mbstudent1); //将其绑定到student1标签上
List<MainBean> mbStudents=mainDao.getStudentByName(""); //没有关键词,将会选择全部学生
mv.addObject("students",mbStudents);//将其绑定到students标签上

你会发现,访问“http://localhost:8080/jiaocheng/getlucky”时会多出如下HTML代码,这些都是取得的结果:

<p>张三</p>    
<li><span>张三</span></li>    
<li><span>李四</span></li>    
<li><span>王麻子</span></li>

2.选择或星号表达式(*)

星号表达式,提供了一种容器,这个容器里的标签,不必在重复写对象的名字,只需要写对象的成员变量名即可。我们来看一个例子,在“lucky.html”底部,增加1个标签即可,这里我们借用上一小节绑定的student1标签:

<p th:object="${student1}">
    姓名:<span th:text="*{name}"></span>,年龄:<span th:text="*{age}"></span>,年级:<span th:text="*{grade}"></span>
</p>

这里,我们使用了“th:object”标签,表明th标签里将包含对象。

其实以上代码完全等价于:

<p th:object="${student1}">
    姓名:<span th:text="${student1.name}"></span>,年龄:<span th:text="${student1.age}"></span>,年级:<span th:text="${student1.grade}"></span>
</p>

可以发现,星号表达式是省去了选择上文的代码,它可以与美元符号混合使用。

重新运行程序后,访问“http://localhost:8080/jiaocheng/getlucky”时会多出如下HTML代码:

<p>
姓名:<span>张三</span>,年龄:<span>8</span>,年级:<span>二年级</span>    
</p>

3.文字国际化表达式(#)

文字国际化表达式作用是,从另外一个.properties的外部文件获取某个标签的值。这个.properties文件,和application.yml(或默认的application.properties)一样,存放在“src/main/resources”文件夹。文字国际化表达式和引用application.yml的键值类似,这里不再详细介绍。

4.URL 表达式(@)

URL 表达式指的是把一个有用的上下文或会话信息添加到 URL,这个过程经常被叫做 URL 重写。比如:

@{/getlucky}

还可以设置参数:

@{/getlucky(id=${studentId})}

或者这样表示相对路径:

@{../hello}

在“lucky.html”页面底部,添加下面的代码:

<a href="https://www.w3xue.com/" th:href="@{/hello}">链接到哪里</a>

这个链接会最终指向“http://localhost:8080/jiaocheng/hello”。


二、表达式支持的语法

我们仍然有许多疑问,比如,如果想在th标签里直接表示文本,或者使用if判断,还有上文提到的循环each语法怎么办?我们一起来看看Thymeleaf标签支持的语法:

1、字面(Literals)

2、文本操作(Text operations)

3、算术运算(Arithmetic operations)

4、布尔操作(Boolean operations)

5、比较和等价(Comparisons and equality)

6、条件运算符(Conditional operators)

7、循环运算符

下面分别来一一介绍。

1、字面(Literals)

字面语法是最常用的语法,它包含以下具体类型:

·文本文字(Text literals): 'one text', 'Another one!',…

·数字文本(Number literals): 0, 34, 3.0, 12.3,…

·布尔文本(Boolean literals):true, false

·空(Null literal):null

·文字标记(Literal tokens):one, sometext, main,…

比如,我想要在th标签中,显示原始的文本,就需要用到单引号,然后让其与标签混合。我们可以在“lucky.html”页面底部,添加下面的代码:

<p th:text="'学生的姓名是:'+${student1.name}"></p>

我们会发现,最终结果是:

<p>学生的姓名是:张三</p>

数字文本常常会和运算符(如加减乘除)一起使用,布尔文本、null常常与if一起使用,后面会提到运算符和if语法。

2、文本操作(Text operations)

文本操作在上文中我们已经用过,即拼接原始文本和标签的+号。文本操作包括:

·字符串连接(String concatenation):+

·文本替换(Literal substitutions):|姓名是: ${name}|

单竖线允许原始文本和标签混用而不用使用单引号和+号连接,例如,上一个例子中使用单引号与下面的例子完全等价:

<p th:text="|学生的姓名是:${student1.name}|"></p>

3、算术运算(Arithmetic operations)

算术运算主要用于数字的操作,包括下面2中类型:

·二元运算符(Binary operators):+, -, *, /, %

·减号(单目运算符)Minus sign (unary operator):-

例如,二元运算符可以将下面代码中该学生的年龄加10岁:

<p th:text="${student1.age}+10"></p>

将显示为:

<p>18</p>

但是,二元运算符中的+号却不能与文本操作符(+|)还有单引号配合使用,否则,将不进行计算,将被视为字符串连接的+号。

<p th:text="'学生10年后的年龄是:'+${student1.age}+10"></p>

将显示为:

<p>学生10年后的年龄是:810</p>

但是除法却可以混用,例如:

<p th:text="'学生年龄的一半是:'+${student1.age}/2"></p>

将显示为:

<p>学生年龄的一半是:4</p>

4、布尔操作(Boolean operations)

布尔操作语法主要包括2类:

·二元运算符(Binary operators):and, or

·布尔否定(一元运算符)Boolean negation (unary operator):!, not

在“lucky.html”页面底部,添加下面的代码:

<p th:text="${student1.age}==8 and ${student1.name}=='李四'"></p>
<p th:text="${student1.age}==8 or ${student1.name}=='李四'"></p>
<p th:text="!${student1.age}==8">张三的年龄为8岁,但是加上否定前缀后,这里将显示false</p>
<p th:text="not ${student1.age}==8">张三的年龄为8岁,但是加上否定前缀后,这里将显示false</p>

将显示如下的结果:

<p>false</p>
<p>true</p>
<p>false</p>
<p>false</p>

5、比较和等价(Comparisons and equality)

比较运算符在上一个例子中已经使用了一个(==,即一般程序语言中的是否相等判断),主要分为以下2类:

·比较(Comparators):>, <, >=, <= (gt, lt, ge, le)

·等值运算符(Equality operators):==, != (eq, ne)

这跟大多数程序语言的用法相同,这里不再赘述。

6、条件运算符(Conditional operators)

条件运算符对应一般程序语言中的if-else分支结构,可以分为以下几类:

·If/unless-then结构:(if/unless) ? (then)

·If-then-else结构(三目运算符):(if) ? (then) : (else)

·Default结构: (表达式或值 ?: defaultvalue)

·switch结构:switch-case

我们来看第一种结构,在“lucky.html”页面底部,添加下面的代码:

<p th:if="${student1.age==8}" >学生的年龄是8</p>
<p th:unless="${student1.age==8}" >学生的年龄不是8</p>

第一行是说,如果这个学生的年龄是8,就显示该段落。第二行正好相反,如果年龄是8则不显示该段落。

结果自然是这样,第二段没有显示出来:

<p >学生的年龄是8</p>

第二种结构实际上是一种三目运算符,而第三种Default结构,它是一种判断表达式或值是否有效的结构,如果表达式或值有效,就使用该值,否则,就使用“defaultvalue”:

<p th:text="( ${student1.age}==8 ? '是的' : '不是' )"></p>
<p th:text="( ${student1.age}==9 ? '是的' : ( ${student1.age} ?: '未知' ) )"></p>

上面的代码运行结果为:

<p>是的</p>
<p>8</p>

这个例子表明,所有这些Thymeleaf支持的语法可以任意的进行组合。

第四种,switch结构,使用方法如下:

<div th:switch="${student1.age}">
    <p th:case="7">年龄为7岁</p>
    <p th:case="8">年龄为8岁</p>
    <p th:case="*">年龄默认值</p>
</div>

其结果如下:

<div>
<p>年龄为8岁</p>
</div>

7、循环运算符

最后我们来看看循环运算符,之前我们就看到了一个例子:

<li th:each="stu : ${students}"><span th:text="${stu.name}"></span></li>

这个例子中,会把students标签包含的所有成员,都拿出来循环打印一遍。而students标签包含的单个成员,在这里被代指为“stu”。

实际上,我们还可以带入一个状态变量,例如,我们把上一个例子改成如下的形态:

<li th:each="stu,i: ${students}"><span th:text="${i.count}+','"></span><span th:text="${stu.name}"></span></li>

这里,在“stu”后面多了一个i,这里被用来作为计数器了,后面用到了${i.count}。上面的代码结果如下:

<li><span>1,</span><span>张三</span></li>
<li><span>2,</span><span>李四</span></li>
<li><span>3,</span><span>王麻子</span></li>

i这个状态变量,除了“count”计数器属性,还有其他的属性:

·index:当前迭代对象的 index(从0开始计算)

·count: 当前迭代对象的 index(从1开始计算)

·size:被迭代对象的大小

·current:当前迭代变量

·even/odd:布尔值,当前循环是否是偶数/奇数(从0开始计算)

·first:布尔值,当前循环是否是第一个

·last:布尔值,当前循环是否是最后一个


三、常用th标签

我们在上面已经使用了th:text,th:utext,th:href,th:inline,th:object,th:each,th:if,th:unless等标签,其实,Thymeleaf还有其他更多的标签,如下表所示:

关键字功能介绍案例
th:id替换id<input th:id="'xxx' + ${collect.id}"/>
th:text文本替换<p th:text="${collect.description}">description</p>
th:utext支持html的文本替换<p th:utext="${htmlcontent}">conten</p>
th:object替换对象<div th:object="${session.user}">
th:value属性赋值<input th:value="${user.name}" />
th:with变量赋值运算<div th:with="isEven=${prodStat.count}%2==0"></div>
th:style设置样式th:style="'display:' + @{(${sitrue} ? 'none' : 'inline-block')} + ''"
th:onclick点击事件th:onclick="'getCollect()'"
th:each属性赋值tr th:each="user,userStat:${users}">
th:if判断条件<a th:if="${userId == collect.userId}" >
th:unless和th:if判断相反<a th:href="@{/login}" th:unless=${session.user != null}>Login</a>
th:href链接地址<a th:href="@{/login}" th:unless=${session.user != null}>Login</a> />
th:switch多路选择 配合th:case 使用<div th:switch="${user.role}">
th:caseth:switch的一个分支<p th:case="'admin'">User is an administrator</p>
th:fragment布局标签,定义一个代码片段,方便其它地方引用<div th:fragment="alert">
th:include布局标签,替换内容到引入的文件<head th:include="layout :: htmlhead" th:with="title='xx'"></head> />
th:replace布局标签,替换整个标签到引入的文件<div th:replace="fragments/header :: title"></div>
th:insert布局标签,插入整个标签到引入的文件<div th:insert="fragments/header :: title"></div>
th:selectedselected选择框 选中th:selected="(${xxx.id} == ${configObj.dd})"
th:src图片类地址引入<img class="img-responsive" alt="App Logo" th:src="@{/img/logo.png}" />
th:inline定义js脚本可以使用变量<script type="text/javascript" th:inline="javascript">
th:action表单提交的地址<form action="subscribe.html" th:action="@{/subscribe}">
th:remove删除某个属性<tr th:remove="all"> 1.all:删除包含标签和所有的孩子。2.body:不包含标记删除,但删除其所有的孩子。3.tag:包含标记的删除,但不删除它的孩子。4.all-but-first:删除所有包含标签的孩子,除了第一个。5.none:什么也不做。这个值是有用的动态评估。
th:attr设置标签属性,多个属性可以用逗号分隔比如th:attr="src=@{/image/aa.jpg},title=#{logo}",此标签不太优雅,一般用的比较少。
th:attrappend追加标签属性比如<p class="btn" th:attrappend="class=${' ' + classname}" />,会在class上追加一个后台绑定的classname值
th:block跟HTML标签平行的标签,而不作为HTML标签的属性使用,在结果中会消失比如<th:block><p>这里是文本</p></th:block>,只会显示为<p>这里是文本</p>,一般用在布局上

还有非常多的标签,这里只列出最常用的几个,由于一个标签内可以包含多个th:x属性,其生效的优先级顺序为:

include,each,if/unless/switch/case,with,attr/attrprepend/attrappend,value/href,src ,etc,text/utext,fragment,remove。

例如,我们可以2个标签,让学生的名字链接到其详细信息页:

<a th:text="${student1.name}+'的信息'" th:href="@{/stuinfo(id=${student1.id})}"></a>

结果为:

<a href="/jiaocheng/stuinfo?id=1">张三的信息</a>

四、Thymeleaf 的布局

在引入布局之前,让我们先了解三个标签:th:insert、th:replace、th:include。我们先来看一个例子,在“lucky.html”页面底部,添加下面的代码:

<p id="someEle">我是一个模板文本框</p>
<p th:insert="this::#someEle"></p>
<p th:replace="this::#someEle"></p>
<p th:include="this::#someEle"></p>

你会得到如下结果:

<p id="someEle">我是一个模板文本框</p>    
<p><p id="someEle">我是一个模板文本框</p></p>    
<p id="someEle">我是一个模板文本框</p>    
<p>我是一个模板文本框</p>

第一个是原始的结果,底下三行可以看出三个标签的区别,th:insert标签是在原有控件内部插入被引用的控件th:replace是拿被引用的控件替换原有控件th:include是拿被引用的控件内容插入到原有控件中。Thymeleaf 3.0 推荐使用 th:insert 替换 2.0 的 th:replace。我们尽量使用 th:insert好了,因为th:replace有可能会被以后的Thymeleaf版本抛弃。

我们可以在其他的页面上引用这个控件,语法是“文件名::#控件ID”。首先,我们在“templates”文件夹,新建一个“greeting.html”文件,然后放入标签引用:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<p th:insert="lucky::#someEle"></p>
<p th:replace="lucky::#someEle"></p>
<p th:include="lucky::#someEle"></p>
</body>
</html>

然后,在MainController类,申明如下路由和方法:

@RequestMapping(value = "/greeting")
public ModelAndView greeting(ModelAndView mv) {
    mv.setViewName("/greeting");
    return mv;
}

最后访问“http://localhost:8080/jiaocheng/greeting”,得到如下结果(body中的主要内容):

<p><p id="someEle">我是一个模板文本框</p></p>    
<p id="someEle">我是一个模板文本框</p>    
<p>我是一个模板文本框</p>

现在我们来看看布局,我们在“lucky.html”页面底部,添加下面的代码:

<th:block th:fragment="commonFooter">
<footer>这里是页脚<br />然后是一堆备案、版权的文字</footer>
</th:block>

注意,这里使用的是“th:block”标签,这个标签比较特殊,不同于其他标签作为HTML控件的属性,它和HTML控件是平行的,但是最终它会被删掉,但是它注册的布局片段却可以起作用。

然后在greeting.html”底部加上这样的代码:

<th:block th:insert="lucky::commonFooter"></th:block>

这里跟刚才的插入控件的语法有一点不同,那就是没有“#”号,直接使用布局片段的名字就可以了。

以后,不管是什么模板文件,只要引用“<th:block th:insert="lucky::commonFooter"></th:block>”这样的代码,就会自动插入lucky.html中定义的这个代码段,一旦修改lucky.html中的这个代码段,其他页面显示的内容会自动更改,非常适合页眉、页脚这种高重复使用的代码段。

当然,建议把这种高度重复的代码,放到专门的模板文件里,方便其他页面集中引用。同时,引用还可以附带上参数。比如,我们在“templates”文件夹,再建一个“iframe.html”:

<!DOCTYPE html>
<html lang="en">
<th:block th:fragment="customHeader(title,keywords)">
    <head>
        <meta charset="UTF-8">
        <title th:text="${title}"></title>
        <meta name="keywords" th:content="${keywords}" />
    </head>
</th:block>
<body>
<th:block th:fragment="commonFooter">
    <footer>这里是页脚<br />然后是一堆备案、版权的文字</footer>
</th:block>
</body>
</html>

这个“iframe.html”无需定义任何路由,因为它只是用来存放一些模板控件。

然后,我们把greeting.html”改成引用这个文件的head和footer:

<!DOCTYPE html>
<html lang="en">
<th:block th:insert="iframe::customHeader('你好','关键词1,关键词2')"></th:block>
<body>
<p th:insert="lucky::#someEle"></p>
<p th:replace="lucky::#someEle"></p>
<p th:include="lucky::#someEle"></p>
<th:block th:insert="iframe::commonFooter"></th:block>
</body>
</html>

访问“http://localhost:8080/jiaocheng/greeting”,得到的结果如下:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>你好</title>
<meta name="keywords" content="关键词1,关键词2" />
</head>
<body>
<p><p id="someEle">我是一个模板文本框</p></p>
<p id="someEle">我是一个模板文本框</p>
<p>我是一个模板文本框</p>
<footer>这里是页脚<br />然后是一堆备案、版权的文字</footer>
</body>
</html>

可以看到,页面标题和关键词已经被替换成了带入的文本。

你也可以在后端定义变量。例如,把greeting.html”的head引用改为如下代码:

<th:block th:insert="iframe::customHeader(${title},${keyword1}+','+${keyword2})"></th:block>

然后在“greeting”路由中,加入如下绑定:

mv.addObject("title","标题:你好");
mv.addObject("keyword1","天空");
mv.addObject("keyword2","大海");

重新运行项目后,访问“http://localhost:8080/jiaocheng/greeting”,head部分会变成后端定义的值:

<head>
<meta charset="UTF-8">
<title>标题:你好</title>
<meta name="keywords" content="天空,大海" />
</head>
注意:本页面内容为W3xue原创,未经授权禁止转载,违者必究!
来源:W3xue  发布时间:2019/9/29 12:31:24