收集整理了一份《2024年最新物联网嵌入式全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升的朋友。
需要这些体系化资料的朋友,可以加我V获取:vip1024c (备注嵌入式)
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人
都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
4.11条件表达式
条件表达式仅用于评估两个表达式中的一个,具体取决于评估条件的结果(它本身就是另一个表达式)。
让我们来看一个例子片段(引入另一个属性修改器,th:class
):
`<tr th:class="${row.even}? 'even' : 'odd'">
...
</tr>`
条件表达式(condition
,then
和else
)的所有三个部分本身都是表达式,这意味着它们可以是变量(${...}
,*{...}
),消息(#{...}
),URL(@{...}
)或文字('...'
)。
条件表达式也可以使用括号嵌套:
`<tr th:class="${row.even}? (${row.first}? 'first' : 'even') : 'odd'">
...
</tr>`
其他表达式也可以省略,在这种情况下,如果条件为false,则返回null值:
`<tr th:class="${row.even}? 'alt'">
...
</tr>`
4.12默认表达式(Elvis运算符)
一个默认的表情是一种特殊的条件值的没有那么一部分。它等同于某些语言(如Groovy)中存在的Elvis运算符,允许您指定两个表达式:如果它不计算为null,则使用第一个表达式,但如果确实如此,则使用第二个表达式。
让我们在用户个人资料页面中看到它:
`<div th:object="${session.user}">
...
<p>Age: <span th:text="\*{age}?: '(no age specified)'">27</span>.</p>
</div>`
正如您所看到的,运算符是?:
,并且我们在此处使用它来指定名称的默认值(在这种情况下为文字值),仅当评估结果*{age}
为null时。因此,这相当于:
`<p>Age: <span th:text="\*{age != null}? \*{age} : '(no age specified)'">27</span>.</p>`
与条件值一样,它们可以包含括号之间的嵌套表达式:
`<p>
Name:
<span th:text="\*{firstName}?: (\*{admin}? 'Admin' : #{default.username})">Sebastian</span>
</p>`
4.13无操作令牌
No-Operation标记由下划线符号(_
)表示。
这个标记背后的想法是指定表达式的期望结果是什么都不做,即完全就像可处理属性(例如th:text
)根本不存在一样。
除了其他可能性之外,这允许开发人员将原型文本用作默认值。例如,而不是:
`<span th:text="${user.name} ?: 'no user authenticated'">...</span>`
…我们可以直接使用*“无用户身份验证”*作为原型文本,从设计的角度来看,这会使代码更简洁,更通用:
`<span th:text="${user.name} ?: \_">no user authenticated</span>`
4.14数据转换/格式化
Thymeleaf 为variable()和selection()表达式定义了一个双括号语法,允许我们通过配置的转换服务应用数据转换。${...}``*{...}
它基本上是这样的:
`<td th:text="${{user.lastAccessDate}}">...</td>`
注意到那里的双支撑?:${{...}}
。这指示Thymeleaf将user.lastAccessDate
表达式的结果传递给*转换服务,*并要求它在写入结果之前执行格式化操作(转换为String
)。
假设它user.lastAccessDate
是类型java.util.Calendar
,如果已经注册了转换服务(实现IStandardConversionService
)并且包含有效的转换Calendar -> String
,则将应用它。
IStandardConversionService
(StandardConversionService
类)的默认实现只是.toString()
在转换为的任何对象上执行String
。有关如何注册自定义转换服务实现的更多信息,请查看“ 更多配置”部分。
官方thymeleaf-spring3和thymeleaf-spring4集成软件包的透明集成了Spring自己Thymeleaf的转换服务机制转换服务的基础设施,所以在Spring配置宣称,转换服务和格式化将进行自动获得
${{...}}
和*{{...}}
表达。
4.15预处理
除了用于表达式处理的所有这些功能外,Thymeleaf还具有预处理表达式的功能。
预处理是在正常表达式之前完成的表达式的执行,它允许修改最终将被执行的表达式。
预处理表达式与普通表达式完全相同,但显示为双下划线符号(如__${expression}__
)。
让我们假设我们有一个Messages_fr.properties
包含OGNL表达式的i18n 条目,该表达式调用特定于语言的静态方法,如:
`article.text=@myapp.translator.Translator@translateToFrench({0})`
…和a Messages_es.properties equivalent
:
`article.text=@myapp.translator.Translator@translateToSpanish({0})`
我们可以创建一个标记片段,根据语言环境评估一个表达式或另一个表达式。为此,我们将首先选择表达式(通过预处理),然后让Thymeleaf执行它:
`<p th:text="${\_\_#{article.text('textVar')}\_\_}">Some text here...</p>`
请注意,法语区域设置的预处理步骤将创建以下等效项:
`<p th:text="${@myapp.translator.Translator@translateToFrench(textVar)}">Some text here...</p>`
__
可以使用在属性中对预处理字符串进行转义\_\_
。
5设置属性值
本章将解释我们在标记中设置(或修改)属性值的方式。
5.1设置任何属性的值
假设我们的网站发布了一个时事通讯,我们希望我们的用户能够订阅它,所以我们创建一个/WEB-INF/templates/subscribe.html
带有以下形式的模板:
`<form action="subscribe.html">
<fieldset>
<input type="text" name="email" />
<input type="submit" value="Subscribe!" />
</fieldset>
</form>`
与Thymeleaf一样,此模板更像是静态原型,而不是Web应用程序的模板。首先,action
我们表单中的属性静态链接到模板文件本身,因此没有地方可以进行有用的URL重写。其次,value
提交按钮中的属性使其显示英文文本,但我们希望它能够国际化。
然后输入th:attr
属性,以及更改其设置的标记属性值的能力:
`<form action="subscribe.html" th:attr="action=@{/subscribe}">
<fieldset>
<input type="text" name="email" />
<input type="submit" value="Subscribe!" th:attr="value=#{subscribe.submit}"/>
</fieldset>
</form>`
这个概念非常简单:th:attr
只需要一个为属性赋值的表达式。创建了相应的控制器和消息文件后,处理该文件的结果将是:
`<form action="/gtvg/subscribe">
<fieldset>
<input type="text" name="email" />
<input type="submit" value="¡Suscríbe!"/>
</fieldset>
</form>`
除了新的属性值之外,您还可以看到applicacion上下文名称已自动添加到URL基础中/gtvg/subscribe
,如前一章所述。
但是,如果我们想一次设置多个属性呢?XML规则不允许您在标记中设置两次属性,因此th:attr
将采用以逗号分隔的分配列表,例如:
`<img src="../../images/gtvglogo.png"
th:attr="src=@{/images/gtvglogo.png},title=#{logo},alt=#{logo}" />`
给定所需的消息文件,这将输出:
`<img src="/gtgv/images/gtvglogo.png" title="Logo de Good Thymes" alt="Logo de Good Thymes" />`
5.2为特定属性设置值
到现在为止,您可能会想到以下内容:
`<input type="submit" value="Subscribe!" th:attr="value=#{subscribe.submit}"/>`
…是一个非常丑陋的标记。在属性值中指定赋值可能非常实用,但如果您必须始终执行此操作,则它不是创建模板的最佳方式。
Thymeleaf同意你的意见,这就是th:attr
模板中几乎没有使用的原因。通常,您将使用th:*
其任务设置特定标记属性的其他属性(而不仅仅是任何属性th:attr
)。
例如,要设置value
属性,请使用th:value
:
`<input type="submit" value="Subscribe!" th:value="#{subscribe.submit}"/>`
这看起来好多了!让我们尝试action
对form
标记中的属性执行相同操作:
`<form action="subscribe.html" th:action="@{/subscribe}">`
你还记得th:href
我们home.html
之前放过的东西吗?它们正是同样的属性:
`<li><a href="product/list.html" th:href="@{/product/list}">Product List</a></li>`
有很多这样的属性,每个属性都针对特定的HTML5属性:
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:fragment |
th:frame | th:frameborder | th:headers |
th:height | th:high | th:href |
th:hreflang | th:hspace | th:http-equiv |
th:icon | th:id | th:inline |
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:onabort | th:onafterprint |
th:onbeforeprint | th:onbeforeunload | th:onblur |
th:oncanplay | th:oncanplaythrough | th:onchange |
th:onclick | th:oncontextmenu | th:ondblclick |
th:ondrag | th:ondragend | th:ondragenter |
th:ondragleave | th:ondragover | th:ondragstart |
th:ondrop | th:ondurationchange | th:onemptied |
th:onended | th:onerror | th:onfocus |
th:onformchange | th:onforminput | th:onhashchange |
th:oninput | th:oninvalid | th:onkeydown |
th:onkeypress | th:onkeyup | th:onload |
th:onloadeddata | th:onloadedmetadata | th:onloadstart |
th:onmessage | th:onmousedown | th:onmousemove |
th:onmouseout | th:onmouseover | th:onmouseup |
th:onmousewheel | th:onoffline | th:ononline |
th:onpause | th:onplay | th:onplaying |
th:onpopstate | th:onprogress | th:onratechange |
th:onreadystatechange | th:onredo | th:onreset |
th:onresize | th:onscroll | th:onseeked |
th:onseeking | th:onselect | th:onshow |
th:onstalled | th:onstorage | th:onsubmit |
th:onsuspend | th:ontimeupdate | th:onundo |
th:onunload | th:onvolumechange | th:onwaiting |
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 |
5.3一次设置多个值
有两个叫比较特殊的属性th:alt-title
和th:lang-xmllang
可用于同时设置两个属性相同的值。特别:
th:alt-title
将设置alt
和title
。th:lang-xmllang
将设置lang
和xml:lang
。
对于我们的GTVG主页,这将允许我们替换:
`<img src="../../images/gtvglogo.png"
th:attr="src=@{/images/gtvglogo.png},title=#{logo},alt=#{logo}" />`
…或者这个,相当于:
`<img src="../../images/gtvglogo.png"
th:src="@{/images/gtvglogo.png}" th:title="#{logo}" th:alt="#{logo}" />`
…有了这个:
`<img src="../../images/gtvglogo.png"
th:src="@{/images/gtvglogo.png}" th:alt-title="#{logo}" />`
5.4附加和预先
Thymeleaf还提供了th:attrappend
和th:attrprepend
属性,它们将评估结果附加(后缀)或前置(前缀)到现有属性值。
例如,您可能希望将要添加的CSS类的名称(未设置,仅添加)存储到上下文变量中的某个按钮,因为要使用的特定CSS类将取决于用户执行的操作。之前:
`<input type="button" value="Do it!" class="btn" th:attrappend="class=${' ' + cssStyle}" />`
如果您在cssStyle
变量设置为的情况下处理此模板"warning"
,您将获得:
`<input type="button" value="Do it!" class="btn warning" />`
标准方言中还有两个特定的附加属性:th:classappend
和th:styleappend
属性,用于向元素添加CSS类或样式片段而不覆盖现有元素:
`<tr th:each="prod : ${prods}" class="row" th:classappend="${prodStat.odd}? 'odd'">`
(不要担心该th:each
属性。它是一个迭代属性,我们稍后会讨论它。)
5.5固定值布尔属性
HTML具有布尔属性的概念,没有值的属性和一个值的前提意味着值为“true”。在XHTML中,这些属性只占用1个值,这本身就是一个值。
例如,checked
:
`<input type="checkbox" name="option2" checked /> <!-- HTML -->
<input type="checkbox" name="option1" checked="checked" /> <!-- XHTML -->`
标准方言包含允许您通过评估条件来设置这些属性的属性,因此如果计算为true,则属性将设置为其固定值,如果计算为false,则不会设置该属性:
`<input type="checkbox" name="active" th:checked="${user.active}" />`
标准方言中存在以下固定值布尔属性:
th:async | th:autofocus | th:autoplay |
th:checked | th:controls | th:declare |
th:default | th:defer | th:disabled |
th:formnovalidate | th:hidden | th:ismap |
th:loop | th:multiple | th:novalidate |
th:nowrap | th:open | th:pubdate |
th:readonly | th:required | th:reversed |
th:scoped | th:seamless | th:selected |
更多信息
5.6设置任何属性的值(默认属性处理器)
Thymeleaf提供了一个默认属性处理器,允许我们设置任何属性的值,即使th:*
在标准方言中没有为它定义特定的处理器。
所以类似于:
`<span th:whatever="${user.name}">...</span>`
将导致:
`<span whatever="John Apricot">...</span>`
5.7支持HTML5友好的属性和元素名称
也可以使用完全不同的语法以更加HTML5友好的方式将处理器应用于模板。
`<table>
<tr data-th-each="user : ${users}">
<td data-th-text="${user.login}">...</td>
<td data-th-text="${user.name}">...</td>
</tr>
</table>`
该data-{prefix}-{name}
语法编写自定义属性在HTML5中,而无需开发人员使用任何命名空间的名称,如标准的方式th:*
。Thymeleaf使所有方言(不仅是标准方言)自动使用此语法。
还有一种语法来指定自定义标签:{prefix}-{name}
,它遵循W3C自定义元素规范(较大的W3C Web组件规范的一部分)。例如,这可以用于th:block
元素(或者也可以th-block
),这将在后面的部分中解释。
**重要提示:**此语法是对命名空间语法的补充th:*
,它不会替换它。完全没有意图在将来弃用命名空间语法。
6迭代
到目前为止,我们已经创建了一个主页,一个用户个人资料页面以及一个允许用户订阅我们的新闻通讯的页面…但是我们的产品呢?为此,我们需要一种方法来迭代集合中的项目以构建我们的产品页面。
6.1迭代基础知识
要在我们的/WEB-INF/templates/product/list.html
页面中显示产品,我们将使用表格。我们的每个产品都会连续显示(一个<tr>
元素),因此对于我们的模板,我们需要创建一个模板行 - 一个可以说明我们希望如何显示每个产品的模板行 - 然后指示Thymeleaf重复它,每个产品一次。
标准方言为我们提供了一个属性:th:each
。
使用th:each
对于我们的产品列表页面,我们需要一个控制器方法,从服务层检索产品列表并将其添加到模板上下文中:
`public void process(
final HttpServletRequest request, final HttpServletResponse response,
final ServletContext servletContext, final ITemplateEngine templateEngine)
throws Exception {
ProductService productService = new ProductService();
List<Product> allProducts = productService.findAll();
WebContext ctx = new WebContext(request, response, servletContext, request.getLocale());
ctx.setVariable("prods", allProducts);
templateEngine.process("product/list", ctx, response.getWriter());
}`
然后我们将th:each
在我们的模板中使用迭代产品列表:
`<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Good Thymes Virtual Grocery</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link rel="stylesheet" type="text/css" media="all"
href="../../../css/gtvg.css" th:href="@{/css/gtvg.css}" />
</head>
<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>
</html>`
也就是说prod : ${prods}
您在上面看到的属性值是指“用于评估的结果的每个元素${prods}
,重复模板的该片段,用在变量称为刺当前元素”。让我们给出一个我们看到的每个事物的名称:
- 我们将调用
${prods}
的迭代式或迭代变量。 - 我们将调用
prod
的迭代变量或者干脆ITER变量。
请注意,prod
iter变量的作用域是<tr>
元素,这意味着它可用于内部标记<td>
。
可重复的值
该java.util.List
班是不是可以用于Thymeleaf迭代onlyvalue。有一组非常完整的对象被属性认为是可迭代的th:each
:
- 任何对象实现
java.util.Iterable
- 任何对象实现
java.util.Enumeration
。 - 任何实现的对象
java.util.Iterator
,其值将在迭代器返回时使用,而不需要将所有值缓存在内存中。 - 任何对象实现
java.util.Map
。迭代地图时,iter变量将属于类java.util.Map.Entry
。 - 任何数组。
- 任何其他对象都将被视为包含对象本身的单值列表。
6.2保持迭代状态
使用时th:each
,Thymeleaf提供了一种机制,可用于跟踪迭代的状态:状态变量。
状态变量在th:each
属性中定义,包含以下数据:
- 当前迭代索引,从0开始。这是
index
属性。 - 当前迭代索引,从1开始。这是
count
属性。 - 迭代变量中元素的总量。这是
size
酒店。 - 每次迭代的iter变量。这是
current
酒店。 - 当前迭代是偶数还是奇数。这些是
even/odd
布尔属性。 - 当前迭代是否是第一个迭代。这是
first
布尔属性。 - 当前迭代是否是最后一次迭代。这是
last
布尔属性。
让我们看看我们如何在前面的例子中使用它:
`<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>`
状态变量(iterStat
在此示例中)在th:each
属性中通过在iter变量本身之后写入其名称来定义,用逗号分隔。就像iter变量一样,状态变量的范围也限定为由包含该th:each
属性的标记定义的代码片段。
我们来看看处理模板的结果:
`<!DOCTYPE html>
<html>
<head>
<title>Good Thymes Virtual Grocery</title>
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/>
<link rel="stylesheet" type="text/css" media="all" href="/gtvg/css/gtvg.css" />
</head>
<body>
<h1>Product list</h1>
<table>
<tr>
<th>NAME</th>
<th>PRICE</th>
<th>IN STOCK</th>
</tr>
<tr class="odd">
<td>Fresh Sweet Basil</td>
<td>4.99</td>
<td>yes</td>
</tr>
<tr>
<td>Italian Tomato</td>
<td>1.25</td>
<td>no</td>
</tr>
<tr class="odd">
<td>Yellow Bell Pepper</td>
<td>2.50</td>
<td>yes</td>
</tr>
<tr>
<td>Old Cheddar</td>
<td>18.75</td>
<td>yes</td>
</tr>
</table>
<p>
<a href="/gtvg/" shape="rect">Return to home</a>
</p>
</body>
</html>`
请注意,我们的迭代状态变量已经完美地工作,odd
仅将CSS类建立到奇数行。
如果您没有显式设置状态变量,Thymeleaf将始终通过后缀Stat
为迭代变量的名称为您创建一个:
`<table>
<tr>
<th>NAME</th>
<th>PRICE</th>
<th>IN STOCK</th>
</tr>
<tr th:each="prod : ${prods}" th:class="${prodStat.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>`
6.3通过延迟检索数据进行优化
有时我们可能希望优化数据集合的检索(例如,从数据库中),以便只有在真正使用它们时才会检索这些集合。
实际上,这可以应用于任何数据片段,但考虑到内存中集合可能具有的大小,检索要迭代的集合是此方案的最常见情况。
为了支持这一点,Thymeleaf提供了一种懒惰加载上下文变量的机制。实现ILazyContextVariable
接口的上下文变量- 最有可能通过扩展其LazyContextVariable
默认实现 - 将在执行时解决。例如:
`context.setVariable(
"users",
new LazyContextVariable<List<User>>() {
@Override
protected List<User> loadValue() {
return databaseRepository.findAllUsers();
}
});`
可以在不知道其惰性的情况下使用此变量,例如:
`<ul>
<li th:each="u : ${users}" th:text="${u.name}">user name</li>
</ul>`
但与此同时,loadValue()
如果在代码中condition
进行求值,则永远不会被初始化(它的方法永远不会被调用)false
:
`<ul th:if="${condition}">
<li th:each="u : ${users}" th:text="${u.name}">user name</li>
</ul>`
7条件评估
7.1简单条件:“if”和“除非”
有时,如果满足某个条件,您将需要模板的片段才会出现在结果中。
例如,假设我们希望在产品表中显示一列,其中包含每个产品的评论数量,如果有任何评论,则指向该产品的评论详细信息页面的链接。
为此,我们将使用以下th:if
属性:
`<table>
<tr>
<th>NAME</th>
<th>PRICE</th>
<th>IN STOCK</th>
<th>COMMENTS</th>
</tr>
<tr th:each="prod : ${prods}" th:class="${prodStat.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>
<td>
<span th:text="${#lists.size(prod.comments)}">2</span> comment/s
<a href="comments.html"
th:href="@{/product/comments(prodId=${prod.id})}"
th:if="${not #lists.isEmpty(prod.comments)}">view</a>
</td>
</tr>
</table>`
这里有很多东西要看,所以让我们关注重要的一点:
`<a href="comments.html"
th:href="@{/product/comments(prodId=${prod.id})}"
th:if="${not #lists.isEmpty(prod.comments)}">view</a>`
这将创建指向评论页面(带有URL /product/comments
)的链接,其prodId
参数设置为id
产品的参数,但仅限于产品有任何评论。
我们来看看生成的标记:
`<table>
<tr>
<th>NAME</th>
<th>PRICE</th>
<th>IN STOCK</th>
<th>COMMENTS</th>
</tr>
<tr>
<td>Fresh Sweet Basil</td>
<td>4.99</td>
<td>yes</td>
<td>
<span>0</span> comment/s
</td>
</tr>
<tr class="odd">
<td>Italian Tomato</td>
<td>1.25</td>
<td>no</td>
<td>
<span>2</span> comment/s
<a href="/gtvg/product/comments?prodId=2">view</a>
</td>
</tr>
<tr>
<td>Yellow Bell Pepper</td>
<td>2.50</td>
<td>yes</td>
<td>
<span>0</span> comment/s
</td>
</tr>
<tr class="odd">
<td>Old Cheddar</td>
<td>18.75</td>
<td>yes</td>
<td>
<span>1</span> comment/s
<a href="/gtvg/product/comments?prodId=4">view</a>
</td>
</tr>
</table>`
完善!这正是我们想要的。
请注意,该th:if
属性不仅会评估布尔条件。它的功能稍微超出了它,它将按照true
以下规则评估指定的表达式:
- 如果value不为null:
- 如果value是布尔值,则为
true
。 - 如果value是数字且不为零
- 如果value是一个字符且不为零
- 如果value是String并且不是“false”,“off”或“no”
- 如果value不是布尔值,数字,字符或字符串。
- 如果value是布尔值,则为
- (如果value为null,则th:if将计算为false)。
此外,th:if
还有一个inverse属性,th:unless
我们可以在前面的示例中使用它,而不是not
在OGNL表达式中使用:
`<a href="comments.html"
th:href="@{/comments(prodId=${prod.id})}"
th:unless="${#lists.isEmpty(prod.comments)}">view</a>`
7.2切换语句
还有一种方法可以使用Java中的等效开关结构有条件地显示内容:th:switch
/ th:case
属性集。
`<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>`
请注意,只要th:case
评估true
一个th:case
属性,就会将同一切换上下文中的每个其他属性评估为false
。
默认选项指定为th:case="*"
:
`<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>`
8模板布局
8.1包括模板片段
定义和引用片段
在我们的模板中,我们经常需要包含其他模板中的部分,页脚,标题,菜单等部分…
为了做到这一点,Thymeleaf需要我们定义这些部分,“片段”,以便包含,这可以使用th:fragment
属性来完成。
假设我们要在所有杂货页面上添加标准版权页脚,因此我们创建一个/WEB-INF/templates/footer.html
包含以下代码的文件:
`<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<div th:fragment="copy">
© 2011 The Good Thymes Virtual Grocery
</div>
</body>
</html>`
上面的代码定义了一个名为的片段copy
,我们可以使用其中一个th:insert
或th:replace
属性轻松地在我们的主页中包含这些片段(并且th:include
,尽管自Thymeleaf 3.0以来不再推荐使用它):
`<body>
...
<div th:insert="~{footer :: copy}"></div>
</body>`
请注意,th:insert
需要一个片段表达式(~{...}
),它是一个导致片段的表达式。在上面的例子中,这是一个非复杂的片段表达式,(~{
,}
)封闭是完全可选的,所以上面的代码相当于:
`<body>
...
<div th:insert="footer :: copy"></div>
</body>`
片段规范语法
片段表达式的语法非常简单。有三种不同的格式:
"~{templatename::selector}"
包括在名为的模板上应用指定标记选择器而产生的片段templatename
。请注意,selector
可以仅仅是一个片段的名字,所以你可以指定为简单的东西~{templatename::fragmentname}
就像在~{footer :: copy}
上面。
标记选择器语法由底层的AttoParser解析库定义,类似于XPath表达式或CSS选择器。有关详细信息,请参阅附录C.
"~{templatename}"
包含名为的完整模板templatename
。
请注意,您在
th:insert
/th:replace
tags中使用的模板名称必须由模板引擎当前使用的模板解析器解析。
~{::selector}"
或"~{this::selector}"
插入来自同一模板的片段,进行匹配selector
。如果在表达式出现的模板上找不到,则模板调用(插入)的堆栈将遍历最初处理的模板(根),直到selector
在某个级别匹配。
双方templatename
并selector
在上面的例子可以是全功能的表达式(甚至条件语句!),如:
`<div th:insert="footer :: (${user.isAdmin}? #{footer.admin} : #{footer.normaluser})"></div>`
再次注意周围的~{...}
包络在th:insert
/中是如何可选的th:replace
。
片段可以包含任何th:*
属性。一旦将片段包含在目标模板(具有th:insert
/ th:replace
attribute的模板)中,就会评估这些属性,并且它们将能够引用此目标模板中定义的任何上下文变量。
这种片段方法的一大优点是,您可以在浏览器完全可显示的页面中编写片段,具有完整且有效的标记结构,同时仍保留使Thymeleaf将其包含在其他模板中的能力。
没有引用片段 th:fragment
由于标记选择器的强大功能,我们可以包含不使用任何th:fragment
属性的片段。它甚至可以是来自不同应用程序的标记代码,完全不了解Thymeleaf:
`...
<div id="copy-section">
© 2011 The Good Thymes Virtual Grocery
</div>
...`
我们可以使用上面的片段,通过其id
属性简单地引用它,与CSS选择器类似:
`<body>
...
<div th:insert="~{footer :: #copy-section}"></div>
</body>`
th:insert
和th:replace
(和th:include
)之间的区别
和之间有什么区别th:insert
和th:replace
(和th:include
,因为3.0不推荐)?
th:insert
是最简单的:它只是插入指定的片段作为其主机标签的主体。th:replace
实际上用指定的片段替换它的主机标签。th:include
类似于th:insert
,但不是插入片段,它只插入此片段的内容。
所以像这样的HTML片段:
`<footer th:fragment="copy">
© 2011 The Good Thymes Virtual Grocery
</footer>`
…在主机<div>
标签中包含三次,如下所示:
`<body>
...
<div th:insert="footer :: copy"></div>
<div th:replace="footer :: copy"></div>
<div th:include="footer :: copy"></div>
</body>`
…将导致:
`<body>
...
<div>
<footer>
© 2011 The Good Thymes Virtual Grocery
</footer>
</div>
<footer>
© 2011 The Good Thymes Virtual Grocery
</footer>
<div>
© 2011 The Good Thymes Virtual Grocery
</div>
</body>`
8.2可参数化的片段签名
为了为模板片段创建更像函数的机制,使用定义的片段th:fragment
可以指定一组参数:
`<div th:fragment="frag (onevar,twovar)">
<p th:text="${onevar} + ' - ' + ${twovar}">...</p>
</div>`
这需要使用这两种语法之一来从th:insert
或调用片段th:replace
:
`<div th:replace="::frag (${value1},${value2})">...</div>
<div th:replace="::frag (onevar=${value1},twovar=${value2})">...</div>`
请注意,在最后一个选项中,顺序并不重要:
`<div th:replace="::frag (twovar=${value2},onevar=${value1})">...</div>`
片段局部变量没有片段参数
即使片段定义没有这样的参数:
`<div th:fragment="frag">
...
</div>`
我们可以使用上面指定的第二种语法来调用它们(只有第二种语法):
`<div th:replace="::frag (onevar=${value1},twovar=${value2})">`
这将相当于组合th:replace
和th:with
:
`<div th:replace="::frag" th:with="onevar=${value1},twovar=${value2}">`
请注意,片段的局部变量的这种规范 - 无论它是否具有参数签名 - 都不会导致在执行之前清空上下文。片段仍然可以访问调用模板中使用的每个上下文变量,就像它们当前一样。
th:断言in-template断言
该th:assert
属性可以指定一个以逗号分隔的表达式列表,这些表达式应该被评估并为每次评估生成true,否则会引发异常。
`<div th:assert="${onevar},(${twovar} != 43)">...</div>`
这对于验证片段签名的参数非常方便:
`<header th:fragment="contentheader(title)" th:assert="${!#strings.isEmpty(title)}">...</header>`
8.3灵活的布局:仅仅是片段插入
由于片段表达式,我们可以为不是文本,数字,bean对象的片段指定参数…而是指定标记片段。
这允许我们以一种方式创建我们的片段,使得它们可以通过来自调用模板的标记来丰富,从而产生非常灵活的模板布局机制。
注意在下面的片段中使用title
和links
变量:
`<head th:fragment="common\_header(title,links)">
<title th:replace="${title}">The awesome application</title>
<!-- Common styles and scripts -->
<link rel="stylesheet" type="text/css" media="all" th:href="@{/css/awesomeapp.css}">
<link rel="shortcut icon" th:href="@{/images/favicon.ico}">
<script type="text/javascript" th:src="@{/sh/scripts/codebase.js}"></script>
<!--/\* Per-page placeholder for additional links \*/-->
<th:block th:replace="${links}" />
</head>`
我们现在可以将这个片段称为:
`...
<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>
...`
…结果将使用我们的调用模板中的实际<title>
和<link>
标签作为title
和links
变量的值,从而导致我们的片段在插入过程中自定义:
`...
<head>
<title>Awesome - Main</title>
<!-- Common styles and scripts -->
<link rel="stylesheet" type="text/css" media="all" href="/awe/css/awesomeapp.css">
<link rel="shortcut icon" href="/awe/images/favicon.ico">
<script type="text/javascript" src="/awe/sh/scripts/codebase.js"></script>
<link rel="stylesheet" href="/awe/css/bootstrap.min.css">
<link rel="stylesheet" href="/awe/themes/smoothness/jquery-ui.css">
</head>
...`
使用空片段
一个特殊的片段表达式,即空片段(~{}
),可用于指定无标记。使用前面的示例:
`<head th:replace="base :: common\_header(~{::title},~{})">
<title>Awesome - Main</title>
</head>
...`
注意fragment(links
)的第二个参数是如何设置为空片段的,因此没有为<th:block th:replace="${links}" />
块写入任何内容:
`...
<head>
<title>Awesome - Main</title>
<!-- Common styles and scripts -->
<link rel="stylesheet" type="text/css" media="all" href="/awe/css/awesomeapp.css">
<link rel="shortcut icon" href="/awe/images/favicon.ico">
<script type="text/javascript" src="/awe/sh/scripts/codebase.js"></script>
</head>
...`
使用无操作令牌
如果我们只想让我们的片段使用其当前标记作为默认值,则no-op也可以用作片段的参数。再次,使用common_header
示例:
`...
<head th:replace="base :: common\_header(\_,~{::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>
...`
看看如何将title
参数(common_header
片段的第一个参数)设置为no-op(_
),这导致片段的这一部分根本不被执行(title
= 无操作):
`<title th:replace="${title}">The awesome application</title>`
结果是:
`...
<head>
<title>The awesome application</title>
<!-- Common styles and scripts -->
<link rel="stylesheet" type="text/css" media="all" href="/awe/css/awesomeapp.css">
<link rel="shortcut icon" href="/awe/images/favicon.ico">
<script type="text/javascript" src="/awe/sh/scripts/codebase.js"></script>
<link rel="stylesheet" href="/awe/css/bootstrap.min.css">
<link rel="stylesheet" href="/awe/themes/smoothness/jquery-ui.css">
</head>
...`
高级条件插入片段
emtpy片段和无操作令牌的可用性允许我们以非常简单和优雅的方式执行片段的条件插入。
例如,我们可以这样做,以便仅在用户是管理员时插入我们的common :: adminhead
片段,并且如果不是,则不插入任何内容(emtpy片段):
`...
<div th:insert="${user.isAdmin()} ? ~{common :: adminhead} : ~{}">...</div>
...`
此外,我们可以使用无操作令牌,以便仅在满足指定条件时插入片段,但如果不满足条件则保留标记而不进行修改:
`...
<div th:insert="${user.isAdmin()} ? ~{common :: adminhead} : \_">
Welcome [[${user.name}]], click <a th:href="@{/support}">here</a> for help-desk support.
</div>
...`
另外,如果我们已经配置了模板解析器来检查模板资源是否存在 - 通过它们的checkExistence
标志 - 我们可以使用片段本身的存在作为默认操作中的条件:
`...
<!-- The body of the <div> will be used if the "common :: salutation" fragment -->
<!-- does not exist (or is empty). -->
<div th:insert="~{common :: salutation} ?: \_">
Welcome [[${user.name}]], click <a th:href="@{/support}">here</a> for help-desk support.
</div>
...`
8.4删除模板片段
回到示例应用程序,让我们重新访问我们的产品列表模板的最新版本:
`<table>
<tr>
<th>NAME</th>
<th>PRICE</th>
<th>IN STOCK</th>
<th>COMMENTS</th>
</tr>
<tr th:each="prod : ${prods}" th:class="${prodStat.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>
<td>
<span th:text="${#lists.size(prod.comments)}">2</span> comment/s
<a href="comments.html"
th:href="@{/product/comments(prodId=${prod.id})}"
th:unless="${#lists.isEmpty(prod.comments)}">view</a>
</td>
</tr>
</table>`
这段代码作为一个模板很好,但作为一个静态页面(当浏览器直接打开而没有Thymeleaf处理它时)它就不会成为一个好的原型。
为什么?因为虽然浏览器可以完全显示,但该表只有一行,而且这行包含模拟数据。作为原型,它看起来不够逼真…我们应该有多个产品,我们需要更多行。
所以让我们添加一些:
`<table>
<tr>
<th>NAME</th>
<th>PRICE</th>
<th>IN STOCK</th>
<th>COMMENTS</th>
</tr>
<tr th:each="prod : ${prods}" th:class="${prodStat.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>
<td>
<span th:text="${#lists.size(prod.comments)}">2</span> comment/s
<a href="comments.html"
th:href="@{/product/comments(prodId=${prod.id})}"
th:unless="${#lists.isEmpty(prod.comments)}">view</a>
</td>
</tr>
<tr class="odd">
<td>Blue Lettuce</td>
<td>9.55</td>
<td>no</td>
<td>
<span>0</span> comment/s
</td>
</tr>
<tr>
<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>
</table>`
好的,现在我们有三个,对原型来说肯定更好。但是…当我们用Thymeleaf处理它时会发生什么?:
`<table>
<tr>
<th>NAME</th>
<th>PRICE</th>
<th>IN STOCK</th>
<th>COMMENTS</th>
</tr>
<tr>
<td>Fresh Sweet Basil</td>
<td>4.99</td>
<td>yes</td>
<td>
<span>0</span> comment/s
</td>
</tr>
<tr class="odd">
<td>Italian Tomato</td>
<td>1.25</td>
<td>no</td>
<td>
<span>2</span> comment/s
<a href="/gtvg/product/comments?prodId=2">view</a>
</td>
</tr>
<tr>
<td>Yellow Bell Pepper</td>
<td>2.50</td>
<td>yes</td>
<td>
<span>0</span> comment/s
</td>
</tr>
<tr class="odd">
<td>Old Cheddar</td>
<td>18.75</td>
<td>yes</td>
<td>
<span>1</span> comment/s
<a href="/gtvg/product/comments?prodId=4">view</a>
</td>
</tr>
<tr class="odd">
<td>Blue Lettuce</td>
<td>9.55</td>
<td>no</td>
<td>
<span>0</span> comment/s
</td>
</tr>
<tr>
<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>
</table>`
最后两行是模拟行!嗯,当然它们是:迭代仅适用于第一行,所以没有理由为什么Thymeleaf应该删除其他两个。
我们需要一种在模板处理过程中删除这两行的方法。让我们th:remove
在第二个和第三个<tr>
标签上使用该属性:
`<table>
<tr>
<th>NAME</th>
<th>PRICE</th>
<th>IN STOCK</th>
<th>COMMENTS</th>
</tr>
<tr th:each="prod : ${prods}" th:class="${prodStat.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>
<td>
<span th:text="${#lists.size(prod.comments)}">2</span> comment/s
<a href="comments.html"
th:href="@{/product/comments(prodId=${prod.id})}"
th:unless="${#lists.isEmpty(prod.comments)}">view</a>
</td>
</tr>
<tr class="odd" th:remove="all">
<td>Blue Lettuce</td>
<td>9.55</td>
<td>no</td>
<td>
<span>0</span> comment/s
</td>
</tr>
<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>
</table>`
处理完毕后,所有内容都将按原样重复:
`<table>
<tr>
<th>NAME</th>
<th>PRICE</th>
<th>IN STOCK</th>
<th>COMMENTS</th>
</tr>
<tr>
<td>Fresh Sweet Basil</td>
<td>4.99</td>
<td>yes</td>
<td>
<span>0</span> comment/s
</td>
</tr>
<tr class="odd">
<td>Italian Tomato</td>
<td>1.25</td>
<td>no</td>
<td>
<span>2</span> comment/s
<a href="/gtvg/product/comments?prodId=2">view</a>
</td>
</tr>
<tr>
<td>Yellow Bell Pepper</td>
<td>2.50</td>
<td>yes</td>
<td>
<span>0</span> comment/s
</td>
</tr>
<tr class="odd">
<td>Old Cheddar</td>
<td>18.75</td>
<td>yes</td>
<td>
<span>1</span> comment/s
<a href="/gtvg/product/comments?prodId=4">view</a>
</td>
</tr>
</table>`
all
属性中的这个值是什么意思?th:remove
可以根据其价值以五种不同的方式表现:
all
:删除包含标记及其所有子标记。body
:不要删除包含标记,但删除其所有子标记。tag
:删除包含标记,但不删除其子项。all-but-first
:除第一个子项外,删除包含标记的所有子项。none
: 没做什么。此值对于动态评估很有用。
这个all-but-first
价值有什么用呢?它将让我们th:remove="all"
在原型设计时节省一些:
`<table>
<thead>
<tr>
<th>NAME</th>
<th>PRICE</th>
<th>IN STOCK</th>
<th>COMMENTS</th>
</tr>
</thead>
<tbody th:remove="all-but-first">
<tr th:each="prod : ${prods}" th:class="${prodStat.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>
<td>
<span th:text="${#lists.size(prod.comments)}">2</span> comment/s
<a href="comments.html"
th:href="@{/product/comments(prodId=${prod.id})}"
th:unless="${#lists.isEmpty(prod.comments)}">view</a>
</td>
</tr>
<tr class="odd">
<td>Blue Lettuce</td>
<td>9.55</td>
<td>no</td>
<td>
<span>0</span> comment/s
</td>
</tr>
<tr>
<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>
</tbody>
</table>`
该th:remove
属性可采取任何Thymeleaf标准表示,因为它返回允许字符串值中的一个,只要(all
,tag
,body
,all-but-first
或none
)。
这意味着删除可能是有条件的,例如:
`<a href="/something" th:remove="${condition}? tag : none">Link text not to be removed</a>`
另请注意,th:remove
考虑null
到同义词none
,因此以下工作方式与上面的示例相同:
`<a href="/something" th:remove="${condition}? tag">Link text not to be removed</a>`
在这种情况下,如果${condition}
为false,null
将返回,因此不会执行删除。
8.5布局继承
为了能够将单个文件作为布局,可以使用片段。具有title
和content
使用th:fragment
和的简单布局示例th:replace
:
`<!DOCTYPE html>
<html th:fragment="layout (title, content)" xmlns:th="http://www.thymeleaf.org">
<head>
<title th:replace="${title}">Layout Title</title>
</head>
<body>
<h1>Layout H1</h1>
<div th:replace="${content}">
<p>Layout content</p>
</div>
<footer>
Layout footer
</footer>
</body>
</html>`
此示例声明了一个名为layout的片段,其中title和content作为参数。在下面的示例中,两者都将在页面上替换,并通过提供的片段表达式继承它。
`<!DOCTYPE html>
<html th:replace="~{layoutFile :: layout(~{::title}, ~{::section})}">
<head>
<title>Page Title</title>
</head>
<body>
<section>
<p>Page content</p>
<div>Included on page</div>
</section>
</body>
</html>`
在这个文件中,该html
标签将被替换的布局,但在布局title
和content
将已被替换title
,并section
分别块。
如果需要,布局可以由多个片段组成页眉和页脚。
9局部变量
Thymeleaf将局部变量称为为模板的特定片段定义的变量,并且仅可用于在该片段内进行评估。
我们已经看到的一个例子是prod
我们的产品列表页面中的iter变量:
`<tr th:each="prod : ${prods}">
...
</tr>`
该prod
变量仅在<tr>
标记的范围内可用。特别:
- 它将可用于
th:*
在该标记中执行的任何其他属性,其优先级低于th:each
(这意味着它们将在之后执行th:each
)。 - 它将可用于
<tr>
标记的任何子<td>
元素,例如任何元素。
Thymeleaf为您提供了一种使用th:with
属性声明局部变量而无需迭代的方法,其语法类似于属性值赋值:
`<div th:with="firstPer=${persons[0]}">
<p>
The name of the first person is <span th:text="${firstPer.name}">Julius Caesar</span>.
</p>
</div>`
当th:with
被处理时,该firstPer
变量被创建为一个局部变量,并加入到变量映射从上下文来,使得它可用于评估与在上下文中声明的任何其它变量一起,但仅在含有的边界<div>
标记。
您可以使用通常的多重赋值语法同时定义多个变量:
`<div th:with="firstPer=${persons[0]},secondPer=${persons[1]}">
<p>
The name of the first person is <span th:text="${firstPer.name}">Julius Caesar</span>.
</p>
<p>
But the name of the second person is
<span th:text="${secondPer.name}">Marcus Antonius</span>.
</p>
</div>`
该th:with
属性允许重用在同一属性中定义的变量:
`<div th:with="company=${user.company + ' Co.'},account=${accounts[company]}">...</div>`
我们在Grocery的主页上使用它!还记得我们为输出格式化日期而编写的代码吗?
`<p>
Today is:
<span th:text="${#calendars.format(today,'dd MMMM yyyy')}">13 february 2011</span>
</p>`
那么,如果我们想要"dd MMMM yyyy"
实际依赖于语言环境怎么办?例如,我们可能希望将以下消息添加到我们的home_en.properties
:
date.format=MMMM dd'','' yyyy
…和我们相同的一个home_es.properties
:
date.format=dd ''de'' MMMM'','' yyyy
现在,让我们使用th:with
将本地化的日期格式转换为变量,然后在th:text
表达式中使用它:
`<p th:with="df=#{date.format}">
Today is: <span th:text="${#calendars.format(today,df)}">13 February 2011</span>
</p>`
那简洁干净。事实上,鉴于这一事实th:with
具有较高的precedence
比th:text
,我们可以解决这一切的span
标签:
`<p>
Today is:
<span th:with="df=#{date.format}"
th:text="${#calendars.format(today,df)}">13 February 2011</span>
</p>`
你可能在想:优先权?我们还没有谈过这个!好吧,不要担心,因为这正是下一章的内容。
10属性优先级
th:*
在同一个标签中写入多个属性会发生什么?例如:
`<ul>
<li th:each="item : ${items}" th:text="${item.description}">Item description here...</li>
</ul>`
我们希望该th:each
属性在之前执行,th:text
以便我们得到我们想要的结果,但是考虑到HTML / XML标准没有给标签中的属性写入的顺序赋予任何意义,优先级必须在属性本身中建立机制,以确保这将按预期工作。
因此,所有Thymeleaf属性都定义了一个数字优先级,它确定了它们在标记中执行的顺序。这个顺序是:
订购 | 特征 | 属性 |
---|---|---|
1 | 片段包含 | th:insert``th:replace |
2 | 片段迭代 | th:each |
3 | 有条件的评估 | th:if``th:unless``th:switch``th:case |
4 | 局部变量定义 | th:object``th:with |
五 | 一般属性修改 | th:attr``th:attrprepend``th:attrappend |
6 | 具体属性修改 | th:value``th:href``th:src``... |
7 | 文字(标签正文修改) | th:text``th:utext |
8 | 片段规范 | th:fragment |
9 | 片段删除 | th:remove |
这个优先级机制意味着如果属性位置被反转,上面的迭代片段将给出完全相同的结果(尽管它的可读性稍差):
`<ul>
<li th:text="${item.description}" th:each="item : ${items}">Item description here...</li>
</ul>`
11评论和块
11.1。标准HTML / XML注释
标准HTML / XML注释<!-- ... -->
可以在Thymeleaf模板中的任何位置使用。Thymeleaf将不会处理这些评论中的任何内容,并将逐字复制到结果中:
`<!-- User info follows -->
<div th:text="${...}">
...
</div>`
11.2。Thymeleaf解析器级注释块
解析器级注释块是在Thymeleaf解析它时将简单地从模板中删除的代码。它们看起来像这样:
`<!--/\* This code will be removed at Thymeleaf parsing time! \*/-->`
Thymeleaf将删除一切与<!--/*
和*/-->
,所以这些注释块也可以用于显示当模板是静态开放代码,知道当Thymeleaf处理它,它都将被删除:
`<!--/\*-->
<div>
you can see me only before Thymeleaf processes me!
</div>
<!--\*/-->`
对于具有大量原型的表进行原型设计,这可能非常方便<tr>
,例如:
`<table>
<tr th:each="x : ${xs}">
...
</tr>
<!--/\*-->
<tr>
...
</tr>
<tr>
...
</tr>
<!--\*/-->
</table>`
11.3。Thymeleaf原型评论块
当模板静态打开时(即作为原型),Thymeleaf允许定义标记为注释的特殊注释块,但在执行模板时Thymeleaf认为是正常标记。
`<span>hello!</span>
<!--/\*/
<div th:text="${...}">
...
</div>
/\*/-->
<span>goodbye!</span>`
Thymeleaf的解析系统将简单地删除<!--/*/
和/*/-->
标记,但不删除其内容,因此将保留未注释。因此,在执行模板时,Thymeleaf实际上会看到:
`<span>hello!</span>
<div th:text="${...}">
...
</div>
<span>goodbye!</span>`
与解析器级注释块一样,此功能与方言无关。
11.4。合成th:block
标签
标准方言中包含的Thymeleaf唯一的元素处理器(不是属性)是th:block
。
th:block
是一个纯粹的属性容器,允许模板开发人员指定他们想要的任何属性。Thymeleaf将执行这些属性,然后简单地使块,但不是它的内容,消失。
因此,在创建<tr>
每个元素需要多个迭代表时,它可能很有用:
`<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>
<!--/\*/ <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>`
注意这个解决方案如何让模板成为有效的HTML(不需要在<div>
里面添加禁止的块<table>
),并且在浏览器中作为原型静态打开时仍然可以正常工作!
12内联
12.1表达内联
虽然标准方言允许我们使用标签属性几乎完成所有操作,但在某些情况下我们可能更喜欢将表达式直接写入HTML文本。例如,我们更喜欢这样写:
`<p>Hello, [[${session.user.name}]]!</p>`
…而不是这个:
`<p>Hello, <span th:text="${session.user.name}">Sebastian</span>!</p>`
在Thymeleaf 之间表达[[...]]
或被[(...)]
认为是内联表达式,在其中我们可以使用任何类型的表达式,这些表达式在一个th:text
或th:utext
属性中也是有效的。
请注意,虽然[[...]]
对应于th:text
(即结果将被HTML转义),但是[(...)]
对应于th:utext
并且不会执行任何HTML转义。所以对于一个变量,如msg = 'This is <b>great!</b>'
给定这个片段:
`<p>The message is "[(${msg})]"</p>`
结果将使这些<b>
标签不转义,因此:
`<p>The message is "This is <b>great!</b>"</p>`
而如果像以下一样逃脱:
`<p>The message is "[[${msg}]]"</p>`
结果将被HTML转义:
`<p>The message is "This is <b>great!</b>"</p>`
请注意,默认情况下,文本内联在标记中的每个标记的主体中都是活动的 - 而不是标记本身 - 因此我们无需执行任何操作即可启用它。
内联vs自然模板
如果你来自其他模板引擎,其中这种输出文本的方式是常态,你可能会问:为什么我们从一开始就不这样做?它的代码少于所有这些 th:text
属性!
好吧,小心那里,因为虽然你可能会发现内联非常有趣,但你应该永远记住,当你静态打开它们时,内联表达式将逐字显示在你的HTML文件中,所以你可能无法将它们用作设计原型了!
浏览器静态显示我们的代码片段而不使用内联的区别…
Hello, Sebastian!
…并使用它…
Hello, [[${session.user.name}]]!
…在设计实用性方面非常清楚。
禁用内联
但是,可以禁用此机制,因为实际上可能存在我们确实希望输出[[...]]
或[(...)]
序列而不将其内容作为表达式处理的情况。为此,我们将使用th:inline="none"
:
`<p th:inline="none">A double array looks like this: [[1, 2, 3], [4, 5]]!</p>`
这将导致:
`<p>A double array looks like this: [[1, 2, 3], [4, 5]]!</p>`
12.2文字内联
文本内联与我们刚刚看到的表达内联功能非常相似,但它实际上增加了更多功能。它必须明确启用th:inline="text"
。
文本内联不仅允许我们使用我们刚才看到的相同内联表达式,而且实际上处理标签主体就好像它们是在TEXT
模板模式下处理的模板一样,这允许我们执行基于文本的模板逻辑(不仅仅是输出表达式)。
我们将在下一章中看到有关文本模板模式的更多信息。
12.3 JavaScript内联
JavaScript内联允许<script>
在HTML
模板模式下处理的模板中更好地集成JavaScript 块。
与文本内联一样,这实际上相当于处理脚本内容,就好像它们是JAVASCRIPT
模板模式中的模板一样,因此文本模板模式的所有功能(见下一章)都将在眼前。但是,在本节中,我们将重点介绍如何使用它将Thymeleaf表达式的输出添加到JavaScript块中。
必须使用th:inline="javascript"
以下方式明确启用此模式:
`<script th:inline="javascript">
...
var username = [[${session.user.name}]];
...
</script>`
这将导致:
`<script th:inline="javascript">
...
var username = "Sebastian \"Fruity\" Applejuice";
...
</script>`
上面代码中需要注意的两件重要事项:
首先,JavaScript内联不仅会输出所需的文本,而且还会用引号和JavaScript来包含它 - 转义其内容,以便将表达式结果输出为格式良好的JavaScript文字。
其次,发生这种情况是因为我们将${session.user.name}
表达式输出为转义,即使用双括号表达式:[[${session.user.name}]]
。如果相反,我们使用非*转义,*如:
`<script th:inline="javascript">
...
var username = [(${session.user.name})];
...
</script>`
结果如下:
`<script th:inline="javascript">
...
var username = Sebastian "Fruity" Applejuice;
...
</script>`
…这是一个格式错误的JavaScript代码。但是,如果我们通过附加内联表达式来构建脚本的一部分,那么输出未转义的内容可能就是我们所需要的,因此最好有这个工具。
JavaScript自然模板
所提到的JavaScript内联机制的智能远不止仅仅应用特定于JavaScript的转义并将表达式结果输出为有效文字。
例如,我们可以在JavaScript注释中包装我们的(转义的)内联表达式,例如:
`<script th:inline="javascript">
...
var username = /*[[${session.user.name}]]*/ "Gertrud Kiwifruit";
...
</script>`
并且Thymeleaf将忽略我们在注释之后和分号之前(在这种情况下'Gertrud Kiwifruit'
)编写的所有内容,因此执行此操作的结果看起来与我们不使用包装注释时的结果完全相同:
`<script th:inline="javascript">
...
var username = "Sebastian \"Fruity\" Applejuice";
...
</script>`
但请仔细查看原始模板代码:
`<script th:inline="javascript">
...
var username = /*[[${session.user.name}]]*/ "Gertrud Kiwifruit";
...
</script>`
请注意这是有效的JavaScript代码。当您以静态方式打开模板文件时(无需在服务器上执行),它将完美执行。
所以我们这里有一个做自然模板的方法!
高级内联评估和JavaScript序列化
关于JavaScript内联的一个重要注意事项是,这种表达式评估是智能的,不仅限于字符串。Thymeleaf将在JavaScript语法中正确编写以下类型的对象:
- 字符串
- 数字
- 布尔
- 数组
- 集合
- 地图
- Bean(具有getter和setter方法的对象)
例如,如果我们有以下代码:
`<script th:inline="javascript">
...
var user = /*[[${session.user}]]*/ null;
...
</script>`
该${session.user}
表达式将评估为一个User
对象,Thymeleaf将正确地将其转换为Javascript语法:
`<script th:inline="javascript">
...
var user = {"age":null,"firstName":"John","lastName":"Apricot",
"name":"John Apricot","nationality":"Antarctica"};
...
</script>`
这种JavaScript序列化的方式是通过org.thymeleaf.standard.serializer.IStandardJavaScriptSerializer
接口的实现,可以StandardDialect
在模板引擎使用的实例上配置。
此JS序列化机制的默认实现将在类路径中查找Jackson库,如果存在,将使用它。如果没有,它将应用内置的序列化机制,涵盖大多数场景的需求并产生类似的结果(但不太灵活)。
12.4 CSS内联
Thymeleaf还允许在CSS <style>
标签中使用内联,例如:
`<style th:inline="css">
...
</style>`
例如,假设我们将两个变量设置为两个不同的String
值:
classname = 'main elems'
align = 'center'
我们可以像以下一样使用它们:
`<style th:inline="css">
.[[${classname}]] {
text-align: [[${align}]];
}
</style>`
结果将是:
`<style th:inline="css">
.main\ elems {
text-align: center;
}
</style>`
请注意CSS内联如何具有一些智能,就像JavaScript一样。具体来说,通过转义表达式输出的表达式[[${classname}]]
将作为CSS标识符进行转义。这就是为什么我们classname = 'main elems'
已经main\ elems
在上面的代码片段中变成了原因。
高级功能:CSS自然模板等
与之前针对JavaScript解释的内容相同,CSS内联还允许我们的<style>
标记静态和动态地工作,即通过在注释中包装内联表达式作为CSS自然模板。看到:
`<style th:inline="css">
.main\ elems {
text-align: /*[[${align}]]*/ left;
}
</style>`
13文本模板模式
13.1文本语法
在Thymeleaf的三种模板模式被认为是文字:TEXT
,JAVASCRIPT
和CSS
。这使它们与标记模板模式区别开来:HTML
和XML
。
文本模板模式与标记模式之间的关键区别在于,在文本模板中没有标签可以以属性的形式插入逻辑,因此我们必须依赖其他机制。
这些机制中的第一个也是最基本的是内联,我们已在前一章中详细介绍过。内联语法是在文本模板模式下输出表达式结果的最简单方法,因此这是一个完全有效的文本电子邮件模板。
Dear [(${name})],
Please find attached the results of the report you requested
with name "[(${report.name})]".
Sincerely,
The Reporter.
即使没有标签,上面的例子也是一个完整有效的Thymeleaf模板,可以在TEXT
模板模式下执行。
但是为了包含比仅仅输出表达式更复杂的逻辑,我们需要一种新的基于非标记的语法:
[# th:each="item : ${items}"]
- [(${item})]
[/]
这实际上是更详细的浓缩版本:
[#th:block th:each="item : ${items}"]
- [#th:block th:utext="${item}" /]
[/th:block]
请注意这个新语法是如何基于声明为[#element ...]
而不是的元素(即可处理标记)<element ...>
。元素是开放的像[#element ...]
封闭一样[/element]
,并且可以通过最小化open元素来声明独立标签/
,其方式几乎等同于XML标签:[#element ... /]
。
标准方言只包含其中一个元素的处理器:已知的th:block
,虽然我们可以在我们的方言中扩展它并以通常的方式创建新元素。此外,允许将th:block
element([#th:block ...] ... [/th:block]
)缩写为空字符串([# ...] ... [/]
),因此上面的块实际上等效于:
[# th:each="item : ${items}"]
- [# th:utext="${item}" /]
[/]
并且给定[# th:utext="${item}" /]
等效于内联非转义表达式,我们可以使用它来获得更少的代码。因此,我们最终得到了上面看到的第一个代码片段:
[# th:each="item : ${items}"]
- [(${item})]
[/]
请注意,文本语法需要完整的元素平衡(没有未关闭的标记)和引用的属性 - 它比HTML风格更加XML风格。
让我们看一个更完整的TEXT
模板示例,一个纯文本电子邮件模板:
Dear [(${customer.name})],
This is the list of our products:
[# th:each="prod : ${products}"]
- [(${prod.name})]. Price: [(${prod.price})] EUR/kg
[/]
Thanks,
The Thymeleaf Shop
执行后,结果可能是这样的:
Dear Mary Ann Blueberry,
This is the list of our products:
- Apricots. Price: 1.12 EUR/kg
- Bananas. Price: 1.78 EUR/kg
- Apples. Price: 0.85 EUR/kg
- Watermelon. Price: 1.91 EUR/kg
Thanks,
The Thymeleaf Shop
另一个例子是JAVASCRIPT
模板模式,一个greeter.js
文件,我们作为文本模板处理,我们从HTML页面调用结果。请注意,这不是<script>
HTML模板中的块,而是.js
自己作为模板处理的文件:
`var greeter = function() {
var username = [[${session.user.name}]];
[# th:each="salut : ${salutations}"]
alert([[${salut}]] + " " + username);
[/]
};`
执行后,结果可能是这样的:
`var greeter = function() {
var username = "Bertrand \"Crunchy\" Pear";
alert("Hello" + " " + username);
alert("Ol\u00E1" + " " + username);
alert("Hola" + " " + username);
};`
转义元素属性
为了避免与可能在其他模式中处理的模板部分的交互(例如text
,HTML
模板内部的模式内联),Thymeleaf 3.0允许转义其文本语法中元素的属性。所以:
TEXT
模板模式中的属性将是HTML-unescaped。JAVASCRIPT
模板模式中的属性将是JavaScript-unescaped。CSS
模板模式中的属性将是CSS-unescaped。
所以这在TEXT
模式模板中完全可以(注意>
):
[# th:if="${120<user.age}"]
Congratulations!
[/]
当然,<
在真实的文本模板中没有任何意义,但如果我们使用th:inline="text"
包含上述代码的块处理HTML模板并且我们希望确保我们的浏览器不会将其<user.age
作为名称,那么这是一个好主意。静态打开文件作为原型时打开标记。
13.2可扩展性
这种语法的一个优点是它和标记一样可扩展。开发人员仍然可以使用自定义元素和属性定义自己的方言,为它们应用前缀(可选),然后在文本模板模式中使用它们:
[#myorg:dosomething myorg:importantattr="211"]some text[/myorg:dosomething]
13.3仅文本原型注释块:添加代码
在JAVASCRIPT
和CSS
模板模式(不适用于TEXT
),允许包括一个特殊的注释语法之间的代码/*[+...+]*/
,这样Thymeleaf会处理模板时自动取消注释这样的代码:
`var x = 23;
/*[+
var msg = "This is a working application";
+]*/
var f = function() {
...`
将被执行为:
`var x = 23;
var msg = "This is a working application";
var f = function() {
...`
您可以在这些注释中包含表达式,并将对它们进行评估:
`var x = 23;
/*[+
var msg = "Hello, " + [[${session.user.name}]];
+]*/
var f = function() {
...`
13.4文本解析器级注释块:删除代码
在类似于仅原型的注释块的方式,所有三个文本模板模式(TEXT
,JAVASCRIPT
和CSS
)使其能够指示Thymeleaf特殊之间移除代码/*[- */
和/* -]*/
标志,就像这样:
`var x = 23;
/*[- */
var msg = "This is shown only when executed statically!";
/* -]*/
var f = function() {
...`
或者这个,在TEXT
模式中:
...
/*[- Note the user is obtained from the session, which must exist -]*/
Welcome [(${session.user.name})]!
...
13.5自然JavaScript和CSS模板
如上一章所示,JavaScript和CSS内联提供了在JavaScript / CSS注释中包含内联表达式的可能性,例如:
`...
var username = /*[[${session.user.name}]]*/ "Sebastian Lychee";
...`
…这是有效的JavaScript,一旦执行可能看起来像:
`...
var username = "John Apricot";
...`
在注释中包含内联表达式的相同技巧实际上可以用于整个文本模式语法:
/*[# th:if="${user.admin}"]*/
alert('Welcome admin');
/*[/]*/
当模板静态打开时,将显示上面代码中的警报 - 因为它是100%有效的JavaScript - 以及当用户是管理员时模板运行时。它相当于:
[# th:if="${user.admin}"]
alert('Welcome admin');
[/]
…实际上是在模板解析期间转换初始版本的代码。
但请注意,注释中的包装元素不会;
像内联输出表达式那样清除它们所在的行(在找到之前的右侧)。该行为仅为内联输出表达式保留。
因此,Thymeleaf 3.0允许以自然模板的形式开发复杂的JavaScript脚本和CSS样式表,既可用作原型,也可用作工作模板。
14我们杂货店的更多页面
现在我们对使用Thymeleaf了解很多,我们可以在我们的网站上添加一些新页面来进行订单管理。
请注意,我们将重点关注HTML代码,但如果要查看相应的控制器,可以查看捆绑的源代码。
14.1订单清单
让我们从创建订单列表页面开始/WEB-INF/templates/order/list.html
:
`<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Good Thymes Virtual Grocery</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link rel="stylesheet" type="text/css" media="all"
href="../../../css/gtvg.css" th:href="@{/css/gtvg.css}" />
</head>
<body>
<h1>Order list</h1>
<table>
<tr>
<th>DATE</th>
<th>CUSTOMER</th>
<th>TOTAL</th>
<th></th>
</tr>
<tr th:each="o : ${orders}" th:class="${oStat.odd}? 'odd'">
<td th:text="${#calendars.format(o.date,'dd/MMM/yyyy')}">13 jan 2011</td>
<td th:text="${o.customer.name}">Frederic Tomato</td>
<td th:text="${#aggregates.sum(o.orderLines.{purchasePrice \* amount})}">23.32</td>
<td>
<a href="details.html" th:href="@{/order/details(orderId=${o.id})}">view</a>
</td>
</tr>
</table>
<p>
<a href="../home.html" th:href="@{/}">Return to home</a>
</p>
</body>
</html>`
这里没有什么可以让我们感到惊讶,除了这一点OGNL魔法:
`<td th:text="${#aggregates.sum(o.orderLines.{purchasePrice \* amount})}">23.32</td>`
这样做,对于顺序中的每个订单行(OrderLine
对象),将其purchasePrice
和amount
属性相乘(通过调用相应的getPurchasePrice()
和getAmount()
方法)并将结果返回到数字列表中,稍后由#aggregates.sum(...)
函数聚合以获得订单总数价钱。
你必须喜欢OGNL的力量。
14.2订单详情
现在,对于订单详细信息页面,我们将在其中大量使用星号语法:
`<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Good Thymes Virtual Grocery</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link rel="stylesheet" type="text/css" media="all"
href="../../../css/gtvg.css" th:href="@{/css/gtvg.css}" />
</head>
<body th:object="${order}">
<h1>Order details</h1>
<div>
<p><b>Code:</b> <span th:text="\*{id}">99</span></p>
<p>
<b>Date:</b>
<span th:text="\*{#calendars.format(date,'dd MMM yyyy')}">13 jan 2011</span>
</p>
</div>
<h2>Customer</h2>
<div th:object="\*{customer}">
<p><b>Name:</b> <span th:text="\*{name}">Frederic Tomato</span></p>
<p>
<b>Since:</b>
<span th:text="\*{#calendars.format(customerSince,'dd MMM yyyy')}">1 jan 2011</span>
</p>
</div>
<h2>Products</h2>
<table>
<tr>
<th>PRODUCT</th>
<th>AMOUNT</th>
<th>PURCHASE PRICE</th>
</tr>
<tr th:each="ol,row : \*{orderLines}" th:class="${row.odd}? 'odd'">
<td th:text="${ol.product.name}">Strawberries</td>
<td th:text="${ol.amount}" class="number">3</td>
<td th:text="${ol.purchasePrice}" class="number">23.32</td>
</tr>
</table>
<div>
<b>TOTAL:</b>
<span th:text="\*{#aggregates.sum(orderLines.{purchasePrice \* amount})}">35.23</span>
</div>
<p>
<a href="list.html" th:href="@{/order/list}">Return to order list</a>
</p>
</body>
</html>`
除了这个嵌套对象选择之外,这里没什么新东西:
`<body th:object="${order}">
...
<div th:object="\*{customer}">
<p><b>Name:</b> <span th:text="\*{name}">Frederic Tomato</span></p>
...
</div>
...
</body>`
…这*{name}
相当于:
`<p><b>Name:</b> <span th:text="${order.customer.name}">Frederic Tomato</span></p>`
15有关配置的更多信息
15.1模板解析器
对于Good Thymes Virtual Grocery,我们选择了一个ITemplateResolver
名为的实现ServletContextTemplateResolver
,它允许我们从Servlet Context获取模板作为资源。
除了让我们能够通过实现ITemplateResolver,
Thymeleaf 创建我们自己的模板解析器包括四个开箱即用的实现:
org.thymeleaf.templateresolver.ClassLoaderTemplateResolver
,将模板解析为类加载器资源,如:
`return Thread.currentThread().getContextClassLoader().getResourceAsStream(template);`
org.thymeleaf.templateresolver.FileTemplateResolver
,将模板解析为文件系统中的文件,如:
`return new FileInputStream(new File(template));`
org.thymeleaf.templateresolver.UrlTemplateResolver
,将模板解析为URL(甚至是非本地的),如:
`return (new URL(template)).openStream();`
org.thymeleaf.templateresolver.StringTemplateResolver
,它可以直接解析模板,因为它String
被指定为template
(或模板名称,在这种情况下显然不仅仅是一个名称):
`return new StringReader(templateName);`
所有预绑定的实现都ITemplateResolver
允许相同的配置参数集,包括:
- 前缀和后缀(已经看到):
`templateResolver.setPrefix("/WEB-INF/templates/");
templateResolver.setSuffix(".html");`
- 允许使用与文件名不直接对应的模板名称的模板别名。如果同时存在后缀/前缀和别名,则将在前缀/后缀之前应用别名:
`templateResolver.addTemplateAlias("adminHome","profiles/admin/home");
templateResolver.setTemplateAliases(aliasesMap);`
- 读取模板时要应用的编码:
`templateResolver.setEncoding("UTF-8");`
- 要使用的模板模式:
`// Default is HTML
templateResolver.setTemplateMode("XML");`
- 模板缓存的默认模式,以及用于定义特定模板是否可缓存的模式:
`// Default is true
templateResolver.setCacheable(false);
templateResolver.getCacheablePatternSpec().addPattern("/users/\*");`
- 解析模板缓存条目的TTL(以毫秒为单位)源自此模板解析程序。如果未设置,从缓存中删除条目的唯一方法是超过缓存最大大小(将删除最旧的条目)。
`// Default is no TTL (only cache size exceeded would remove entries)
templateResolver.setCacheTTLMs(60000L);`
Thymeleaf + Spring集成包提供了一个
SpringResourceTemplateResolver
实现,它使用所有Spring基础结构来访问和读取应用程序中的资源,这是在启用Spring的应用程序中推荐的实现。
链接模板解析器
此外,模板引擎可以指定多个模板解析器,在这种情况下,可以在它们之间建立用于模板解析的顺序,这样,如果第一个无法解析模板,则会询问第二个,依此类推:
`ClassLoaderTemplateResolver classLoaderTemplateResolver = new ClassLoaderTemplateResolver();
classLoaderTemplateResolver.setOrder(Integer.valueOf(1));
ServletContextTemplateResolver servletContextTemplateResolver =
new ServletContextTemplateResolver(servletContext);
servletContextTemplateResolver.setOrder(Integer.valueOf(2));
templateEngine.addTemplateResolver(classLoaderTemplateResolver);
templateEngine.addTemplateResolver(servletContextTemplateResolver);`
当应用多个模板解析器时,建议为每个模板解析器指定模式,以便Thymeleaf可以快速丢弃那些不打算解析模板的模板解析器,从而提高性能。这样做不是必要条件,而是建议:
`ClassLoaderTemplateResolver classLoaderTemplateResolver = new ClassLoaderTemplateResolver();
classLoaderTemplateResolver.setOrder(Integer.valueOf(1));
// This classloader will not be even asked for any templates not matching these patterns
classLoaderTemplateResolver.getResolvablePatternSpec().addPattern("/layout/\*.html");
classLoaderTemplateResolver.getResolvablePatternSpec().addPattern("/menu/\*.html");
ServletContextTemplateResolver servletContextTemplateResolver =
new ServletContextTemplateResolver(servletContext);
servletContextTemplateResolver.setOrder(Integer.valueOf(2));`
如果未指定这些可解析的模式,我们将依赖于ITemplateResolver
我们正在使用的每个实现的特定功能。请注意,并非所有实现都可以在解析之前确定模板的存在,因此可以始终将模板视为可解析并破坏解析链(不允许其他解析器检查相同的模板),但随后无法阅读真实的资源。
ITemplateResolver
核心Thymeleaf中包含的所有实现都包含一种机制,允许我们在解析可解析之前让解析器真正检查资源是否存在。这是旗帜,其作用如下:checkExistence
`ClassLoaderTemplateResolver classLoaderTemplateResolver = new ClassLoaderTemplateResolver();
classLoaderTemplateResolver.setOrder(Integer.valueOf(1));
classLoaderTempalteResolver.setCheckExistence(true);`
此checkExistence
标志强制解析器在解析阶段执行资源存在的实际检查(如果存在检查返回false,则调用链中的后续解析器)。虽然这在每种情况下听起来都不错,但在大多数情况下,这意味着对资源本身的双重访问(一次用于检查存在,另一次用于读取它),并且在某些情况下可能是性能问题,例如基于远程URL模板资源 - 一个潜在的性能问题,无论如何都可以通过使用模板缓存来大大减轻(在这种情况下,模板只会在第一次访问时解析)。
15.2消息解析器
我们没有为Grocery应用程序明确指定Message Resolver实现,正如之前所解释的那样,这意味着所使用的实现是一个org.thymeleaf.messageresolver.StandardMessageResolver
对象。
StandardMessageResolver
是IMessageResolver
接口的标准实现,但我们可以根据需要创建自己的,以适应我们的应用程序的特定需求。
Thymeleaf + Spring集成包默认提供了一个
IMessageResolver
实现,它使用标准的Spring方法检索外部化消息,使用MessageSource
在Spring Application Context中声明的bean。
标准消息解析器
那么如何StandardMessageResolver
查找特定模板中请求的消息?
如果模板名称是,home
并且它位于/WEB-INF/templates/home.html
,并且请求的区域设置是,gl_ES
则此解析程序将按以下顺序查找以下文件中的消息:
/WEB-INF/templates/home_gl_ES.properties
/WEB-INF/templates/home_gl.properties
/WEB-INF/templates/home.properties
StandardMessageResolver
有关完整消息解析机制如何工作的更多详细信息,请参阅该类的JavaDoc文档。
配置邮件解析器
如果我们想要向模板引擎添加消息解析器(或更多),该怎么办?简单:
`// For setting only one
templateEngine.setMessageResolver(messageResolver);
// For setting more than one
templateEngine.addMessageResolver(messageResolver);`
为什么我们想拥有多个消息解析器?出于与模板解析器相同的原因:订购消息解析器,如果第一个消息解析器无法解析特定消息,则会询问第二个,然后是第三个,等等。
15.3转换服务
使我们能够通过双括号语法()执行数据转换和格式化操作的转换服务实际上是标准方言的一个特征,而不是Thymeleaf模板引擎本身。${{...}}
因此,配置它的方法是将IStandardConversionService
接口的自定义实现直接设置到StandardDialect
正在配置到模板引擎中的实例中。喜欢:
`IStandardConversionService customConversionService = ...
StandardDialect dialect = new StandardDialect();
dialect.setConversionService(customConversionService);
templateEngine.setDialect(dialect);`
请注意,thymeleaf-spring3和thymeleaf-spring4软件包包含
SpringStandardDialect
,并且此方言已预先配置了一个实现IStandardConversionService
,将Spring自己的转换服务基础结构集成到Thymeleaf中。
15.4记录
Thymeleaf非常关注日志记录,并始终尝试通过其日志记录界面提供最大量的有用信息。
使用的日志库slf4j,
实际上充当了我们可能希望在我们的应用程序中使用的任何日志记录实现的桥梁(例如,log4j
)。
Thymeleaf班会记录TRACE
,DEBUG
并INFO
-level信息,这取决于我们希望的详细程度,并且除了一般的记录它会使用与TemplateEngine类,我们可以为不同的目的而单独配置相关的三个特殊记录器:
org.thymeleaf.TemplateEngine.CONFIG
将在初始化期间输出库的详细配置。org.thymeleaf.TemplateEngine.TIMER
将输出有关处理每个模板所用时间的信息(对基准测试很有用!)org.thymeleaf.TemplateEngine.cache
是一组记录器的前缀,用于输出有关高速缓存的特定信息。虽然缓存记录器的名称可由用户配置,因此可能会更改,但默认情况下它们是:org.thymeleaf.TemplateEngine.cache.TEMPLATE_CACHE
org.thymeleaf.TemplateEngine.cache.EXPRESSION_CACHE
使用Thymeleaf的日志记录基础结构的示例配置log4j
可以是:
log4j.logger.org.thymeleaf=DEBUG
log4j.logger.org.thymeleaf.TemplateEngine.CONFIG=TRACE
log4j.logger.org.thymeleaf.TemplateEngine.TIMER=TRACE
log4j.logger.org.thymeleaf.TemplateEngine.cache.TEMPLATE_CACHE=TRACE
16模板缓存
Thymeleaf的工作得益于一组解析器 - 用于标记和文本 - 将模板解析为事件序列(开放标记,文本,关闭标记,注释等)和一系列处理器 - 每种类型的行为都需要一个应用 - 修改模板解析的事件序列,以便通过将原始模板与我们的数据相结合来创建我们期望的结果。
它还包括 - 默认情况下 - 一个存储已解析模板的缓存; 在处理模板文件之前读取和解析模板文件所产生的事件序列。这在Web应用程序中工作时特别有用,并基于以下概念构建:
- 输入/输出几乎总是任何应用程序中最慢的部分。相比之下,内存处理非常快。
- 克隆现有的内存中事件序列总是比读取模板文件,解析模板文件并为其创建新的事件序列快得多。
- Web应用程序通常只有几十个模板。
- 模板文件是中小型的,并且在应用程序运行时不会修改它们。
这一切都导致了这样的想法:在不浪费大量内存的情况下缓存Web应用程序中最常用的模板是可行的,并且它还将节省大量时间,这些时间将花费在一小组文件上的输入/输出操作上事实上,这永远不会改变。
我们如何控制这个缓存?首先,我们之前已经了解到,我们可以在模板解析器中启用或禁用它,甚至只对特定模板执行操作:
`// Default is true
templateResolver.setCacheable(false);
templateResolver.getCacheablePatternSpec().addPattern("/users/\*");`
此外,我们可以通过建立自己的缓存管理器对象来修改其配置,该对象可以是默认StandardCacheManager
实现的实例:
`// Default is 200
StandardCacheManager cacheManager = new StandardCacheManager();
cacheManager.setTemplateCacheMaxSize(100);
...
templateEngine.setCacheManager(cacheManager);`
org.thymeleaf.cache.StandardCacheManager
有关配置缓存的更多信息,请参阅javadoc API 。
可以从模板缓存中手动删除条目:
`// Clear the cache completely
templateEngine.clearTemplateCache();
// Clear a specific template from the cache
templateEngine.clearTemplateCacheFor("/users/userList");`
17解耦模板逻辑
17.1解耦逻辑:概念
到目前为止,我们已经为我们的Grocery Store工作,模板以通常的方式完成,逻辑以属性的形式插入到我们的模板中。
但Thymeleaf也让我们彻底脱钩从逻辑模板标记,允许创建完全逻辑较少标记模板在HTML
和XML
模板模式。
主要思想是模板逻辑将在单独的逻辑文件中定义(更确切地说是逻辑资源,因为它不需要是文件)。默认情况下,该逻辑资源将是与模板文件位于同一位置(例如文件夹)的附加文件,具有相同的名称但具有.th.xml
扩展名:
/templates
+->/home.html
+->/home.th.xml
因此该home.html
文件可以完全无逻辑。它可能看起来像这样:
`<!DOCTYPE html>
<html>
<body>
<table id="usersTable">
<tr>
<td class="username">Jeremy Grapefruit</td>
<td class="usertype">Normal User</td>
</tr>
<tr>
<td class="username">Alice Watermelon</td>
<td class="usertype">Administrator</td>
</tr>
</table>
</body>
</html>`
绝对没有Thymeleaf代码。这是一个模板文件,没有Thymeleaf或模板知识的设计师可以创建,编辑和/或理解。或者由某些外部系统提供的HTML片段,根本没有Thymeleaf挂钩。
现在让我们home.html
通过创建我们的附加home.th.xml
文件将该模板转换为Thymeleaf模板:
<?xml version="1.0"?>
<thlogic>
<attr sel="#usersTable" th:remove="all-but-first">
<attr sel="/tr[0]" th:each="user : ${users}">
<attr sel="td.username" th:text="${user.name}" />
<attr sel="td.usertype" th:text="#{|user.type.${user.type}|}" />
</attr>
</attr>
</thlogic>
在这里,我们可以<attr>
在thlogic
块内看到很多标签。这些<attr>
标签通过其属性选择在原始模板的节点上执行属性注入,这些sel
属性包含Thymeleaf 标记选择器(实际上是AttoParser标记选择器)。
另请注意,<attr>
可以嵌套标记,以便附加其选择器。即sel="/tr[0]"
上述中,例如,将被处理为sel="#usersTable/tr[0]"
。并且用户名的选择器<td>
将被处理为sel="#usersTable/tr[0]//td.username"
。
所以一旦合并,上面看到的两个文件都将是:
`<!DOCTYPE html>
<html>
<body>
<table id="usersTable" th:remove="all-but-first">
<tr th:each="user : ${users}">
<td class="username" th:text="${user.name}">Jeremy Grapefruit</td>
<td class="usertype" th:text="#{|user.type.${user.type}|}">Normal User</td>
</tr>
<tr>
<td class="username">Alice Watermelon</td>
<td class="usertype">Administrator</td>
</tr>
</table>
</body>
</html>`
这看起来更熟悉,并且确实比创建两个单独的文件更简洁。但是,解耦模板的优势在于我们可以为我们的模板提供完全独立于Thymeleaf的独立性,因此从设计角度来看,它具有更好的可维护性。
当然,仍然需要设计人员或开发人员之间的一些合同 - 例如用户<table>
需要的合同id="usersTable"
- 但在许多情况下,纯HTML模板将是设计和开发团队之间更好的通信工件。
17.2配置解耦模板
启用解耦模板
默认情况下,每个模板都不会出现解耦逻辑。相反,配置的模板解析器(实现ITemplateResolver
)需要使用解耦逻辑专门标记它们解析的模板。
除了StringTemplateResolver
(不允许解耦逻辑)之外,所有其他开箱即用的实现都ITemplateResolver
将提供一个标记useDecoupledLogic
,该标记将标记由该解析器解析的所有模板,因为它可能将其全部或部分逻辑生活在单独的资源中:
`final ServletContextTemplateResolver templateResolver =
new ServletContextTemplateResolver(servletContext);
...
templateResolver.setUseDecoupledLogic(true);`
混合耦合和解耦逻辑
启用时,解耦模板逻辑不是必需的。启用后,这意味着引擎将查找包含解耦逻辑的资源,解析并将其与原始模板(如果存在)合并。如果解耦逻辑资源不存在,则不会引发错误。
此外,在同一模板中,我们可以混合耦合和解耦逻辑,例如通过在原始模板文件中添加一些Thymeleaf属性,但将其他属性留给单独的解耦逻辑文件。最常见的情况是使用new(in v3.0)th:ref
属性。
17.3 th:ref属性
th:ref
只是一个标记属性。它从处理的角度来看并没有做任何事情,只是在处理模板时就消失了,但它的用处在于它充当标记引用,即它可以通过名称从标记选择器中解析,就像标记名称或片段一样(th:fragment
)。
所以,如果我们有一个选择器,如:
<attr sel="whatever" .../>
这将匹配:
- 任何
<whatever>
标签。 - 任何带有
th:fragment="whatever"
属性的标签。 - 任何带有
th:ref="whatever"
属性的标签。
th:ref
例如,使用纯HTML id
属性的优点是什么?仅仅是事实,我们可能不希望添加这么多id
和class
属性,我们的标记作为逻辑锚,这最终可能会污染我们的产量。
从同样的意义上讲,有什么缺点th:ref
?好吧,显然我们会在模板中添加一些Thymeleaf逻辑(“逻辑”)。
请注意,该th:ref
属性的适用性不仅适用于解耦的逻辑模板文件:它在其他类型的场景中也是如此,例如片段表达式(~{...}
)。
17.4解耦模板的性能影响
影响非常小。当已解析的模板被标记为使用解耦逻辑并且未缓存时,模板逻辑资源将首先被解析,解析并处理成内存中指令的序列:基本上是要注入每个标记选择器的属性列表。
但这是唯一需要的额外步骤,因为在此之后,真正的模板将被解析,并且在解析时,这些属性将由解析器本身即时注入,这得益于AttoParser中节点选择的高级功能。因此,解析后的节点将从解析器中出来,就好像它们将注入的属性写入原始模板文件中一样。
这个的最大优点是什么?将模板配置为高速缓存时,它将被缓存,其中包含已注入的属性。因此,一旦缓存模板的缓存模板使用解耦模板的开销将绝对为零。
17.5解耦逻辑的分辨率
Thymeleaf解析对应于每个模板的解耦逻辑资源的方式可由用户配置。它由扩展点确定org.thymeleaf.templateparser.markup.decoupled.IDecoupledTemplateLogicResolver
,为其提供默认实现:StandardDecoupledTemplateLogicResolver
。
这个标准实现有什么作用?
- 首先,它将a
prefix
和a 应用于模板资源suffix
的基本名称(通过其ITemplateResource#getBaseName()
方法获得)。前缀和后缀都可以配置,默认情况下,前缀为空,后缀为.th.xml
。 - 其次,它要求模板资源通过其方法解析具有计算名称的相对资源
ITemplateResource#relative(String relativeLocation)
。
IDecoupledTemplateLogicResolver
可以TemplateEngine
轻松配置要使用的具体实现:
`final StandardDecoupledTemplateLogicResolver decoupledresolver =
new StandardDecoupledTemplateLogicResolver();
decoupledResolver.setPrefix("../viewlogic/");
...
templateEngine.setDecoupledTemplateLogicResolver(decoupledResolver);`
18附录A:表达式基本对象
始终可以调用某些对象和变量映射。我们来看看他们:
基础对象
- #ctx:上下文对象。实施
org.thymeleaf.context.IContext
或org.thymeleaf.context.IWebContext
依赖于我们的环境(独立或Web)。
注意#vars
并且#root
是同一对象的同义词,但#ctx
建议使用。
`/\*
\* ======================================================================
\* See javadoc API for class org.thymeleaf.context.IContext
\* ======================================================================
\*/
${#ctx.locale}
${#ctx.variableNames}
/\*
\* ======================================================================
\* See javadoc API for class org.thymeleaf.context.IWebContext
\* ======================================================================
\*/
${#ctx.request}
${#ctx.response}
${#ctx.session}
${#ctx.servletContext}`
- #locale:直接访问
java.util.Locale
与当前请求关联的。
`${#locale}`
请求/会话属性的Web上下文命名空间等。
在Web环境中使用Thymeleaf时,我们可以使用一系列快捷方式来访问请求参数,会话属性和应用程序属性:
请注意,这些不是上下文对象,而是作为变量添加到上下文中的映射,因此我们不使用它们
#
。在某种程度上,它们充当命名空间。
- param:用于检索请求参数。
${param.foo}
是一个String[]
带有foo
请求参数值的,因此${param.foo[0]}
通常用于获取第一个值。
`/\*
\* ============================================================================
\* See javadoc API for class org.thymeleaf.context.WebRequestParamsVariablesMap
\* ============================================================================
\*/
${param.foo} // Retrieves a String[] with the values of request parameter 'foo'
${param.size()}
${param.isEmpty()}
${param.containsKey('foo')}
...`
- session:用于检索会话属性。
`/\*
\* ======================================================================
\* See javadoc API for class org.thymeleaf.context.WebSessionVariablesMap
\* ======================================================================
\*/
${session.foo} // Retrieves the session atttribute 'foo'
${session.size()}
${session.isEmpty()}
${session.containsKey('foo')}
...`
- application:用于检索应用程序/ servlet上下文属性。
`/\*
\* =============================================================================
\* See javadoc API for class org.thymeleaf.context.WebServletContextVariablesMap
\* =============================================================================
\*/
${application.foo} // Retrieves the ServletContext atttribute 'foo'
${application.size()}
${application.isEmpty()}
${application.containsKey('foo')}
...`
请注意,无需为访问请求属性(与请求参数相对)**指定名称空间,**因为所有请求属性都会自动作为上下文根中的变量添加到上下文中:
`${myRequestAttribute}`
Web上下文对象
在Web环境中,还可以直接访问以下对象(请注意这些是对象,而不是映射/命名空间):
- #request:直接访问
javax.servlet.http.HttpServletRequest
与当前请求关联的对象。
`${#request.getAttribute('foo')}
${#request.getParameter('foo')}
${#request.getContextPath()}
${#request.getRequestName()}
...`
- #session:直接访问
javax.servlet.http.HttpSession
与当前请求关联的对象。
`${#session.getAttribute('foo')}
${#session.id}
${#session.lastAccessedTime}
...`
- #servletContext:直接访问
javax.servlet.ServletContext
与当前请求关联的对象。
`${#servletContext.getAttribute('foo')}
${#servletContext.contextPath}
...`
19附录B:表达式实用程序对象
执行信息
- #execInfo:表达式对象,提供有关在Thymeleaf标准表达式中处理的模板的有用信息。
`/\*
\* ======================================================================
\* See javadoc API for class org.thymeleaf.expression.ExecutionInfo
\* ======================================================================
\*/
/\*
\* Return the name and mode of the 'leaf' template. This means the template
\* from where the events being processed were parsed. So if this piece of
\* code is not in the root template "A" but on a fragment being inserted
\* into "A" from another template called "B", this will return "B" as a
\* name, and B's mode as template mode.
\*/
${#execInfo.templateName}
${#execInfo.templateMode}
/\*
\* Return the name and mode of the 'root' template. This means the template
\* that the template engine was originally asked to process. So if this
\* piece of code is not in the root template "A" but on a fragment being
\* inserted into "A" from another template called "B", this will still
\* return "A" and A's template mode.
\*/
${#execInfo.processedTemplateName}
${#execInfo.processedTemplateMode}
/\*
\* Return the stacks (actually, List<String> or List<TemplateMode>) of
\* templates being processed. The first element will be the
\* 'processedTemplate' (the root one), the last one will be the 'leaf'
\* template, and in the middle all the fragments inserted in nested
\* manner to reach the leaf from the root will appear.
\*/
${#execInfo.templateNames}
${#execInfo.templateModes}
/\*
\* Return the stack of templates being processed similarly (and in the
\* same order) to 'templateNames' and 'templateModes', but returning
\* a List<TemplateData> with the full template metadata.
\*/
${#execInfo.templateStack}`
消息
- #messages:用于在变量表达式中获取外部化消息的实用程序方法,与使用
#{...}
语法获取它们的方式相同。
`/\*
\* ======================================================================
\* See javadoc API for class org.thymeleaf.expression.Messages
\* ======================================================================
\*/
/\*
\* Obtain externalized messages. Can receive a single key, a key plus arguments,
\* or an array/list/set of keys (in which case it will return an array/list/set of
\* externalized messages).
\* If a message is not found, a default message (like '??msgKey??') is returned.
\*/
${#messages.msg('msgKey')}
${#messages.msg('msgKey', param1)}
${#messages.msg('msgKey', param1, param2)}
${#messages.msg('msgKey', param1, param2, param3)}
${#messages.msgWithParams('msgKey', new Object[] {param1, param2, param3, param4})}
${#messages.arrayMsg(messageKeyArray)}
${#messages.listMsg(messageKeyList)}
${#messages.setMsg(messageKeySet)}
/\*
\* Obtain externalized messages or null. Null is returned instead of a default
\* message if a message for the specified key is not found.
\*/
${#messages.msgOrNull('msgKey')}
${#messages.msgOrNull('msgKey', param1)}
${#messages.msgOrNull('msgKey', param1, param2)}
${#messages.msgOrNull('msgKey', param1, param2, param3)}
${#messages.msgOrNullWithParams('msgKey', new Object[] {param1, param2, param3, param4})}
${#messages.arrayMsgOrNull(messageKeyArray)}
${#messages.listMsgOrNull(messageKeyList)}
${#messages.setMsgOrNull(messageKeySet)}`
的URI /网址
- #uris:用于在Thymeleaf标准表达式中执行URI / URL操作(尤其是转义/转义)的实用程序对象。
`/\*
\* ======================================================================
\* See javadoc API for class org.thymeleaf.expression.Uris
\* ======================================================================
\*/
/\*
\* Escape/Unescape as a URI/URL path
\*/
${#uris.escapePath(uri)}
${#uris.escapePath(uri, encoding)}
${#uris.unescapePath(uri)}
${#uris.unescapePath(uri, encoding)}
/\*
\* Escape/Unescape as a URI/URL path segment (between '/' symbols)
\*/
${#uris.escapePathSegment(uri)}
${#uris.escapePathSegment(uri, encoding)}
${#uris.unescapePathSegment(uri)}
${#uris.unescapePathSegment(uri, encoding)}
/\*
\* Escape/Unescape as a Fragment Identifier (#frag)
\*/
${#uris.escapeFragmentId(uri)}
${#uris.escapeFragmentId(uri, encoding)}
${#uris.unescapeFragmentId(uri)}
${#uris.unescapeFragmentId(uri, encoding)}
/\*
\* Escape/Unescape as a Query Parameter (?var=value)
\*/
${#uris.escapeQueryParam(uri)}
${#uris.escapeQueryParam(uri, encoding)}
${#uris.unescapeQueryParam(uri)}
${#uris.unescapeQueryParam(uri, encoding)}`
转换
- #conversions:允许在模板的任何位置执行转换服务的实用程序对象:
`/\*
\* ======================================================================
\* See javadoc API for class org.thymeleaf.expression.Conversions
\* ======================================================================
\*/
/\*
\* Execute the desired conversion of the 'object' value into the
\* specified class.
\*/
${#conversions.convert(object, 'java.util.TimeZone')}
${#conversions.convert(object, targetClass)}`
日期
- #dates:
java.util.Date
对象的实用方法:
`/\*
\* ======================================================================
\* See javadoc API for class org.thymeleaf.expression.Dates
\* ======================================================================
\*/
/\*
\* Format date with the standard locale format
\* Also works with arrays, lists or sets
\*/
${#dates.format(date)}
${#dates.arrayFormat(datesArray)}
${#dates.listFormat(datesList)}
${#dates.setFormat(datesSet)}
/\*
\* Format date with the ISO8601 format
\* Also works with arrays, lists or sets
\*/
${#dates.formatISO(date)}
${#dates.arrayFormatISO(datesArray)}
${#dates.listFormatISO(datesList)}
${#dates.setFormatISO(datesSet)}
/\*
\* Format date with the specified pattern
\* Also works with arrays, lists or sets
\*/
${#dates.format(date, 'dd/MMM/yyyy HH:mm')}
${#dates.arrayFormat(datesArray, 'dd/MMM/yyyy HH:mm')}
${#dates.listFormat(datesList, 'dd/MMM/yyyy HH:mm')}
${#dates.setFormat(datesSet, 'dd/MMM/yyyy HH:mm')}
/\*
\* Obtain date properties
\* Also works with arrays, lists or sets
\*/
${#dates.day(date)} // also arrayDay(...), listDay(...), etc.
${#dates.month(date)} // also arrayMonth(...), listMonth(...), etc.
${#dates.monthName(date)} // also arrayMonthName(...), listMonthName(...), etc.
${#dates.monthNameShort(date)} // also arrayMonthNameShort(...), listMonthNameShort(...), etc.
${#dates.year(date)} // also arrayYear(...), listYear(...), etc.
${#dates.dayOfWeek(date)} // also arrayDayOfWeek(...), listDayOfWeek(...), etc.
${#dates.dayOfWeekName(date)} // also arrayDayOfWeekName(...), listDayOfWeekName(...), etc.
${#dates.dayOfWeekNameShort(date)} // also arrayDayOfWeekNameShort(...), listDayOfWeekNameShort(...), etc.
${#dates.hour(date)} // also arrayHour(...), listHour(...), etc.
${#dates.minute(date)} // also arrayMinute(...), listMinute(...), etc.
${#dates.second(date)} // also arraySecond(...), listSecond(...), etc.
${#dates.millisecond(date)} // also arrayMillisecond(...), listMillisecond(...), etc.
/\*
\* Create date (java.util.Date) objects from its components
\*/
${#dates.create(year,month,day)}
${#dates.create(year,month,day,hour,minute)}
${#dates.create(year,month,day,hour,minute,second)}
${#dates.create(year,month,day,hour,minute,second,millisecond)}
/\*
\* Create a date (java.util.Date) object for the current date and time
\*/
${#dates.createNow()}
${#dates.createNowForTimeZone()}
/\*
\* Create a date (java.util.Date) object for the current date (time set to 00:00)
\*/
${#dates.createToday()}
${#dates.createTodayForTimeZone()}`
日历
- #calendars:类似于
#dates
,但对于java.util.Calendar
对象:
`/\*
\* ======================================================================
\* See javadoc API for class org.thymeleaf.expression.Calendars
\* ======================================================================
\*/
/\*
\* Format calendar with the standard locale format
\* Also works with arrays, lists or sets
\*/
${#calendars.format(cal)}
${#calendars.arrayFormat(calArray)}
${#calendars.listFormat(calList)}
${#calendars.setFormat(calSet)}
/\*
\* Format calendar with the ISO8601 format
\* Also works with arrays, lists or sets
\*/
${#calendars.formatISO(cal)}
${#calendars.arrayFormatISO(calArray)}
${#calendars.listFormatISO(calList)}
${#calendars.setFormatISO(calSet)}
/\*
\* Format calendar with the specified pattern
\* Also works with arrays, lists or sets
\*/
${#calendars.format(cal, 'dd/MMM/yyyy HH:mm')}
${#calendars.arrayFormat(calArray, 'dd/MMM/yyyy HH:mm')}
${#calendars.listFormat(calList, 'dd/MMM/yyyy HH:mm')}
${#calendars.setFormat(calSet, 'dd/MMM/yyyy HH:mm')}
/\*
\* Obtain calendar properties
\* Also works with arrays, lists or sets
\*/
${#calendars.day(date)} // also arrayDay(...), listDay(...), etc.
${#calendars.month(date)} // also arrayMonth(...), listMonth(...), etc.
${#calendars.monthName(date)} // also arrayMonthName(...), listMonthName(...), etc.
${#calendars.monthNameShort(date)} // also arrayMonthNameShort(...), listMonthNameShort(...), etc.
${#calendars.year(date)} // also arrayYear(...), listYear(...), etc.
${#calendars.dayOfWeek(date)} // also arrayDayOfWeek(...), listDayOfWeek(...), etc.
${#calendars.dayOfWeekName(date)} // also arrayDayOfWeekName(...), listDayOfWeekName(...), etc.
${#calendars.dayOfWeekNameShort(date)} // also arrayDayOfWeekNameShort(...), listDayOfWeekNameShort(...), etc.
${#calendars.hour(date)} // also arrayHour(...), listHour(...), etc.
${#calendars.minute(date)} // also arrayMinute(...), listMinute(...), etc.
${#calendars.second(date)} // also arraySecond(...), listSecond(...), etc.
${#calendars.millisecond(date)} // also arrayMillisecond(...), listMillisecond(...), etc.
/\*
\* Create calendar (java.util.Calendar) objects from its components
\*/
${#calendars.create(year,month,day)}
${#calendars.create(year,month,day,hour,minute)}
${#calendars.create(year,month,day,hour,minute,second)}
${#calendars.create(year,month,day,hour,minute,second,millisecond)}
${#calendars.createForTimeZone(year,month,day,timeZone)}
${#calendars.createForTimeZone(year,month,day,hour,minute,timeZone)}
${#calendars.createForTimeZone(year,month,day,hour,minute,second,timeZone)}
${#calendars.createForTimeZone(year,month,day,hour,minute,second,millisecond,timeZone)}
/\*
\* Create a calendar (java.util.Calendar) object for the current date and time
\*/
${#calendars.createNow()}
${#calendars.createNowForTimeZone()}
/\*
\* Create a calendar (java.util.Calendar) object for the current date (time set to 00:00)
\*/
${#calendars.createToday()}
${#calendars.createTodayForTimeZone()}`
数字
- #numbers:数字对象的实用方法:
`/\*
\* ======================================================================
\* See javadoc API for class org.thymeleaf.expression.Numbers
\* ======================================================================
\*/
/\*
\* ==========================
\* Formatting integer numbers
\* ==========================
\*/
/\*
\* Set minimum integer digits.
\* Also works with arrays, lists or sets
\*/
${#numbers.formatInteger(num,3)}
${#numbers.arrayFormatInteger(numArray,3)}
${#numbers.listFormatInteger(numList,3)}
${#numbers.setFormatInteger(numSet,3)}
/\*
\* Set minimum integer digits and thousands separator:
\* 'POINT', 'COMMA', 'WHITESPACE', 'NONE' or 'DEFAULT' (by locale).
\* Also works with arrays, lists or sets
\*/
${#numbers.formatInteger(num,3,'POINT')}
${#numbers.arrayFormatInteger(numArray,3,'POINT')}
${#numbers.listFormatInteger(numList,3,'POINT')}
${#numbers.setFormatInteger(numSet,3,'POINT')}
/\*
\* ==========================
\* Formatting decimal numbers
\* ==========================
\*/
/\*
\* Set minimum integer digits and (exact) decimal digits.
\* Also works with arrays, lists or sets
\*/
${#numbers.formatDecimal(num,3,2)}
${#numbers.arrayFormatDecimal(numArray,3,2)}
${#numbers.listFormatDecimal(numList,3,2)}
${#numbers.setFormatDecimal(numSet,3,2)}
/\*
\* Set minimum integer digits and (exact) decimal digits, and also decimal separator.
\* Also works with arrays, lists or sets
\*/
${#numbers.formatDecimal(num,3,2,'COMMA')}
${#numbers.arrayFormatDecimal(numArray,3,2,'COMMA')}
${#numbers.listFormatDecimal(numList,3,2,'COMMA')}
${#numbers.setFormatDecimal(numSet,3,2,'COMMA')}
/\*
\* Set minimum integer digits and (exact) decimal digits, and also thousands and
\* decimal separator.
\* Also works with arrays, lists or sets
\*/
${#numbers.formatDecimal(num,3,'POINT',2,'COMMA')}
${#numbers.arrayFormatDecimal(numArray,3,'POINT',2,'COMMA')}
${#numbers.listFormatDecimal(numList,3,'POINT',2,'COMMA')}
${#numbers.setFormatDecimal(numSet,3,'POINT',2,'COMMA')}
/\*
\* =====================
\* Formatting currencies
\* =====================
\*/
${#numbers.formatCurrency(num)}
${#numbers.arrayFormatCurrency(numArray)}
${#numbers.listFormatCurrency(numList)}
${#numbers.setFormatCurrency(numSet)}
/\*
\* ======================
\* Formatting percentages
\* ======================
\*/
${#numbers.formatPercent(num)}
${#numbers.arrayFormatPercent(numArray)}
${#numbers.listFormatPercent(numList)}
${#numbers.setFormatPercent(numSet)}
/\*
\* Set minimum integer digits and (exact) decimal digits.
\*/
${#numbers.formatPercent(num, 3, 2)}
${#numbers.arrayFormatPercent(numArray, 3, 2)}
${#numbers.listFormatPercent(numList, 3, 2)}
${#numbers.setFormatPercent(numSet, 3, 2)}
/\*
\* ===============
\* Utility methods
\* ===============
\*/
/\*
\* Create a sequence (array) of integer numbers going
\* from x to y
\*/
${#numbers.sequence(from,to)}
${#numbers.sequence(from,to,step)}`
字符串
- #strings:
String
对象的实用方法:
`/\*
\* ======================================================================
\* See javadoc API for class org.thymeleaf.expression.Strings
\* ======================================================================
\*/
/\*
\* Null-safe toString()
\*/
${#strings.toString(obj)} // also array\*, list\* and set\*
/\*
\* Check whether a String is empty (or null). Performs a trim() operation before check
\* Also works with arrays, lists or sets
\*/
${#strings.isEmpty(name)}
${#strings.arrayIsEmpty(nameArr)}
${#strings.listIsEmpty(nameList)}
${#strings.setIsEmpty(nameSet)}
/\*
\* Perform an 'isEmpty()' check on a string and return it if false, defaulting to
\* another specified string if true.
\* Also works with arrays, lists or sets
\*/
${#strings.defaultString(text,default)}
${#strings.arrayDefaultString(textArr,default)}
${#strings.listDefaultString(textList,default)}
${#strings.setDefaultString(textSet,default)}
/\*
\* Check whether a fragment is contained in a String
\* Also works with arrays, lists or sets
\*/
${#strings.contains(name,'ez')} // also array\*, list\* and set\*
${#strings.containsIgnoreCase(name,'ez')} // also array\*, list\* and set\*
/\*
\* Check whether a String starts or ends with a fragment
\* Also works with arrays, lists or sets
\*/
${#strings.startsWith(name,'Don')} // also array\*, list\* and set\*
${#strings.endsWith(name,endingFragment)} // also array\*, list\* and set\*
/\*
\* Substring-related operations
\* Also works with arrays, lists or sets
\*/
${#strings.indexOf(name,frag)} // also array\*, list\* and set\*
${#strings.substring(name,3,5)} // also array\*, list\* and set\*
${#strings.substringAfter(name,prefix)} // also array\*, list\* and set\*
${#strings.substringBefore(name,suffix)} // also array\*, list\* and set\*
${#strings.replace(name,'las','ler')} // also array\*, list\* and set\*
/\*
\* Append and prepend
\* Also works with arrays, lists or sets
\*/
${#strings.prepend(str,prefix)} // also array\*, list\* and set\*
${#strings.append(str,suffix)} // also array\*, list\* and set\*
/\*
\* Change case
\* Also works with arrays, lists or sets
\*/
${#strings.toUpperCase(name)} // also array\*, list\* and set\*
${#strings.toLowerCase(name)} // also array\*, list\* and set\*
/\*
\* Split and join
\*/
${#strings.arrayJoin(namesArray,',')}
${#strings.listJoin(namesList,',')}
${#strings.setJoin(namesSet,',')}
${#strings.arraySplit(namesStr,',')} // returns String[]
${#strings.listSplit(namesStr,',')} // returns List<String>
${#strings.setSplit(namesStr,',')} // returns Set<String>
/\*
\* Trim
\* Also works with arrays, lists or sets
\*/
${#strings.trim(str)} // also array\*, list\* and set\*
/\*
\* Compute length
\* Also works with arrays, lists or sets
\*/
${#strings.length(str)} // also array\*, list\* and set\*
/\*
\* Abbreviate text making it have a maximum size of n. If text is bigger, it
\* will be clipped and finished in "..."
\* Also works with arrays, lists or sets
\*/
${#strings.abbreviate(str,10)} // also array\*, list\* and set\*
/\*
\* Convert the first character to upper-case (and vice-versa)
\*/
${#strings.capitalize(str)} // also array\*, list\* and set\*
${#strings.unCapitalize(str)} // also array\*, list\* and set\*
/\*
\* Convert the first character of every word to upper-case
\*/
${#strings.capitalizeWords(str)} // also array\*, list\* and set\*
${#strings.capitalizeWords(str,delimiters)} // also array\*, list\* and set\*
/\*
\* Escape the string
\*/
${#strings.escapeXml(str)} // also array\*, list\* and set\*
${#strings.escapeJava(str)} // also array\*, list\* and set\*
${#strings.escapeJavaScript(str)} // also array\*, list\* and set\*
${#strings.unescapeJava(str)} // also array\*, list\* and set\*
${#strings.unescapeJavaScript(str)} // also array\*, list\* and set\*
/\*
\* Null-safe comparison and concatenation
\*/
${#strings.equals(first, second)}
${#strings.equalsIgnoreCase(first, second)}
${#strings.concat(values...)}
${#strings.concatReplaceNulls(nullValue, values...)}
/\*
\* Random
\*/
${#strings.randomAlphanumeric(count)}`
对象
- #objects:一般对象的实用程序方法
`/\*
\* ======================================================================
\* See javadoc API for class org.thymeleaf.expression.Objects
\* ======================================================================
\*/
/\*
\* Return obj if it is not null, and default otherwise
\* Also works with arrays, lists or sets
\*/
${#objects.nullSafe(obj,default)}
${#objects.arrayNullSafe(objArray,default)}
${#objects.listNullSafe(objList,default)}
${#objects.setNullSafe(objSet,default)}`
布尔
- #bools:布尔评估的实用程序方法
`/\*
\* ======================================================================
\* See javadoc API for class org.thymeleaf.expression.Bools
\* ======================================================================
\*/
/\*
\* Evaluate a condition in the same way that it would be evaluated in a th:if tag
\* (see conditional evaluation chapter afterwards).
\* Also works with arrays, lists or sets
\*/
${#bools.isTrue(obj)}
${#bools.arrayIsTrue(objArray)}
${#bools.listIsTrue(objList)}
${#bools.setIsTrue(objSet)}
/\*
\* Evaluate with negation
\* Also works with arrays, lists or sets
\*/
${#bools.isFalse(cond)}
${#bools.arrayIsFalse(condArray)}
${#bools.listIsFalse(condList)}
${#bools.setIsFalse(condSet)}
/\*
\* Evaluate and apply AND operator
\* Receive an array, a list or a set as parameter
\*/
${#bools.arrayAnd(condArray)}
${#bools.listAnd(condList)}
${#bools.setAnd(condSet)}
/\*
\* Evaluate and apply OR operator
\* Receive an array, a list or a set as parameter
\*/
${#bools.arrayOr(condArray)}
${#bools.listOr(condList)}
${#bools.setOr(condSet)}`
数组
- #arrays:数组的实用程序方法
**收集整理了一份《2024年最新物联网嵌入式全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升的朋友。**


**[如果你需要这些资料,可以戳这里获取](https://bbs.youkuaiyun.com/topics/618679757)**
**需要这些体系化资料的朋友,可以加我V获取:vip1024c (备注嵌入式)**
**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人**
**都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**
h arrays, lists or sets
\*/
${#strings.prepend(str,prefix)} // also array\*, list\* and set\*
${#strings.append(str,suffix)} // also array\*, list\* and set\*
/\*
\* Change case
\* Also works with arrays, lists or sets
\*/
${#strings.toUpperCase(name)} // also array\*, list\* and set\*
${#strings.toLowerCase(name)} // also array\*, list\* and set\*
/\*
\* Split and join
\*/
${#strings.arrayJoin(namesArray,',')}
${#strings.listJoin(namesList,',')}
${#strings.setJoin(namesSet,',')}
${#strings.arraySplit(namesStr,',')} // returns String[]
${#strings.listSplit(namesStr,',')} // returns List<String>
${#strings.setSplit(namesStr,',')} // returns Set<String>
/\*
\* Trim
\* Also works with arrays, lists or sets
\*/
${#strings.trim(str)} // also array\*, list\* and set\*
/\*
\* Compute length
\* Also works with arrays, lists or sets
\*/
${#strings.length(str)} // also array\*, list\* and set\*
/\*
\* Abbreviate text making it have a maximum size of n. If text is bigger, it
\* will be clipped and finished in "..."
\* Also works with arrays, lists or sets
\*/
${#strings.abbreviate(str,10)} // also array\*, list\* and set\*
/\*
\* Convert the first character to upper-case (and vice-versa)
\*/
${#strings.capitalize(str)} // also array\*, list\* and set\*
${#strings.unCapitalize(str)} // also array\*, list\* and set\*
/\*
\* Convert the first character of every word to upper-case
\*/
${#strings.capitalizeWords(str)} // also array\*, list\* and set\*
${#strings.capitalizeWords(str,delimiters)} // also array\*, list\* and set\*
/\*
\* Escape the string
\*/
${#strings.escapeXml(str)} // also array\*, list\* and set\*
${#strings.escapeJava(str)} // also array\*, list\* and set\*
${#strings.escapeJavaScript(str)} // also array\*, list\* and set\*
${#strings.unescapeJava(str)} // also array\*, list\* and set\*
${#strings.unescapeJavaScript(str)} // also array\*, list\* and set\*
/\*
\* Null-safe comparison and concatenation
\*/
${#strings.equals(first, second)}
${#strings.equalsIgnoreCase(first, second)}
${#strings.concat(values...)}
${#strings.concatReplaceNulls(nullValue, values...)}
/\*
\* Random
\*/
${#strings.randomAlphanumeric(count)}`
对象
- #objects:一般对象的实用程序方法
`/\*
\* ======================================================================
\* See javadoc API for class org.thymeleaf.expression.Objects
\* ======================================================================
\*/
/\*
\* Return obj if it is not null, and default otherwise
\* Also works with arrays, lists or sets
\*/
${#objects.nullSafe(obj,default)}
${#objects.arrayNullSafe(objArray,default)}
${#objects.listNullSafe(objList,default)}
${#objects.setNullSafe(objSet,default)}`
布尔
- #bools:布尔评估的实用程序方法
`/\*
\* ======================================================================
\* See javadoc API for class org.thymeleaf.expression.Bools
\* ======================================================================
\*/
/\*
\* Evaluate a condition in the same way that it would be evaluated in a th:if tag
\* (see conditional evaluation chapter afterwards).
\* Also works with arrays, lists or sets
\*/
${#bools.isTrue(obj)}
${#bools.arrayIsTrue(objArray)}
${#bools.listIsTrue(objList)}
${#bools.setIsTrue(objSet)}
/\*
\* Evaluate with negation
\* Also works with arrays, lists or sets
\*/
${#bools.isFalse(cond)}
${#bools.arrayIsFalse(condArray)}
${#bools.listIsFalse(condList)}
${#bools.setIsFalse(condSet)}
/\*
\* Evaluate and apply AND operator
\* Receive an array, a list or a set as parameter
\*/
${#bools.arrayAnd(condArray)}
${#bools.listAnd(condList)}
${#bools.setAnd(condSet)}
/\*
\* Evaluate and apply OR operator
\* Receive an array, a list or a set as parameter
\*/
${#bools.arrayOr(condArray)}
${#bools.listOr(condList)}
${#bools.setOr(condSet)}`
数组
- #arrays:数组的实用程序方法
**收集整理了一份《2024年最新物联网嵌入式全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升的朋友。**
[外链图片转存中...(img-mCsa26FA-1715794139946)]
[外链图片转存中...(img-ZPBhXIXk-1715794139946)]
**[如果你需要这些资料,可以戳这里获取](https://bbs.youkuaiyun.com/topics/618679757)**
**需要这些体系化资料的朋友,可以加我V获取:vip1024c (备注嵌入式)**
**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人**
**都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**