Thymeleaf简介
前面的例子我们使用的视图技术主要是JSP。JSP的优点是它是Java EE容器的一部分,几乎所有Java EE服务器都支持JSP。缺点就是它在视图表现方面的功能很少,假如我们想迭代一个数组之类的,只能使用<% %>
来包括Java语句进行。虽然有标准标签库(JSTL)的补足,但是使用仍然不太方便。另外JSP只能在Java EE容器中使用,如果我们希望渲染电子邮件之类的,JSP就无能为力了。
Java生态圈广泛,自然有很多视图框架,除了JSP之外,还有Freemarker、Velocity、Thymeleaf等很多框架。Thymeleaf的优点是它是基于HTML的,即使视图没有渲染成功,也是一个标准的HTML页面。因此它的可读性很不错,也可以作为设计原型来使用。而且它是完全独立于Java EE容器的,意味着我们可以在任何需要渲染HTML的地方使用Thymeleaf。
Thymeleaf也提供了Spring的支持,我们可以非常方便的在Spring配置文件中声明Thymeleaf Beans,然后用它们渲染视图。
引入Thymeleaf
Thymeleaf的官网上有详细教程,如果需要的话可以直接上官网查看。我们现在使用一个Spring项目来集成Thymeleaf。首先引入Thymeleaf的依赖项。thymeleaf-spring4
会自动引入Thymeleaf核心包thymeleaf-core
,因此我们只需要在Gradle项目中声明这一个依赖即可。
compile group: 'org.thymeleaf', name: 'thymeleaf-spring4', version: '3.0.3.RELEASE'
然后需要声明Thymeleaf的几个Bean,模板引擎、模板解析器和视图解析器。如果是开发过程中,可以关闭Thymeleaf页面缓存,这样可以让对页面的改动及时反映到视图中。
<bean id="templateResolver"
class="org.thymeleaf.spring4.templateresolver.SpringResourceTemplateResolver">
<property name="prefix" value="/WEB-INF/templates/"/>
<property name="suffix" value=".html"/>
<property name="templateMode" value="HTML5"/>
<!-- 缓存--><property name="cacheable" value="false"/>
</bean>
<bean id="templateEngine"
class="org.thymeleaf.spring4.SpringTemplateEngine">
<property name="templateResolver" ref="templateResolver"/>
</bean>
<bean class="org.thymeleaf.spring4.view.ThymeleafViewResolver">
<property name="templateEngine" ref="templateEngine"/>
<property name="order" value="1"/>
<property name="characterEncoding" value="UTF-8"/>
</bean>
然后我们新建一个Thymeleaf页面,它和普通的HTML页面非常相似,只不过在html元素上需要添加xmlns:th="http://www.thymeleaf.org"
属性。如果需要向页面传值的话,可以在控制器方法参数中添加Model对象,然后向其添加需要传递的对象。之后可以使用${var_name}
在Thymeleaf中访问了。
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>主页</title>
</head>
<body>
<h1>你好</h1>
<h2 th:text="呵呵"></h2>
</body>
</html>
然后我们用一个视图控制器将/
和主页绑定。
<mvc:view-controller path="/" view-name="index"/>
然后运行程序,访问主页,即可看到Thymeleaf的结果。
基本使用
Thymeleaf使用的是OGNL语言,如果和Spring集成的话,会改为使用Spring EL。不过这两者之间大部分是相同的。因此这里讨论的大部分使用OGNL语言方法对Spring EL也适用。
文本、字面值和国际化文本
文本
文本需要th:text
属性来设置。我们可以在文本元素中添加默认值,这样当Thymeleaf引擎处理失败的时候页面会显示默认值。${...}
是变量表达式,将括号中的变量替换为其值。
<p th:text="${hello}">你好<p>
字面值
在th:text
中我们可以使用各种字面值,下面列举如下。
字符串字面值。如果需要连接多个字符串使用+
即可。
<span th:text="'字符串字面值需要用单引号包括'">默认值</span>
数字字面量。我们可以使用运算符计算数字的值。
<span th:text="2012 + 5">2017</span>
布尔值字面量。
<p th:text="true==true"></p>
国际化文本
默认情况下,我们可以在与视图文件相同的目录下编写properties文件,然后使用消息语法#{home.welcome}
来引用文件中的字符串。可以有多个属性文件,对应不同的区域,例如home_en.properties
代表英文区域,home_es.properties
代表西班牙区域。下面是一个示例属性文件。
home.welcome=你好
默认情况下Thymeleaf会在与视图相同的文件下寻找同名的属性文件来加载消息。我们也可以自定义消息解析器,用自己的策略从任何地方加载消息。
内插字符串
很多语言都支持内插字符串,可以方便的格式化字符串。不过Java不支持,这就比较蛋疼了。内插字符串可以将一个字符串中给定部分替换为实际字符串。内插字符串需要使用|
包括,在|
中只能包括${}
表达式,不能包括其他表达式。
<span th:text="|你好, ${name}!|">
上面的内插字符串等效于下面的一段。
<span th:text="'你好, ' + '${name}' + '!'">
算术、比较和逻辑运算
在Thymeleaf中可以进行常见的各种算术运算。如果使用除/
或者取余%
运算符的话,还可以使用div
或mod
代替。
<p th:text="(17%5)-2"></p>
也可以进行比较和逻辑运算。由于<
这样的符号用在了HTML标签中。所以在Thymeleaf中需要使用gt
等代替。比较和逻辑运算符有gt (>), lt (<), ge (>=), le (<=), not (!), eq (==), neq/ne (!=) 。
<p th:text="1 gt 2"></p>
条件表达式
这个和Java中的三元条件表达式?:
一样。
<p th:text="true?'真':'假'"></p>
默认表达式
这也是一个非常方便的特性,在C#和一些语言中提供。默认表达式是val1 ?: val2
,给定两个变量,当前面一个不是空的时候,前面的值会被使用,否则后面的值作为默认值被使用。
<p th:text="null?:'我不是空值'"></p>
它可以用三元条件表达式替换。
<p th:text="${val}!=null?${val}:'我不是空值'"></p>
无操作标记
无操作标记是下划线_
,Thymeleaf遇到该标记的时候不会进行任何操作。
<p th:text="_">不进行任何操作</p>
生成链接
Thymeleaf也可以生成URL,类似JSTL中的<c:url>
标签。这需要使用另外一种类型的表达式@{...}
,表示其中的是URL。支持的URL有绝对路径(完整的URL),Servlet环境相对路径(/showUsers
这样的)、服务器相对路径(~/myapp/showUser
这样的,允许我们调用同一个服务器下其他Servlet环境中的URL)、协议相对路径(//code.jquery.com/jquery-2.0.3.min.js
)。
下面是简单的一个例子。
<p><a th:href="@{/}">返回主页</a></p>
假如需要在链接中包含查询参数,可以在@{}
中使用括号,有多个查询参数使用逗号分隔开。下面这个例子最终会生成类似/hello?name=易天&age=23
这样的链接。
<p><a th:href="@{/hello(name='易天',age=23)}">问候</a></p>
如果需要路径参数的话,也是类似的,只不过路径参数需要括号包括起来。
<p><a th:href="@{/hello/{name}(name='易天')}">问候</a></p>
这些链接会由org.thymeleaf.linkbuilder.ILinkBuilder
接口处理,转换为实际的URL。Thymeleaf的默认实现org.thymeleaf.linkbuilder.StandardLinkBuilder
可以用于非Web环境和Servlet环境。如果使用其他框架的话Thymeleaf可能不能正确生成URL,这时候就需要查阅相关文档了。
使用Thymeleaf表达式
前面用到的主要是${...}
,用来获取变量的值。除了美元表达式之外,Thymeleaf还有其他表达式,以下会逐一介绍。
表达式基本对象
Thymeleaf包含了一些基本对象,可以用于我们的视图中。这些基本对象使用#
开头。
-
#ctx
: 上下文对象. -
#vars
: 上下文变量. -
#locale
: 区域对象. -
#request
: (仅Web环境可用)HttpServletRequest对象. -
#response
: (仅Web环境可用)HttpServletResponse对象. -
#session
: (仅Web环境可用) HttpSession对象 -
#servletContext
: (仅Web环境可用)ServletContext对象.
几个Web相关的对象会被Thymeleaf映射为Thymeleaf上下文中的对象,因此我们不需要也不能在它们前面添加#
。例如如果我们要引用Session中的值,可以像这样使用。
<p th:text="${session.user}"></p>
表达式工具对象
这些对象同样以#
开头,包含了各种工具方法,可以执行格式化、转换、生成URL等各种操作。由于这些工具对象比较多,所以我在这里就不介绍了。如果需要详细了解,可以查看表达式工具对象文档。Thymeleaf文档附录B列出这些对象的使用方法。
选择表达式(星号语法)
星号表达式和美元表达式的作用都是求变量的值。不过它们之间有一些区别,星号表达式作用于被选择的对象。被选择的对象需要使用th:object
指定。下面是Thymeleaf的一个例子。
<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="*{nationality}">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.nationality}">Saturn</span>.</p>
</div>
当存在选择对象的时候,我们可以使用美元表达式${#object}
来访问选择对象,也可以同时使用星号表达式和美元表达式。选择表达式的主要作用是在表达式比较长例如${session.users.address.street.fullname}
的时候,简化表达式。
类型转换和格式化
当使用双括号包括的变量${{...}}
或者*{{...}}
时,Thymeleaf会使用它的IStandardConversionService
来将变量转换为字符串。Thymeleaf默认的实现只是简单地调用了toString方法。如果使用了thymeleaf-spring4
包和Spring集成的话,Thymeleaf会自动使用Spring的ConversionService来进行转换。
页面元素和布局
设置标签属性
有时候我们需要设置某些页面元素的属性(例如按钮)。在JSP中我们需要编写这样的代码<input type="button" value="${msg}">
。在Thymeleaf中非常简单。
<input th:attr="type='button',value='${msg}'">
如果需要设定某个特定的属性。可以这样。
<input th:type="button" th:value="${msg}">
前面生成超链接的代码其实也是一样的,只不过属性是href、
<p><a th:href="@{/}">返回主页</a></p>
这些特定的属性有很多,详见Thymeleaf文档 设置特定属性的值。
还有特殊的属性,可以同时设置两个值。它们分别是th:alt-title
和th:lang-xmllang
,在设置图片和语言属性的时候非常有用。
有时候需要向已有的属性中添加新的值(例如在Bootstrap中卫按钮设置不同的颜色)。这可以通过th:attrappend
和th:attrprepend
向后或向前添加属性。
<input type="button" value="warn" class="btn" th:attrappend="class=${' ' + style}" />
条件选择块
在开发Web程序的时候, 很常见的需求就是根据某个值的真假,显示或隐藏某个HTML元素。这可以使用th:if="${exp}"
实现。需要注意和JSTL中的<c:if>
不同,Thymeleaf会根据条件显示或隐藏包含th:if
的整个标签块,而不仅是它的子标签块。
<div th:if="true">....</div>
需要注意th:if
的表达式不仅支持Java式的纯条件判断,还支持C语言式的真值判断,即当一个变量不为空或者整数不是0的时候,也认为是真值。另外还有一个th:unless
执行和th:if
相反的判断。
多重选择块
如果需要多重选择,可以使用th:switch
。需要注意一旦有一个子条件匹配,Thymeleaf就不会继续检查其他条件了。如果需要提供默认值的话,可以这样写,th:case="*"
。
<div th:switch="${number}">
<p th:case="1'">这是1</p>
<p th:case="2">这是2</p>
<p th:case="*">其他值</p>
</div>
迭代块
有时候我们需要遍历一个数组中的元素。这可以使用th:each
实现。在th:each
中我们要指定迭代元素和被迭代集合。被迭代集合可以是:数组,任何实现了java.util.Iterable
的对象,任何实现了java.util.Enumeration
的对象,任何实现了java.util.Iterator
的对象,任何实现了java.util.Map
的对象。需要注意的是,和JSTL的<c:forEach>
标签不同,Thymeleaf会迭代包含<th:each>
的整个标签块,而不仅仅是它的子标签块。
<tr th:each="user : ${users}">
<td th:text="${user.name}"></td>
<td th:text="${user.age}"></td>
<td th:text="${user.birthday}"></td>
</tr>
Thymeleaf还提供了迭代状态变量来检测当前迭代的状态,只需要在th:each
中声明iterStat
即可。假如没有显式声明状态变量,Thymeleaf也会创建一个状态变量,名称是迭代元素添加后缀Stat
。上面的代码中,Thymeleaf会创建一个名为userStat
的状态变量。
<tr th:each="user,iterStat : ${users}">
<td th:text="${user.name}"></td>
<td th:text="${user.age}"></td>
<td th:text="${user.birthday}"></td>
</tr>
状态变量包含以下属性;
- index属性,以0开始的迭代元素索引。
- count属性,以1开始的迭代元素索引。
- size属性,迭代集合的总元素数。
- iter属性,当前迭代元素。
- even和odd两个布尔值,表明当前index的奇偶。
- first和last两个布尔值,表明当前元素是否是第一个/最后一个。
代码段
定义代码段
首先我们来定义一个代码段。假设文件名叫做_header.html
,该文件的内容如下。在文件中我们使用th:fragment
定义了一个代码段。
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:fragment="header">
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link th:href="@{/static/css/bootstrap.css}" rel="stylesheet"/>
<link th:href="@{/static/css/bootstrap-theme.css}" rel="stylesheet"/>
<script th:href="@{/static/js/jquery.js}"></script>
<script th:href="@{/static/js/bootstrap.js}"></script>
</th:block>
</head>
<body>
</body>
</html>
使用代码段
然后在其他地方,我们就可以使用th:insert
或th:replace
来插入代码段了。这两者的区别是th:insert
会将代码段插入该元素内部,而th:replace
会将整个元素替换为要插入的代码块。在这里又出现了一种新语法~{file :: fragment}
,表示引用文件的某个代码块。
<head th:replace="~{_header :: head}">
另外~{..}
是可选的,所以上面的代码还可以写为下面的形式。
<head th:replace="_header :: head">
代码段表达式的第一个参数可以省略(~{::selector}
)或者写为this(~{this::selector}
),表示要引用的代码段就在当前文件中。表达式的第二个参数可以是代码段的名称,也可以是CSS选择器(~{file::#form}
)。如果不指定第二个参数的话,就会引入整个文件(~{templatename}
)。
参数化代码段
代码段可以有参数,折让它拥有类似函数的行为。下面是一个例子。
<head th:fragment="header(title)">
<title th:text="${title}"></title>
</head>
要引入代码块的时候,同样指定参数即可。
<head th:replace="_header::header('问候')">
</head>
如果有多个参数,用逗号分隔即可。在使用代码块的时候,既可以按照位置传入参数,也可以按照名称传入参数。
<div th:replace="::frag (${value1},${value2})">...</div>
<div th:replace="::frag (onevar=${value1},twovar=${value2})">...</div>
块代码段
前面的代码段全部在某个页面元素中。如果代码块是一些平级的元素,又该怎么做呢?Thymeleaf提供了唯一的一个Thymeleaf块级元素<th:block>
,可以帮助我们完成这一工作。首先创建一个_footer.html
文件,在其中使用<th:block>
创建一个代码块。
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
</head>
<body>
<th:block th:fragment="footer">
<br>
<p>这是页脚</p>
</th:block>
</body>
</html>
然后在其他地方一样使用即可。Thymeleaf模板引擎在处理<th:block>
的时候会删掉它本身,而保留其内容。
<div th:replace="_footer::footer"></div>
灵活的代码段配置
Thymeleaf的这些特性让它可以配置非常灵活的代码段。来看看Thymeleaf的这个例子。首先我们定义一个公用代码段。注意这里的links
,它是一个Thymeleaf块,会被实际传入的参数替代。
<head th:fragment="common_header(title,links)">
<title th:replace="${title}">默认标题</title>
<!-- 公用CSS和JS文件 -->
<!--/* 各页面单独的文件 */-->
<th:block th:replace="${links}" />
</head>
然后引用代码段。注意这里的两个代码块参数,会直接选择本页面的所有标题和link标签,然后将它们传入公用代码段中。最后生成的结果大家可以想象一下,是不是非常的灵活呢?
<head th:replace="base :: common_header(~{::title},~{::link})">
<title>Awesome - Main</title>
<link rel="stylesheet" th:href="@{/css/bootstrap.min.css}">
<link rel="stylesheet" th:href="@{/themes/smoothness/jquery-ui.css}">
</head>
空代码段和无操作符
还是上面这个例子。如果我们传入空代码段,那么实际结果中相应的地方就会为空。
<head th:replace="base :: common_header(~{::title},~{})">
如果使用无操作符,那么实际生成的代码相应部分不会做任何Thymeleaf替换,也就是说会我们会得到默认标题。
<head th:replace="base :: common_header(_,~{::link})">
删除代码段
以下是Thymeleaf的一个例子。我们可以使用th:remove
来删除指定的部分,这在原型设计和调试的时候很有用。
<tr th:remove="all">
<td>Mild Cinnamon</td>
<td>1.99</td>
<td>yes</td>
<td>
<span>3</span> comment/s
<a href="comments.html">view</a>
</td>
</tr>
th:remove
可接受的值有5个:
- all: 移除标签和所有子元素
- body: 移除所有子元素,保留标签
- tag: 移除标签,保留子元素
- all-but-first: 保留第一个子元素,移除所有其他
- none : 什么也不做。这个值在动态求值的时候会有作用
其他特性
由于Thymeleaf的用法很多,这里没办法完全写下,所以我就不写了。前面这些应该可以满足基本需求了。如果需要其他特性的话请看官方文档吧。这里简单的说明一下未详细介绍的其他特性。
本地变量
使用th:with
声明一个本地变量,可以在某段代码中重用变量。
属性处理
Thymeleaf是如何处理这些th:*
属性的呢?
注释和代码块
这里介绍了几种Thymeleaf注释。利用这些注释,我们可以让某些代码在原型设计的时候出现,某些代码在Thymeleaf引擎处理之后出现,等等。
内联
前面所有这些Thymeleaf属性都是使用属性方式写入的,能不能通过内联的方式直接在元素内部添加值?当然可以,只不过这样的话,当显示未处理的页面时,就不会显示预设的默认值,而是丑陋的表达式代码了。除此之外,还可以对CSS、JavaScript内联,让Thymeleaf引擎生成合适的代码。
模板模式
有HTML5、XML、TEXT等多种模式,可以用于生成各种类型的文档。
Thymeleaf配置
模板解析器、消息解析器、类型转换器、日志服务、缓存的配置方法。
附录
表达式基本对象、表达式工具对象和标记选择器语法的使用方法。
这些Thymeleaf的使用例子可以直接查看Thymeleaf官方文档,也可以看看我的代码。