日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

fundamentals\java\Thymeleaf

發(fā)布時(shí)間:2024/3/24 编程问答 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 fundamentals\java\Thymeleaf 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

Thymeleaf

翻譯自:https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html

up主用google蝦譯的,和up主完全無關(guān),強(qiáng)裂建議直接看原文。

主要就是幾個(gè)符號啦:

消息:#{}

表達(dá)式有作用域:${}?

表達(dá)式無作用域:*{}

url:@{}

相對地址:~{}???

js內(nèi)轉(zhuǎn)義特殊字符:[[${})]

js內(nèi)原生格式:[(${})],

dom屬性:th:each th:object th:href th:url

內(nèi)置函數(shù):#ctx #request ......

Thymeleaf也沒有什么大不了的。

目錄

Thymeleaf

1簡介

1.1什么是Thymeleaf?

1.2 Thymeleaf可以處理什么樣的模板?

1.3方言:標(biāo)準(zhǔn)方言

2 設(shè)計(jì)良好的虛擬雜貨店

2.1雜貨店網(wǎng)站

2.2創(chuàng)建和配置模板引擎

模板解析器

模板引擎

3 文本

3.1多語言的歡迎

使用th:文本和外部化文本

上下文

執(zhí)行模板引擎

3.2更多關(guān)于文本和變量的信息

非轉(zhuǎn)義的文本

使用變量

4標(biāo)準(zhǔn)表達(dá)式語法

4.1 消息

?4.2 變量

基本對象表達(dá)式

擴(kuò)展表達(dá)式對象

4.3表達(dá)式的作用域(星號語法)

4.4 URL鏈接

4.5 模板

4.6 常量

4.7 追加文字

4.8 常量替換

4.9 數(shù)學(xué)計(jì)算

4.10 比較和相等

4.11 條件表達(dá)式

4.12 默認(rèn)值

4.13 無操作

4.14 數(shù)據(jù)轉(zhuǎn)換和格式化

4.15 預(yù)處理

5 設(shè)置屬性

5.1 設(shè)置任意屬性

5.2特定屬性賦值

5.3 一次性設(shè)置多個(gè)值

5.4 追加和追加在前面

5.5 布爾值屬性

5.6 設(shè)置任意屬性的值

5.7 支持HTML5屬性

6 迭代

6.1 基礎(chǔ)迭代

th:each

可以被迭代的值

6.2 迭代狀態(tài)

6.3 延遲加載

7 條件表達(dá)式

7.1 簡單條件:if 和 unless

7.2 Switch表達(dá)式

8 布局模板

8.1 引用模板

定義和引用模板

模板規(guī)格

不使用?th:fragment引用模板

th:insert??th:replace?(and?th:include)區(qū)別

8.2 模板參數(shù)

沒有片段參數(shù)的片段局部變量

模板內(nèi)斷言

8.3 靈活的布局:不僅僅是片段插入

使用空模板

使用_操作符

模板的高級條件引用

8.4 刪除多余的靜態(tài)html代碼

8.5 布局繼承

9 本地參數(shù)

10屬性優(yōu)先級

11 注釋

11.1. 標(biāo)志HTML/XML注釋

11.2. Thymeleaf 注釋模塊

11.3. Thymeleaf原型注釋

11.4.th:block

12 Thymeleaf嵌入

12.1 表達(dá)式嵌入

嵌入vs非嵌入

禁止嵌入

12.2 文本嵌入

12.3 JavaScript 嵌入

JavaScript 非嵌入寫法

嵌入求值和js序列號

12.4 CSS嵌入

高級特性

13 文本模式

13.1 文本語法

忽略元素屬性

13.2擴(kuò)展

13.3 原型注釋

13.4 文本解析器級注釋塊:刪除代碼

13.5 JavaScript and CSS 模板

14雜貨店(略)

15 配置

15.1 模板解析器

模板解析器鏈

15.2 消息解析器

標(biāo)準(zhǔn)消息解析器

配置消息解析器

15.3 轉(zhuǎn)換服務(wù)

15.4 日志

16 模板緩存

17 離線模板

17.1 離線模板的概念(分離thymeleaf和dom)

17.2 配置離線模板

開啟離線模板

混用在線/離線模板

17.3 ref屬性

17.4 離線模板的性能

17.5 何時(shí)分離邏輯代碼和樣式(離線模板)


1簡介

1.1什么是Thymeleaf?

Thymeleaf是一個(gè)用于web和獨(dú)立環(huán)境的現(xiàn)代服務(wù)器端Java模板引擎,能夠處理HTMLXMLJavaScriptCSS甚至純文本。

Thymeleaf的主要目標(biāo)是提供一種優(yōu)雅且易于維護(hù)的創(chuàng)建模板的方法。為了實(shí)現(xiàn)這一點(diǎn),它基于自然模板的概念,以一種不影響模板作為設(shè)計(jì)原型使用的方式將其邏輯注入模板文件中。這改善了設(shè)計(jì)的交流,并彌合了設(shè)計(jì)和開發(fā)團(tuán)隊(duì)之間的差距。

從一開始,Thymeleaf的設(shè)計(jì)就考慮到了Web標(biāo)準(zhǔn)——尤其是HTML5——如果需要的話,它允許您創(chuàng)建完全驗(yàn)證模板。

1.2 Thymeleaf可以處理什么樣的模板?

開箱即用,Thymeleaf允許您處理六種模板,每一種都被稱為模板模式:

  • HTML
  • XML
  • TEXT
  • JAVASCRIPT
  • CSS
  • RAW

有兩種標(biāo)記模板模式(HTMLXML)、三種文本模板模式(文本、JAVASCRIPTCSS)和無操作模板模式(RAW)

HTML模板模式將允許任何類型的HTML輸入,包括HTML5HTML 4XHTML。不執(zhí)行驗(yàn)證或格式良好性檢查,模板代碼/結(jié)構(gòu)將在輸出中得到最大程度的尊重。

XML模板模式將允許XML輸入。在這種情況下,代碼應(yīng)該是格式良好的沒有未關(guān)閉的標(biāo)記,沒有未引用的屬性,等等如果發(fā)現(xiàn)違反格式良好的情況,解析器將拋出異常。注意,不會執(zhí)行(針對DTDXML模式)驗(yàn)證。

文本模板模式將允許對非標(biāo)記性質(zhì)的模板使用特殊語法。此類模板的示例可能是文本電子郵件或模板化文檔。注意,HTMLXML模板也可以作為文本處理,在這種情況下,它們不會被解析為標(biāo)記,并且每個(gè)標(biāo)記、DOCTYPE、注釋等都將被視為純文本。

JAVASCRIPT模板模式將允許在Thymeleaf應(yīng)用程序中處理JAVASCRIPT文件。這意味著能夠像在HTML文件中那樣在JavaScript文件中使用模型數(shù)據(jù),但是使用特定于JavaScript的集成,如專門轉(zhuǎn)義或自然腳本。JAVASCRIPT模板模式被認(rèn)為是文本模式,因此使用與文本模板模式相同的特殊語法。

CSS模板模式將允許處理Thymeleaf應(yīng)用程序中涉及的CSS文件。與JAVASCRIPT模式類似,CSS模板模式也是文本模式,使用來自文本模板模式的特殊處理語法。

原始模板模式將完全不處理模板。它用于將未接觸的資源(文件、URL響應(yīng)等)插入正在處理的模板中。例如,可以將HTML格式的外部不受控制的資源包含到應(yīng)用程序模板中,并且安全地知道這些資源可能包含的任何Thymeleaf代碼都不會被執(zhí)行。

1.3方言:標(biāo)準(zhǔn)方言

2 設(shè)計(jì)良好的虛擬雜貨店

本文所示示例的源代碼以及本指南的后續(xù)章節(jié)可以在Good中找到

Thymes Virtual Grocery GitHub repository.

2.1雜貨店網(wǎng)站

我們的應(yīng)用程序還有一個(gè)非常簡單的服務(wù)層,由包含如下方法的服務(wù)對象組成:

public class ProductService {...public List<Product> findAll() {return ProductRepository.getInstance().findAll();}public Product findById(Integer id) {return ProductRepository.getInstance().findById(id);}}

web層,我們的應(yīng)用程序?qū)⒂幸粋€(gè)過濾器,根據(jù)請求URL將執(zhí)行委托給支持thymeleaf的命令:

private boolean process(HttpServletRequest request, HttpServletResponse response)throws ServletException {try {// This prevents triggering engine executions for resource URLsif (request.getRequestURI().startsWith("/css") ||request.getRequestURI().startsWith("/images") ||request.getRequestURI().startsWith("/favicon")) {return false;}/** Query controller/URL mapping and obtain the controller* that will process the request. If no controller is available,* return false and let other filters/servlets process the request.*/IGTVGController controller = this.application.resolveControllerForRequest(request);if (controller == null) {return false;}/** Obtain the TemplateEngine instance.*/ITemplateEngine templateEngine = this.application.getTemplateEngine();/** Write the response headers*/response.setContentType("text/html;charset=UTF-8");response.setHeader("Pragma", "no-cache");response.setHeader("Cache-Control", "no-cache");response.setDateHeader("Expires", 0);/** Execute the controller and process view template,* writing the results to the response writer. */controller.process(request, response, this.servletContext, templateEngine);return true;} catch (Exception e) {try {response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);} catch (final IOException ignored) {// Just ignore this}throw new ServletException(e);}}

這是我們的IGTVGController接口:

我們現(xiàn)在要做的就是創(chuàng)建IGTVGController接口的實(shí)現(xiàn),從服務(wù)中檢索數(shù)據(jù),并使用ITemplateEngine對象處理模板

最后,它會是這樣的:

但是首先讓我們看看模板引擎是如何初始化的。

2.2創(chuàng)建和配置模板引擎

我們過濾器中的process(…)方法包含這一行:

ITemplateEngine templateEngine = this.application.getTemplateEngine();

這意味著GTVGApplication類負(fù)責(zé)創(chuàng)建和配置Thymeleaf應(yīng)用程序中最重要的對象之一:TemplateEngine實(shí)例(ITemplateEngine接口的實(shí)現(xiàn))

我們的org.thymeleafTemplateEngine對象是這樣初始化的:

public class GTVGApplication {...private final TemplateEngine templateEngine;...public GTVGApplication(final ServletContext servletContext) {super();ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver(servletContext);// HTML is the default mode, but we set it anyway for better understanding of codetemplateResolver.setTemplateMode(TemplateMode.HTML);// This will convert "home" to "/WEB-INF/templates/home.html"templateResolver.setPrefix("/WEB-INF/templates/");templateResolver.setSuffix(".html");// Template cache TTL=1h. If not set, entries would be cached until expelledtemplateResolver.setCacheTTLMs(Long.valueOf(3600000L));// Cache is set to true by default. Set to false if you want templates to// be automatically updated when modified.templateResolver.setCacheable(true);this.templateEngine = new TemplateEngine();this.templateEngine.setTemplateResolver(templateResolver);...}}

有很多方法可以配置TemplateEngine對象,但是現(xiàn)在這幾行代碼可以告訴我們所需的步驟

模板解析器

讓我們從模板解析器開始:

ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver(servletContext);

模板解析器是實(shí)現(xiàn)Thymeleaf API接口的對象,該接口名為org.thymeleaf.templateresolver.ITemplateResolver:

public interface ITemplateResolver {.../** Templates are resolved by their name (or content) and also (optionally) their * owner template in case we are trying to resolve a fragment for another template.* Will return null if template cannot be handled by this template resolver.*/public TemplateResolution resolveTemplate(final IEngineConfiguration configuration,final String ownerTemplate, final String template,final Map<String, Object> templateResolutionAttributes); }

這些對象負(fù)責(zé)決定如何訪問我們的模板,在這個(gè)GTVG應(yīng)用程序中,是org.thymeleaf.templateresolverservletcontext意味著我們將從Servlet上下文中檢索模板文件作為資源:一個(gè)應(yīng)用程序范圍的javax.servletServletContext對象,它存在于每個(gè)Java web應(yīng)用程序中,并解析來自web應(yīng)用程序根目錄的資源。

但這并不是關(guān)于模板解析器的全部內(nèi)容,因?yàn)槲覀兛梢栽谄渖显O(shè)置一些配置參數(shù)。第一,模板模式:

templateResolver.setTemplateMode(TemplateMode.HTML);

HTMLservlet context ttemplateresolver的默認(rèn)模板模式,但是無論如何建立它都是一個(gè)很好的實(shí)踐,這樣我們的代碼就可以清楚地記錄發(fā)生了什么。

templateResolver.setPrefix("/WEB-INF/templates/"); templateResolver.setSuffix(".html");

前綴和后綴修改我們將傳遞給引擎的模板名稱,以獲得要使用的實(shí)際資源名稱。

使用此配置,模板名稱“product/list”對應(yīng)于:

servletContext.getResourceAsStream("/WEB-INF/templates/product/list.html")

可選地,通過cacheTTLMs屬性在模板解析器上配置已解析模板可以駐留在緩存中的時(shí)間:

templateResolver.setCacheTTLMs(3600000L);

如果達(dá)到最大緩存大小,并且它是當(dāng)前緩存的最古老的條目,那么在到達(dá)TTL之前,仍然可以從緩存中驅(qū)逐模板。

用戶可以通過實(shí)現(xiàn)ICacheManager接口或修改StandardCacheManager對象來管理默認(rèn)緩存來定義緩存行為和大小。

關(guān)于模板解析器還有很多要了解的,但是現(xiàn)在讓我們來看看模板引擎對象的創(chuàng)建。

模板引擎

模板引擎對象是org.thymeleaf的實(shí)現(xiàn)。ITemplateEngine接口。這些實(shí)現(xiàn)之一是由Thymeleaf核心提供的:org.thymeleafTemplateEngine,我們在這里創(chuàng)建一個(gè)實(shí)例:

templateEngine = new TemplateEngine(); templateEngine.setTemplateResolver(templateResolver);

很簡單,不是嗎?我們只需要創(chuàng)建一個(gè)實(shí)例并將模板解析器設(shè)置為它。

模板解析器是TemplateEngine需要的惟一必需參數(shù),不過后面還將介紹其他許多參數(shù)(消息解析器、緩存大小等)。現(xiàn)在,這就是我們所需要的。

我們的模板引擎現(xiàn)在已經(jīng)準(zhǔn)備好了,我們可以開始使用Thymeleaf創(chuàng)建頁面。

3 文本

3.1多語言的歡迎

使用th:文本和外部化文本

上下文

執(zhí)行模板引擎

3.2更多關(guān)于文本和變量的信息

非轉(zhuǎn)義的文本

<p th:utext="#{home.welcome}">Welcome to our grocery store!</p>

使用變量

<body><p th:utext="#{home.welcome}">Welcome to our grocery store!</p><p>Today is: <span th:text="${today}">13 February 2011</span></p></body>

4標(biāo)準(zhǔn)表達(dá)式語法

<p th:utext="#{home.welcome}">Welcome to our grocery store!</p><p>Today is: <span th:text="${today}">13 february 2011</span></p>

Simple expressions:

  • Variable Expressions:?${...}
  • Selection Variable Expressions:?*{...}
  • Message Expressions:?#{...}
  • Link URL Expressions:?@{...}
  • Fragment Expressions:?~{...}

Literals 常量

  • Text literals:?'one text',?'Another one!',…
  • Number literals:?0,?34,?3.0,?12.3,…
  • Boolean literals:?true,?false
  • Null literal:?null
  • Literal tokens:?one,?sometext,?main,…

Text operations:

  • String concatenation:?+
  • Literal substitutions:?|The name is ${name}|

Arithmetic operations:

  • Binary operators:?+,?-,?*,?/,?%
  • Minus sign (unary operator):?-

Boolean operations:

  • Binary operators:?and,?or
  • Boolean negation (unary operator):?!,?not

Comparisons and equality:

  • Comparators:?>,?<,?>=,?<=?(gt,?lt,?ge,?le)
  • Equality operators:?==,?!=?(eq,?ne)

Conditional operators:

  • If-then:?(if) ? (then)
  • If-then-else:?(if) ? (then) : (else)
  • Default:?(value) ?: (defaultvalue)

Special tokens:

  • No-Operation:?_

所有這些功能可以組合和嵌套:

'User is of type ' + (${user.isAdmin()} ? 'Administrator' : (${user.type} ?: 'Unknown'))

4.1 消息

如我們所知,……消息表達(dá)式允許我們鏈接這個(gè):

<p th:utext="#{home.welcome}">Welcome to our grocery store!</p>

消息鍵本身可以來自一個(gè)變量:

<p th:utext="#{${welcomeMsgKey}(${session.user.name})}">Welcome to our grocery store, Sebastian Pepper! </p>

?4.2 變量

<p>Today is: <span th:text="${today}">13 february 2011</span>.</p>

實(shí)際上等于

ctx.getVariable("today");

但是OGNL允許我們創(chuàng)建更強(qiáng)大的表達(dá)式,這就是它的原理:

<p th:utext="#{home.welcome(${session.user.name})}">Welcome to our grocery store, Sebastian Pepper! </p>

通過執(zhí)行以下命令獲得用戶名:

((User) ctx.getVariable("session").get("user")).getName();

但是getter方法導(dǎo)航只是OGNL的特性之一。讓我們再看一些:

/** Access to properties using the point (.). Equivalent to calling property getters.*/ ${person.father.name}/** Access to properties can also be made by using brackets ([]) and writing * the name of the property as a variable or between single quotes.*/ ${person['father']['name']}/** If the object is a map, both dot and bracket syntax will be equivalent to * executing a call on its get(...) method.*/ ${countriesByCode.ES} ${personsByName['Stephen Zucchini'].age}/** Indexed access to arrays or collections is also performed with brackets, * writing the index without quotes.*/ ${personsArray[0].name}/** Methods can be called, even with arguments.*/ ${person.createCompleteName()} ${person.createCompleteNameWithSeparator('-')}

基本對象表達(dá)式

當(dāng)在上下文變量上計(jì)算OGNL表達(dá)式時(shí),為了獲得更高的靈活性,表達(dá)式可以使用一些對象。這些對象將被引用(按照OGNL標(biāo)準(zhǔn)),從#符號開始:

  • #ctx: the context object.
  • #vars:?the context variables.
  • #locale: the context locale.
  • #request: (only in Web Contexts) the?HttpServletRequest?object.
  • #response: (only in Web Contexts) the?HttpServletResponse?object.
  • #session: (only in Web Contexts) the?HttpSession?object.
  • #servletContext: (only in Web Contexts) the?ServletContext?object.

我們可以這樣做:

Established locale country: <span th:text="${#locale.country}">US</span>. ?

擴(kuò)展表達(dá)式對象

除了這些基本對象之外,Thymeleaf還將提供一組實(shí)用程序?qū)ο?#xff0c;幫助我們在表達(dá)式中執(zhí)行常見的任務(wù)。

  • #execInfo: information about the template being processed.
  • #messages: methods for obtaining externalized messages inside variables expressions, in the same way as they would be obtained using #{…} syntax.
  • #uris: methods for escaping parts of URLs/URIs
  • #conversions: methods for executing the configured?conversion service?(if any).
  • #dates: methods for?java.util.Date?objects: formatting, component extraction, etc.
  • #calendars: analogous to?#dates, but for?java.util.Calendar?objects.
  • #numbers: methods for formatting numeric objects.
  • #strings: methods for?String?objects: contains, startsWith, prepending/appending, etc.
  • #objects: methods for objects in general.
  • #bools: methods for boolean evaluation.
  • #arrays: methods for arrays.
  • #lists: methods for lists.
  • #sets: methods for sets.
  • #maps: methods for maps.
  • #aggregates: methods for creating aggregates on arrays or collections.
  • #ids: methods for dealing with id attributes that might be repeated (for example, as a result of an iteration).

4.3表達(dá)式的作用域(星號語法)

變量表達(dá)式不僅可以寫成${…},也可以寫成*{…}

但是有一個(gè)重要的區(qū)別:星號語法對所選對象的表達(dá)式求值,而不是對整個(gè)上下文求值。也就是說,只要沒有選擇對象,美元和星號語法的作用是完全相同的。

當(dāng)對象選擇就緒時(shí),所選對象也將作為#對象表達(dá)式變量提供給美元表達(dá)式:

<div th:object="${session.user}"><p>Name: <span th:text="${#object.firstName}">Sebastian</span>.</p><p>Surname: <span th:text="${session.user.lastName}">Pepper</span>.</p><p>Nationality: <span th:text="*{nationality}">Saturn</span>.</p> </div>

如前所述,如果沒有th:object,則美元語法和星號語法是等效的。

<div><p>Name: <span th:text="*{session.user.name}">Sebastian</span>.</p><p>Surname: <span th:text="*{session.user.surname}">Pepper</span>.</p><p>Nationality: <span th:text="*{session.user.nationality}">Saturn</span>.</p> </div>

4.4 URL鏈接

由于它們的重要性,urlweb應(yīng)用程序模板中是一等公民,Thymeleaf標(biāo)準(zhǔn)方言為它們提供了一種特殊的語法,即@語法:@{…}

有不同類型的URL:

絕對url:http://www.thymeleaf.org

相對url,可以是:

  • Page-relative:?user/login.html
  • Context-relative:?/itemdetails?id=3?(context name in server will be added automatically)
  • Server-relative:?~/billing/processInvoice?(allows calling URLs in another context (= application) in the same server.
  • Protocol-relative URLs:?//code.jquery.com/jquery-2.0.3.min.js

這些表達(dá)式的實(shí)際處理以及它們對將要輸出的url的轉(zhuǎn)換是由org.thymeleaf.linkbuilder的實(shí)現(xiàn)完成的。注冊到正在使用的ITemplateEngine對象中的ILinkBuilder接口。

默認(rèn)情況下,該接口的單個(gè)實(shí)現(xiàn)注冊為org.thymeleaf.linkbuilder類。StandardLinkBuilder,這對于離線(web)和基于Servlet APIweb場景都足夠了。其他場景(如與非servletapi web框架的集成)可能需要link builder接口的特定實(shí)現(xiàn)。

讓我們使用這個(gè)新語法。滿足th:href屬性:

<!-- Will produce 'http://localhost:8080/gtvg/order/details?orderId=3' (plus rewriting) --> <a href="details.html" th:href="@{http://localhost:8080/gtvg/order/details(orderId=${o.id})}">view</a><!-- Will produce '/gtvg/order/details?orderId=3' (plus rewriting) --> <a href="details.html" th:href="@{/order/details(orderId=${o.id})}">view</a><!-- Will produce '/gtvg/order/3/details' (plus rewriting) --> <a href="details.html" th:href="@{/order/{orderId}/details(orderId=${o.id})}">view</a>

這里需要注意的是:

th:href是一個(gè)修飾符屬性:一旦處理,它將計(jì)算要使用的鏈接URL,并將該值設(shè)置為標(biāo)記的href屬性。

我們可以為URL參數(shù)使用表達(dá)式(正如您可以在orderId=${o.id}中看到的那樣)。還將自動執(zhí)行所需的url參數(shù)編碼操作。

如果需要幾個(gè)參數(shù),則用逗號分隔:@{/order/process(execId=${execId}execType='FAST')}

URL路徑中也允許使用變量模板:@{/order/{orderId}/details(orderId=${orderId})}

/(例如:/order/details)開頭的相對url將自動以應(yīng)用程序上下文名稱作為前綴。

如果沒有啟用cookie,或者還不知道,可以向相對url添加一個(gè)“;jsessionid=…”后綴,以便保留會話。這稱為URL重寫,Thymeleaf允許您通過使用Servlet API中的response.encodeURL(…)機(jī)制為每個(gè)URL插入自己的重寫過濾器。

th:href屬性允許我們(可選地)在模板中有一個(gè)可工作的靜態(tài)href屬性,這樣在直接為原型目的打開模板鏈接時(shí),瀏覽器就可以繼續(xù)導(dǎo)航。

與消息語法(#{…})一樣,URL基也可以是計(jì)算另一個(gè)表達(dá)式的結(jié)果:

<a th:href="@{${url}(orderId=${o.id})}">view</a> <a th:href="@{'/details/'+${user.login}(orderId=${o.id})}">view</a>

4.5 模板

最常見的用法是使用th:insertth:replace插入片段(在后面的小節(jié)中將詳細(xì)介紹):

<div th:insert="~{commons :: main}">...</div>

在本教程的后面,有一個(gè)完整的部分專門討論模板布局,包括對片段表達(dá)式的更深入的解釋。

4.6 常量

略。

4.7 追加文字

文本,無論它們是文本還是變量或消息表達(dá)式的計(jì)算結(jié)果,都可以使用+運(yùn)算符輕松地附加:

<span th:text="'The name of the user is ' + ${user.name}">

4.8 常量替換

文字替換允許對包含變量值的字符串進(jìn)行簡單的格式化,而不需要在字符串后面加上'…”+“…”

這些替換必須被豎條(|)包圍,例如:

<span th:text="|Welcome to our application, ${user.name}!|">

等于:

<span th:text="'Welcome to our application, ' + ${user.name} + '!'">

文字替代可以與其他類型的表達(dá)式結(jié)合:

<span th:text="${onevar} + ' ' + |${twovar}, ${threevar}|">

?

只有變量/消息表達(dá)式(${…},{…} #{…})允許在|…|文字替換。沒有其他的文字('…'),布爾/數(shù)字符號,條件表達(dá)式等。

4.9 數(shù)學(xué)計(jì)算

還有一些算術(shù)運(yùn)算:+-*/%

<div th:with="isEven=(${prodStat.count} % 2 == 0)">

注意,這些運(yùn)算符也可以應(yīng)用于OGNL變量表達(dá)式本身(在這種情況下,將由OGNL而不是Thymeleaf標(biāo)準(zhǔn)表達(dá)式引擎執(zhí)行):

4.10 比較和相等

表達(dá)式中的值可以與><>=<=符號進(jìn)行比較,并且可以使用==!=操作符檢查是否相等(或是否不相等)。注意,XML規(guī)定不應(yīng)該在屬性值中使用<>符號,因此應(yīng)該用&lt;?&gt;

<div th:if="${prodStat.count} &gt; 1">

?

<span th:text="'Execution mode is ' + ( (${execMode} == 'dev')? 'Development' : 'Production')"> ?

一個(gè)更簡單的選擇可能是使用這些操作符中存在的文本別名:gt(>)lt(<)ge(>=)le(<=),而不是(!)eq (==) neq/ne (!=)

4.11 條件表達(dá)式

條件表達(dá)式的作用是僅對兩個(gè)表達(dá)式中的一個(gè)求值,這取決于求值條件的結(jié)果(條件本身也是另一個(gè)表達(dá)式)

讓我們看一個(gè)示例片段(引入另一個(gè)屬性修飾符th:class):

<tr th:class="${row.even}? 'even' : 'odd'">... </tr>

條件表達(dá)式的所有三個(gè)部分(condition, thenelse)本身都是表達(dá)式,這意味著它們可以是變量(${…)}*{…}),消息(# {…}),url(@{…})或文本(“…”)

條件表達(dá)式也可以嵌套使用括號:

<tr th:class="${row.even}? (${row.first}? 'first' : 'even') : 'odd'">... </tr>

Else表達(dá)式也可以省略,如果條件為false,則返回null:

<tr th:class="${row.even}? 'alt'">... </tr>

4.12 默認(rèn)值

<div th:object="${session.user}">...<p>Age: <span th:text="*{age}?: '(no age specified)'">27</span>.</p> </div>

4.13 無操作

無操作令牌由下劃線符號(_)表示。

這個(gè)令牌背后的思想是指定表達(dá)式的期望結(jié)果是什么都不做,也就是說,就像根本不存在可處理屬性(例如th:text)一樣。

在其他可能性中,這允許開發(fā)人員使用原型文本作為默認(rèn)值。例如,代替:

<span th:text="${user.name} ?: 'no user authenticated'">...</span>

……我們可以直接使用“no user authenticated”作為原型文本,這使得代碼從設(shè)計(jì)角度來說更加簡潔和通用:

<span th:text="${user.name} ?: _">no user authenticated</span>

4.14 數(shù)據(jù)轉(zhuǎn)換和格式化

Thymeleaf為變量(${…})和選擇(*{…})表達(dá)式定義了一個(gè)雙括號語法,它允許我們通過配置的轉(zhuǎn)換服務(wù)應(yīng)用數(shù)據(jù)轉(zhuǎn)換。

基本上是這樣的:

<td th:text="${{user.lastAccessDate}}">...</td>

注意到雙括號了嗎?:$ {{…}}。它指示Thymeleaf傳遞用戶的結(jié)果。轉(zhuǎn)換服務(wù)的lastAccessDate表達(dá)式,并要求它在寫入結(jié)果之前執(zhí)行格式化操作(轉(zhuǎn)換為字符串)

假設(shè)用戶。lastAccessDate類型為java.util.Calendar,如果一個(gè)轉(zhuǎn)換服務(wù)(IStandardConversionService的實(shí)現(xiàn))已經(jīng)注冊,并且包含一個(gè)有效的日歷->字符串轉(zhuǎn)換,那么它將被應(yīng)用。

IStandardConversionService (StandardConversionService)的默認(rèn)實(shí)現(xiàn)只是在任何轉(zhuǎn)換為字符串的對象上執(zhí)行. tostring()。有關(guān)如何注冊自定義轉(zhuǎn)換服務(wù)實(shí)現(xiàn)的更多信息,請參閱配置部分的更多信息。

官方的Thymeleaf -spring3Thymeleaf -spring4集成包透明地將Thymeleaf的轉(zhuǎn)換服務(wù)機(jī)制與Spring自己的轉(zhuǎn)換服務(wù)基礎(chǔ)設(shè)施集成在一起,這樣在Spring配置中聲明的轉(zhuǎn)換服務(wù)和格式化程序?qū)⒆詣犹峁┙o${{…}}{{…}}表達(dá)式。

4.15 預(yù)處理

除了這些用于表達(dá)處理的特性外,Thymeleaf還具有預(yù)處理表達(dá)式的特性。

<p th:text="${__#{article.text('textVar')}__}">Some text here...</p>

注意,法語語言環(huán)境的預(yù)處理步驟將創(chuàng)建以下等價(jià)的代碼:

<p th:text="${@myapp.translator.Translator@translateToFrench(textVar)}">Some text here...</p>

預(yù)處理字符串可以使用\_\_在屬性中轉(zhuǎn)義。

5 設(shè)置屬性

本章將解釋如何在標(biāo)記中設(shè)置(或修改)屬性值

5.1 設(shè)置任意屬性

然后輸入th:attr屬性,以及它改變所設(shè)置的標(biāo)簽屬性值的能力:

<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>

但是如果我們想一次設(shè)置多個(gè)屬性呢?XML規(guī)則不允許在標(biāo)記中兩次設(shè)置屬性,因此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特定屬性賦值

現(xiàn)在,你可能會想:

<input type="submit" value="Subscribe!" th:attr="value=#{subscribe.submit}"/>

是一段相當(dāng)難看的標(biāo)記。在屬性值中指定賦值可能非常實(shí)用,但如果必須一直這樣做,那么它不是創(chuàng)建模板的最優(yōu)雅的方法。

Thymeleaf同意您的觀點(diǎn),這就是為什么th:attr很少用于模板中。通常,您將使用其他th:*屬性,其任務(wù)是設(shè)置特定的標(biāo)記屬性(而不是像th:attr這樣的任何屬性)

例如,要設(shè)置value屬性,可以使用th:value:

<input type="submit" value="Subscribe!" th:value="#{subscribe.submit}"/>

這看起來好多了!讓我們嘗試對表單標(biāo)記中的action屬性執(zhí)行相同的操作:

<form action="subscribe.html" th:action="@{/subscribe}">

5.3 一次性設(shè)置多個(gè)值

有兩個(gè)非常特殊的屬性叫做th:alt-titleth:lang-xmllang,可以用于將兩個(gè)屬性同時(shí)設(shè)置為相同的值。具體地說:

  • th:alt-title?will set?alt?and?title.
  • th:lang-xmllang?will set?lang?and?xml:lang.

5.4 追加和追加在前面

Thymeleaf還提供th:attrappendth:attrprepend屬性,它們將對現(xiàn)有屬性值求值的結(jié)果追加(后綴)prepend(前綴)

例如,您可能想要將要添加(不是設(shè)置,只是添加)CSS類的名稱存儲在上下文變量中的一個(gè)按鈕中,因?yàn)橐褂玫奶囟?/span>CSS類將取決于用戶以前所做的事情:

<input type="button" value="Do it!" class="btn" th:attrappend="class=${' ' + cssStyle}" />

5.5 布爾值屬性

HTML有布爾屬性的概念,沒有值的屬性,一個(gè)值的前綴表示值為“true”。在XHTML中,這些屬性只取1個(gè)值,也就是它本身。

<input type="checkbox" name="option2" checked /> <!-- HTML -->

標(biāo)準(zhǔn)方言包括允許您通過計(jì)算條件來設(shè)置這些屬性的屬性,因此,如果計(jì)算為true,屬性將被設(shè)置為其固定值,如果計(jì)算為false,屬性將不會被設(shè)置:

<input type="checkbox" name="active" th:checked="${user.active}" />

5.6 設(shè)置任意屬性的值

Thymeleaf提供了一個(gè)默認(rèn)的屬性處理器,允許我們設(shè)置任何屬性的值,即使在標(biāo)準(zhǔn)方言中沒有為它定義特定的th:*處理器。

<span th:whatever="${user.name}">...</span>

將會導(dǎo)致

<span whatever="John Apricot">...</span>

5.7 支持HTML5屬性

還可以使用一種完全不同的語法,以一種更加html友好的方式將處理器應(yīng)用到模板中。

<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中編寫定制屬性的標(biāo)準(zhǔn)方法,不要求開發(fā)人員使用任何名稱空間名稱,如th:*Thymeleaf使這種語法自動適用于所有方言(不僅僅是標(biāo)準(zhǔn)方言)

還有一種語法可以指定定制標(biāo)記:{prefix}-{name},它遵循W3C定制元素規(guī)范(W3C Web組件規(guī)范的一部分)。例如,這可以用于th:block元素(或者也可以用于th),后面的部分將對此進(jìn)行解釋。

重要提示:這個(gè)語法是對名稱空間th:* one的補(bǔ)充,它不會替換它。將來完全不打算廢棄帶有名稱空間的語法。

6 迭代

到目前為止,我們已經(jīng)創(chuàng)建了一個(gè)主頁、一個(gè)用戶個(gè)人資料頁面以及一個(gè)允許用戶訂閱我們的時(shí)事通訊的頁面……但是我們的產(chǎn)品呢?為此,我們將需要一種迭代集合中的項(xiàng)的方法來構(gòu)建我們的產(chǎn)品頁面。

6.1 基礎(chǔ)迭代

th:each

<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>

上面看到的prod: ${prods}屬性值的意思是對于計(jì)算${prods}的結(jié)果中的每個(gè)元素,使用名為prod的變量中的當(dāng)前元素重復(fù)這個(gè)模板片段。讓我們給我們看到的每樣?xùn)|西命名:

  • We will call?${prods}?the?iterated expression?or?iterated variable.
  • We will call?prod?the?iteration variable?or simply?iter variable.

可以被迭代的值

java.utilList類不是Thymeleaf中惟一可以用于迭代的值。有一個(gè)相當(dāng)完整的對象集,被th:each屬性認(rèn)為是可迭代的:

  • Any object implementing?java.util.Iterable
  • Any object implementing?java.util.Enumeration.
  • Any object implementing?java.util.Iterator, whose values will be used as they are returned by the iterator, without the need to cache all values in memory.
  • Any object implementing?java.util.Map. When iterating maps, iter variables will be of class?java.util.Map.Entry.
  • Any array.
  • Any other object will be treated as if it were a single-valued list containing the object itself.

6.2 迭代狀態(tài)

在使用th:each時(shí),Thymeleaf提供了一種用于跟蹤迭代狀態(tài)的機(jī)制:狀態(tài)變量。

狀態(tài)變量定義在th:每個(gè)屬性中,包含以下數(shù)據(jù):

  • The current?iteration index, starting with 0. This is the?index?property.
  • The current?iteration index, starting with 1. This is the?count?property.
  • The total amount of elements in the iterated variable. This is the?size?property.
  • The?iter variable?for each iteration. This is the?current?property.
  • Whether the current iteration is even or odd. These are the?even/odd?boolean properties.
  • Whether the current iteration is the first one. This is the?first?boolean property.
  • Whether the current iteration is the last one. This is the?last?boolean property.

讓我們看看如何在前面的例子中使用它:

<tr th:each="prod,iterStat : ${prods}" th:class="${iterStat.odd}? 'odd'">

狀態(tài)變量(本例中為iterStat)th:each屬性中定義,方法是在iter變量本身后面寫入其名稱,中間用逗號分隔。就像iter變量一樣,status變量的作用域也限定在包含th:each屬性的標(biāo)記所定義的代碼片段中。

如果您沒有顯式地設(shè)置狀態(tài)變量,Thymeleaf將始終為您創(chuàng)建一個(gè)狀態(tài)變量,方法是將Stat添加到迭代變量的名稱后面:

6.3 延遲加載

有時(shí)我們可能希望優(yōu)化數(shù)據(jù)集合的檢索(例如從數(shù)據(jù)庫中檢索),以便只有在真正要使用這些集合時(shí)才檢索這些集合。

?

實(shí)際上,這可以應(yīng)用于任何數(shù)據(jù)塊,但是考慮到內(nèi)存集合可能具有的大小,檢索要迭代的集合是本場景中最常見的情況。

?

為了支持這一點(diǎn),Thymeleaf提供了一種延遲加載上下文變量的機(jī)制。實(shí)現(xiàn)ILazyContextVariable接口的上下文變量——很可能是通過擴(kuò)展其LazyContextVariable默認(rèn)實(shí)現(xiàn)實(shí)現(xiàn)的——將在執(zhí)行時(shí)解析。例如:

context.setVariable("users",new LazyContextVariable<List<User>>() {@Overrideprotected List<User> loadValue() {return databaseRepository.findAllUsers();}});

這個(gè)變量可以在不知道它是否懶惰的情況下使用,代碼如下:

<li th:each="u : ${users}" th:text="${u.name}">user name</li>

但同時(shí),如果條件在代碼中計(jì)算為false,則永遠(yuǎn)不會被初始化(它的loadValue()方法將永遠(yuǎn)不會被調(diào)用),例如:

<li th:each="u : ${users}" th:text="${u.name}">user name</li>

7 條件表達(dá)式

7.1 簡單條件:if 和 unless

有時(shí)候,您需要模板的一個(gè)片段只出現(xiàn)在滿足特定條件的結(jié)果中。

例如,假設(shè)我們希望在product表中顯示一列,其中包含每個(gè)產(chǎn)品的評論數(shù)量,如果有任何評論,則顯示到該產(chǎn)品的評論詳細(xì)信息頁面的鏈接。

為了做到這一點(diǎn),我們將使用th:if屬性:

th:if="${not #lists.isEmpty(prod.comments)}">view</a>

這里有很多東西值得一看,所以讓我們來看看這條重要的線:

這將創(chuàng)建一個(gè)到comments頁面的鏈接(帶有URL /product/comments),并將prodId參數(shù)設(shè)置為產(chǎn)品的id,但只有在產(chǎn)品有任何注釋時(shí)才會這樣做。

讓我們來看看結(jié)果標(biāo)記:

<a href="/gtvg/product/comments?prodId=2">view</a>

請注意,th:if屬性不僅計(jì)算布爾條件。它的功能稍微超出了這一范圍,它將按照以下規(guī)則計(jì)算指定的表達(dá)式為true:

  • If value is a boolean and is?true.
  • If value is a number and is non-zero
  • If value is a character and is non-zero
  • If value is a String and is not “false”, “off” or “no”
  • If value is not a boolean, a number, a character or a String.

(If value is null, th:if will evaluate to false).

同樣,th:if有一個(gè)逆屬性,th:unless,我們可以在前面的例子中使用,而不是在OGNL表達(dá)式中使用not:

th:unless="${#lists.isEmpty(prod.comments)}">view</a>

7.2 Switch表達(dá)式

還有一種方法可以有條件地顯示內(nèi)容,它使用Java中等價(jià)的交換結(jié)構(gòu):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,同一切換上下文中的每一個(gè)其他th:case屬性的值都為false

默認(rèn)選項(xiàng)指定為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 引用模板

定義和引用模板

在我們的模板中,我們通常希望包含來自其他模板的部分,比如頁腳、頁眉、菜單……

為了做到這一點(diǎn),Thymeleaf需要我們?yōu)榘x這些部分,“fragments”,這可以使用th:fragment屬性來完成。

假設(shè)我們想為所有的雜貨頁面添加一個(gè)標(biāo)準(zhǔn)的版權(quán)頁腳,因此我們創(chuàng)建了一個(gè)/WEB-INF/templates/footer.html文件,其中包含以下代碼:

<!DOCTYPE html><html xmlns:th="http://www.thymeleaf.org"><body><div th:fragment="copy">&copy; 2011 The Good Thymes Virtual Grocery</div></body></html>

上面的代碼定義了一個(gè)名為copy的片段,我們可以使用th:insertth:replace屬性(以及th:include,不過自Thymeleaf 3.0以來不再推薦使用它)輕松地將其包含在主頁中:

<body>...<div th:insert="~{footer :: copy}"></div></body>

注意,th:insert需要一個(gè)片段表達(dá)式(~{…}),這是一個(gè)產(chǎn)生片段的表達(dá)式。在上面的例子中,這是一個(gè)非復(fù)雜的片段表達(dá)式,(~{})的封裝是完全可選的,所以上面的代碼相當(dāng)于:

<body>...<div th:insert="footer :: copy"></div></body>

模板規(guī)格

片段表達(dá)式的語法非常簡單。有三種不同的格式:

"~{templatename::selector}"

包含在名為templatename的模板上應(yīng)用指定標(biāo)記選擇器生成的片段。注意,選擇器可以僅僅是一個(gè)片段名稱,所以您可以像上面的~{footer:: copy}中那樣指定像~{templatename::fragmentname}這樣簡單的東西。

?

標(biāo)記選擇器語法由底層的AttoParser解析庫定義,類似于XPath表達(dá)式或CSS選擇器。有關(guān)更多信息,請參見附錄C

?

"~{templatename}"

包含名為templatename的完整模板。

注意,在th:insert/th:replace標(biāo)記中使用的模板名稱必須由模板引擎當(dāng)前使用的模板解析器解析。

~{::selector}"?or?"~{this::selector}"

插入來自相同模板的片段,匹配選擇器。如果在表達(dá)式出現(xiàn)的模板上沒有找到,則會將模板調(diào)用(插入)的堆棧遍歷到原始處理的模板(),直到選擇器在某個(gè)級別上匹配為止。?

上面例子中的templatenameselector都可以是全功能的表達(dá)式(甚至是條件語句!)

<div th:insert="footer :: (${user.isAdmin}? #{footer.admin} : #{footer.normaluser})"></div>

片段可以包括任何th:*屬性。一旦片段被包含到目標(biāo)模板中(具有th:insert/th:replace屬性的那個(gè)),這些屬性將被計(jì)算,并且它們將能夠引用在這個(gè)目標(biāo)模板中定義的任何上下文變量。

?

這種片段處理方法的一大優(yōu)點(diǎn)是,您可以將片段編寫在瀏覽器完全可顯示的頁面中,頁面具有完整甚至有效的標(biāo)記結(jié)構(gòu),同時(shí)仍然能夠使Thymeleaf將它們包含到其他模板中。

不使用?th:fragment引用模板

由于標(biāo)記選擇器的強(qiáng)大功能,我們可以包含不使用任何th:fragment屬性的片段。它甚至可以是來自完全不了解Thymeleaf的另一個(gè)應(yīng)用程序的標(biāo)記代碼:

<div id="copy-section">&copy; 2011 The Good Thymes Virtual Grocery </div>

我們可以使用上面的片段簡單地引用它的id屬性,以類似于CSS選擇器的方式:

<body>...<div th:insert="~{footer :: #copy-section}"></div></body>

th:insert??th:replace?(and?th:include)區(qū)別

th:insertth:replace(以及th:include, 3.0以后就不推薦了)之間的區(qū)別是什么?

th:insert?是最簡單的:它將簡單地插入指定的片段作為其宿主標(biāo)記的主體。

Th:replace 實(shí)際上用指定的片段替換其主機(jī)標(biāo)記。

th:include 類似于th:insert,但它只插入片段的內(nèi)容,而不是插入片段。

像這樣的HTML片段:

<footer th:fragment="copy">&copy; 2011 The Good Thymes Virtual Grocery </footer>

Insert

<div><footer>&copy; 2011 The Good Thymes Virtual Grocery</footer></div>

Replace

<footer>&copy; 2011 The Good Thymes Virtual Grocery</footer>

Include

<div>&copy; 2011 The Good Thymes Virtual Grocery </div>

8.2 模板參數(shù)

為了為模板片段創(chuàng)建更類似函數(shù)的機(jī)制,使用th:fragment定義的片段可以指定一組參數(shù):

<div th:fragment="frag (onevar,twovar)"><p th:text="${onevar} + ' - ' + ${twovar}">...</p> </div>

這需要使用這兩種語法中的一種來調(diào)用來自th:insertth:replace的片段:

<div th:replace="::frag (${value1},${value2})">...</div> <div th:replace="::frag (onevar=${value1},twovar=${value2})">...</div>

注意最后一個(gè)選項(xiàng)中的順序并不重要:

沒有片段參數(shù)的片段局部變量

即使沒有這樣的參數(shù)來定義片段:

<div th:fragment="frag">... </div>

我們可以使用上面指定的第二種語法來調(diào)用它們(只有第二種):

<div th:replace="::frag (onevar=${value1},twovar=${value2})">

這將等價(jià)于th:replaceth:with:的組合:

<div th:replace="::frag" th:with="onevar=${value1},twovar=${value2}">

請注意,片段的這種局部變量規(guī)范——無論它是否具有參數(shù)簽名——不會導(dǎo)致在執(zhí)行之前清空上下文。片段仍然能夠像當(dāng)前一樣訪問調(diào)用模板中使用的每個(gè)上下文變量。

模板內(nèi)斷言

斷言屬性可以指定一個(gè)以逗號分隔的表達(dá)式列表,這些表達(dá)式應(yīng)該被求值,并為每個(gè)求值生成true,如果不是,則引發(fā)異常。

<div th:assert="${onevar},(${twovar} != 43)">...</div>

這對于驗(yàn)證片段簽名的參數(shù)很方便:

<header th:fragment="contentheader(title)" th:assert="${!#strings.isEmpty(title)}">...</header>

8.3 靈活的布局:不僅僅是片段插

多虧了片段表達(dá)式,我們可以為片段指定參數(shù),這些片段不是文本、數(shù)字、bean對象……而是標(biāo)記的片段。

這允許我們以這樣一種方式創(chuàng)建片段,通過調(diào)用模板中的標(biāo)記來豐富片段,從而形成非常靈活的模板布局機(jī)制。

注意下面片段中標(biāo)題和鏈接變量的使用:

<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>

We can now call this fragment like:

... <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> ...

結(jié)果將使用我們調(diào)用模板中實(shí)際的

... <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> ...

使用空模板

一個(gè)特殊的片段表達(dá)式,空片段(~{}),可以用來指定沒有標(biāo)記。使用前面的例子:

<head th:replace="base :: common_header(~{::title},~{})"><title>Awesome - Main</title></head>

注意片段(鏈接)的第二個(gè)參數(shù)是如何設(shè)置為空片段的,因此沒有為塊編寫任何內(nèi)容:

... <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> ...

使用_操作符

如果我們只想讓片段使用其當(dāng)前標(biāo)記作為默認(rèn)值,那么no-op也可以用作片段的參數(shù)。再次使用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參數(shù)(common_header片段的第一個(gè)參數(shù))是如何設(shè)置為no-op(_)的,這將導(dǎo)致片段的這一部分根本不執(zhí)行(title = no-operation):

<title th:replace="${title}">The awesome application</title>

所以結(jié)果是:

... <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片段和無操作標(biāo)記的可用性允許我們以一種非常簡單和優(yōu)雅的方式執(zhí)行片段的條件插入。

例如,我們可以只在用戶是管理員的情況下插入我們的common:: adminhead片段,如果用戶不是管理員,則不插入任何內(nèi)容(emtpy片段):

? <div th:insert="${user.isAdmin()} ? ~{common :: adminhead} : ~{}">...</div>

無操作標(biāo)記,以便僅在滿足指定條件時(shí)插入片段,但如果不滿足條件,則保留標(biāo)記不做任何修改:

... <div th:insert="${user.isAdmin()} ? ~{common :: adminhead} : _">Welcome [[${user.name}]], click <a th:href="@{/support}">here</a> for help-desk support. </div> ...

此外,如果我們配置了模板解析器來檢查模板資源是否存在——通過它們的checkExistence標(biāo)志——我們可以使用片段本身的存在作為默認(rèn)操作的條件:

... <!-- 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 刪除多余的靜態(tài)html代碼

回到示例應(yīng)用程序,讓我們重溫產(chǎn)品列表模板的最后一個(gè)版本:

<tr th:each="prod : ${prods}" th:class="${prodStat.odd}? 'odd'">

這段代碼作為模板很好,但是作為靜態(tài)頁面(當(dāng)瀏覽器直接打開而不需要Thymeleaf處理它時(shí))就不是一個(gè)很好的原型。

為什么?因?yàn)?#xff0c;盡管瀏覽器可以完美地顯示該表,但該表只有一行,而這一行有模擬數(shù)據(jù)。作為一個(gè)原型,它看起來不夠真實(shí)……我們應(yīng)該有多個(gè)產(chǎn)品,我們需要更多行。

所以讓我們添加一些:

我們需要一種方法在模板處理期間刪除這兩行。

讓我們使用th:remove第二個(gè)和第三個(gè)<tr>標(biāo)記上的屬性:

<tr class="odd" th:remove="all">

屬性中的所有值是什么意思呢?th:remove可以有五種不同的行為,這取決于它的值:

  • all: Remove both the containing tag and all its children.
  • body: Do not remove the containing tag, but remove all its children.
  • tag: Remove the containing tag, but do not remove its children.
  • all-but-first: Remove all children of the containing tag except the first one.
  • none?: Do nothing. This value is useful for dynamic evaluation.

這個(gè)幾乎是最重要的價(jià)值有什么用呢?它將讓我們保存一些th:remove="all"當(dāng)原型:

th:remove屬性可以接受任何Thymeleaf標(biāo)準(zhǔn)表達(dá)式,只要它返回一個(gè)允許的字符串值(alltagbodyall-but-firstnone)

這意味著移除可以是有條件的,比如:

<a href="/something" th:remove="${condition}? tag : none">Link text not to be removed</a>

還要注意,th:remove認(rèn)為nullnone的同義詞,所以下面的工作與上面的示例相同:

<a href="/something" th:remove="${condition}? tag">Link text not to be removed</a>

在這種情況下,如果${condition}false,將返回null,因此不會執(zhí)行刪除操作。

8.5 布局繼承

為了能夠使用單個(gè)文件作為布局,可以使用片段。一個(gè)簡單的布局的例子,標(biāo)題和內(nèi)容使用th:fragmentth: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>

這個(gè)示例聲明了一個(gè)名為layout的片段,其中標(biāo)題和內(nèi)容作為參數(shù)。下面的示例中提供的片段表達(dá)式將在繼承它的頁面上替換這兩個(gè)表達(dá)式。

<!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>

在這個(gè)文件中,html標(biāo)記將被layout替換,但是在布局中標(biāo)題和內(nèi)容將分別被titlesection塊替換。

如果需要,布局可以由幾個(gè)片段組成,如頁眉和頁腳。

9 本地參數(shù)

Thymeleaf調(diào)用局部變量,這些變量是為模板的特定片段定義的,并且僅可用于該片段內(nèi)的求值。

我們已經(jīng)看到的一個(gè)例子是產(chǎn)品列表頁面中的prod iter變量:

<tr th:each="prod : ${prods}">... </tr>

prod變量僅在<tr>標(biāo)記的范圍內(nèi)可用。明確地:

對于在該標(biāo)記中執(zhí)行的優(yōu)先級低于th:each的任何其他th:*屬性,它都是可用的(這意味著它們將在th:each之后執(zhí)行)

prod變量僅在<tr>標(biāo)記的范圍內(nèi)可用。明確地:

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>

當(dāng)th:with被處理時(shí),firstPer變量被創(chuàng)建為一個(gè)局部變量,并添加到來自上下文的變量映射中,這樣它就可以與上下文中聲明的任何其他變量一起進(jìn)行計(jì)算,但只能在包含標(biāo)記的范圍內(nèi)。

您可以使用通常的多重賦值語法同時(shí)定義多個(gè)變量:

<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屬性允許重用在同一個(gè)屬性中定義的變量:

<div th:with="company=${user.company + ' Co.'},account=${accounts[company]}">...</div>

讓我們用這個(gè)在我們的雜貨店的主頁上!還記得我們編寫的輸出格式化日期的代碼嗎?

<p>Today is: <span th:text="${#calendars.format(today,'dd MMMM yyyy')}">13 february 2011</span> </p>

現(xiàn)在,讓我們使用th:with將本地化的日期格式轉(zhuǎn)換為變量,然后在th:text表達(dá)式中使用它:

<p th:with="df=#{date.format}">Today is: <span th:text="${#calendars.format(today,df)}">13 February 2011</span> </p>

這既干凈又簡單。事實(shí)上,考慮到th:with的優(yōu)先級高于th:text,我們可以在span標(biāo)記中解決所有問題:

<p>Today is: <span th:with="df=#{date.format}" th:text="${#calendars.format(today,df)}">13 February 2011</span> </p>

你可能會想:優(yōu)先?我們還沒說呢!別擔(dān)心,這正是下一章的內(nèi)容。

10屬性優(yōu)先級

當(dāng)您在同一個(gè)標(biāo)記中編寫多個(gè)th:*屬性時(shí)會發(fā)生什么?例如:

<li th:each="item : ${items}" th:text="${item.description}">Item description here...</li>

我們希望th:每個(gè)屬性執(zhí)行之前th:文本,這樣我們得到我們想要的結(jié)果,但考慮到HTML / XML標(biāo)準(zhǔn)不給任何意義的順序編寫一個(gè)標(biāo)簽的屬性,屬性的優(yōu)先級機(jī)制必須建立自己為了確保這將正常工作。

因此,所有的Thymeleaf屬性都定義了一個(gè)數(shù)字優(yōu)先級,它確定了它們在標(biāo)記中執(zhí)行的順序。這個(gè)順序是:

Order

Feature

Attributes

1

Fragment inclusion

th:insert
th:replace

2

Fragment iteration

th:each

3

Conditional evaluation

th:if
th:unless
th:switch
th:case

4

Local variable definition

th:object
th:with

5

General attribute modification

th:attr
th:attrprepend
th:attrappend

6

Specific attribute modification

th:value
th:href
th:src
...

7

Text (tag body modification)

th:text
th:utext

8

Fragment specification

th:fragment

9

Fragment removal

th:remove

這種優(yōu)先機(jī)制意味著,如果屬性位置顛倒,上面的迭代片段將給出完全相同的結(jié)果(盡管它的可讀性稍微差一些):

11 注釋

11.1. 標(biāo)志HTML/XML注釋

標(biāo)準(zhǔn)HTML/XML注釋<!——……——>可以在Thymeleaf模板的任何地方使用。這些評論中的任何內(nèi)容都不會被Thymeleaf處理,并將逐字復(fù)制到結(jié)果:

<!-- User info follows --> <div th:text="${...}">... </div>

11.2. Thymeleaf 注釋模塊

解析器級別的注釋塊是在Thymeleaf解析模板時(shí)從模板中簡單刪除的代碼。它們是這樣的:

<!--/* This code will be removed at Thymeleaf parsing time! */-->

Thymeleaf將刪除<!——/**/——>,所以這些注釋塊也可以用于靜態(tài)打開模板時(shí)顯示代碼,知道當(dāng)Thymeleaf處理它時(shí),它會被刪除:

<!--/*--> <div>you can see me only before Thymeleaf processes me!</div> <!--*/-->

11.3. Thymeleaf原型注釋

Thymeleaf允許在靜態(tài)打開模板時(shí)(即作為原型)將特殊的注釋塊定義為注釋,但在執(zhí)行模板時(shí)將其視為普通標(biāo)記。

<span>hello!</span> <!--/*/<div th:text="${...}">...</div> /*/--> <span>goodbye!</span>

Thymeleaf的解析系統(tǒng)將簡單地刪除<!—/*//*/—>標(biāo)記,但不包括其內(nèi)容,因此將不注釋這些內(nèi)容。所以在執(zhí)行模板時(shí),Thymeleaf會看到:

<span>hello!</span><div th:text="${...}">...</div><span>goodbye!</span>

與解析器級別的注釋塊一樣,這個(gè)特性與方言無關(guān)。

11.4.th:block

標(biāo)準(zhǔn)方言中包含的惟一元素處理器(不是屬性)th:block

block僅僅是一個(gè)屬性容器,它允許模板開發(fā)人員指定他們想要的任何屬性。Thymeleaf將執(zhí)行這些屬性,然后使塊(而不是其內(nèi)容)消失。

因此,它可能很有用,例如,當(dāng)創(chuàng)建迭代表時(shí),每個(gè)元素需要一個(gè)以上的<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>

當(dāng)與僅原型的注釋塊結(jié)合使用時(shí)尤其有用:

<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>

注意這個(gè)解決方案是如何允許模板是有效的HTML(不需要在中添加禁止的

),并且在瀏覽器中作為原型靜態(tài)打開時(shí)仍然可以正常工作!

12 Thymeleaf嵌入

12.1 表達(dá)式嵌入

盡管標(biāo)準(zhǔn)方言允許我們使用標(biāo)記屬性做幾乎所有的事情,但是在某些情況下,我們更喜歡直接將表達(dá)式寫入HTML文本。例如,我們可以這樣寫:

<p>Hello, [[${session.user.name}]]!</p>

……不是這樣的:

<p>Hello, <span th:text="${session.user.name}">Sebastian</span>!</p>

之間的表達(dá)式[[…]][(…)]Thymeleaf中被認(rèn)為是內(nèi)聯(lián)表達(dá)式,在它們內(nèi)部,我們可以使用在th:textth:utext屬性中也有效的任何類型的表達(dá)式。

注意,然而[[……]]對應(yīng)于th:text(即結(jié)果為html轉(zhuǎn)義)[(…)]對應(yīng)于th:utext不執(zhí)行任何html轉(zhuǎn)義。因此,對于msg = '這是這樣的變量,很好!',給定這個(gè)片段:

<p>The message is "[(${msg})]"</p>

嵌入vs非嵌入

如果您來自以這種方式輸出文本為標(biāo)準(zhǔn)的其他模板引擎,您可能會問:為什么我們不從一開始就這樣做呢?它的代碼比所有那些th:text屬性都要少!

那么,在這里要小心,因?yàn)楸M管您可能會發(fā)現(xiàn)內(nèi)聯(lián)非常有趣,但是您應(yīng)該始終記住,當(dāng)靜態(tài)打開內(nèi)聯(lián)表達(dá)式時(shí),它們將在HTML文件中逐字顯示,所以您可能再也不能將它們用作設(shè)計(jì)原型了!

瀏覽器如何在不使用內(nèi)聯(lián)的情況下靜態(tài)顯示我們的代碼片段……

Hello, Sebastian!
  • 并利用這
Hello, [[${session.user.name}]]!

就設(shè)計(jì)的實(shí)用性而言,……是相當(dāng)清楚的。

禁止嵌入

但是可以禁用此機(jī)制,因?yàn)樵谀承┣闆r下,我們確實(shí)希望輸出[[…]][(…)]序列,但不將其內(nèi)容作為表達(dá)式處理。為此,我們將使用th:inline="none":

<p th:inline="none">A double array looks like this: [[1, 2, 3], [4, 5]]!</p>

12.2 文本嵌入

文本內(nèi)聯(lián)非常類似于我們剛才看到的表達(dá)式內(nèi)聯(lián)功能,但是它實(shí)際上增加了更多的功能。它必須顯式啟用th:inline="text"

文本內(nèi)聯(lián)不僅允許我們使用我們剛才看到的相同的內(nèi)聯(lián)表達(dá)式,而且實(shí)際上還可以像處理文本模板模式中的模板那樣處理標(biāo)記體,這允許我們執(zhí)行基于文本的模板邏輯(不僅僅是輸出表達(dá)式)

我們將在下一章的文本模板模式中看到更多關(guān)于這一點(diǎn)的內(nèi)容。

12.3 JavaScript 嵌入

JavaScript內(nèi)聯(lián)允許在HTML模板模式下處理的模板中更好地集成JavaScript

與文本內(nèi)聯(lián)一樣,這實(shí)際上相當(dāng)于在JAVASCRIPT模板模式下像處理模板一樣處理腳本內(nèi)容,因此文本模板模式的所有功能(參見下一章)就在手邊。然而,在本節(jié)中,我們將重點(diǎn)介紹如何使用它將Thymeleaf表達(dá)式的輸出添加到JavaScript塊中。

這個(gè)模式必須顯式啟用使用th:inline="javascript":

<script th:inline="javascript">...var username = [[${session.user.name}]];... </script>

這將導(dǎo)致

<script th:inline="javascript">...var username = "Sebastian \"Fruity\" Applejuice";... </script>

在上面的代碼中需要注意兩件重要的事情:

首先,JavaScript內(nèi)聯(lián)不僅輸出所需的文本,還用引號和JavaScript—轉(zhuǎn)義其內(nèi)容將表達(dá)式結(jié)果作為格式良好的JavaScript文本輸出。

其次,這是因?yàn)槲覀儗?/span>${session.user.name}表達(dá)式作為轉(zhuǎn)義輸出,即使用一個(gè)雙尖括號表達(dá)式:[[${session.user.name}]]。如果我們使用unescape:

<script th:inline="javascript">...var username = [(${session.user.name})];... </script>

結(jié)果會是這樣的:

<script th:inline="javascript">...var username = Sebastian "Fruity" Applejuice;... </script>

這是格式錯(cuò)誤的JavaScript代碼。但是,如果要通過追加內(nèi)聯(lián)表達(dá)式來構(gòu)建腳本的某些部分,輸出未轉(zhuǎn)義的內(nèi)容可能是我們所需要的,所以手邊有這個(gè)工具是很好的。

JavaScript 非嵌入寫法

前面提到的JavaScript內(nèi)聯(lián)機(jī)制的智能遠(yuǎn)遠(yuǎn)不止應(yīng)用JavaScript特有的轉(zhuǎn)義和將表達(dá)式結(jié)果作為有效的文本輸出。

例如,我們可以將(轉(zhuǎn)義)內(nèi)聯(lián)表達(dá)式包裝在JavaScript注釋中,如:

<script th:inline="javascript">...var username = /*[[${session.user.name}]]*/ "Gertrud Kiwifruit";... </script>

Thymeleaf會忽略我們在注釋后面和分號之前寫的所有東西(在本例中是“Gertrud Kiwifruit”),所以執(zhí)行這個(gè)的結(jié)果看起來就像我們沒有使用包裝注釋的時(shí)候:

<script th:inline="javascript">...var username = "Sebastian \"Fruity\" Applejuice";... </script>

但是再仔細(xì)看看原始模板代碼:

<script th:inline="javascript">...var username = /*[[${session.user.name}]]*/ "Gertrud Kiwifruit";... </script>

注意這是有效的JavaScript代碼。當(dāng)您以靜態(tài)方式(而不是在服務(wù)器上執(zhí)行)打開模板文件時(shí),它將完美地執(zhí)行。

所以我們這里有一種做JavaScript自然模板的方法!

嵌入求值和js序列號

關(guān)于JavaScript內(nèi)聯(lián)需要注意的一件重要事情是,這個(gè)表達(dá)式的求值是智能的,不限于字符串。Thymeleaf將正確地用JavaScript語法編寫以下類型的對象:

  • Strings
  • Numbers
  • Booleans
  • Arrays
  • Collections
  • Maps
  • Beans (objects with?getter?and?setter?methods)

例如,如果我們有以下代碼:

<script th:inline="javascript">...var user = /*[[${session.user}]]*/ null;... </script>

$ {session.user}表達(dá)式將求值為user對象,Thymeleaf將其正確轉(zhuǎn)換為Javascript語法:

<script th:inline="javascript">...var user = {"age":null,"firstName":"John","lastName":"Apricot","name":"John Apricot","nationality":"Antarctica"};... </script>

這種JavaScript序列化的實(shí)現(xiàn)方式是通過org.thymeleaf.standard.serializer的實(shí)現(xiàn)實(shí)現(xiàn)的。IStandardJavaScriptSerializer接口,可以在模板引擎使用的標(biāo)準(zhǔn)方言的實(shí)例中配置該接口。

這個(gè)JS序列化機(jī)制的默認(rèn)實(shí)現(xiàn)將在類路徑中查找Jackson庫,如果存在,將使用它。如果不是,它將應(yīng)用一個(gè)內(nèi)置的序列化機(jī)制,該機(jī)制覆蓋大多數(shù)場景的需求,并產(chǎn)生類似的結(jié)果(但靈活性較差)

12.4 CSS嵌入

Thymeleaf還允許在CSS

<style th:inline="css">... </style>

例如,我們有兩個(gè)變量被設(shè)置為兩個(gè)不同的字符串值:

classname = 'main elems' align = 'center'

我們可以這樣使用它們:

<style th:inline="css">.[[${classname}]] {text-align: [[${align}]];} </style>

結(jié)果會是:

<style th:inline="css">.main\ elems {text-align: center;} </style>

注意CSS內(nèi)聯(lián)也有一些智能,就像JavaScript一樣。具體地說,通過轉(zhuǎn)義表達(dá)式([[${classname}]])輸出的表達(dá)式將轉(zhuǎn)義為CSS標(biāo)識符。這就是為什么我們的classname = 'main elems'在上面的代碼片段中變成了main\ elems

高級特性

與之前對javascript的解釋類似,CSS內(nèi)聯(lián)還允許我們的<style>標(biāo)記靜態(tài)和動態(tài)地工作,即通過將內(nèi)聯(lián)表達(dá)式包裝在注釋中作為CSS自然模板。見:

<style th:inline="css">.main\ elems {text-align: /*[[${align}]]*/ left;} </style>

13 文本模式

13.1 文本語法

三種Thymeleaf模板模式被認(rèn)為是文本模式:文本、JAVASCRIPTCSS。這將它們與標(biāo)記模板模式(HTMLXML)區(qū)分開來。

文本模板模式和標(biāo)記模式之間的關(guān)鍵區(qū)別在于,在文本模板中沒有以屬性形式插入邏輯的標(biāo)記,因此我們必須依賴其他機(jī)制。

第一個(gè)也是最基本的機(jī)制是內(nèi)聯(lián),我們在上一章已經(jīng)詳細(xì)介紹過了。內(nèi)聯(lián)語法是在文本模板模式下輸出表達(dá)式結(jié)果的最簡單方法,因此這是一個(gè)非常有效的文本電子郵件模板。

Dear [(${name})],Please find attached the results of the report you requestedwith name "[(${report.name})]".Sincerely,The Reporter.

即使沒有標(biāo)記,上面的示例也是一個(gè)完整而有效的Thymeleaf模板,可以在文本模板模式下執(zhí)行。

但是為了包含比輸出表達(dá)式更復(fù)雜的邏輯,我們需要一種新的非基于標(biāo)記的語法:

[# th:each="item : ${items}"]- [(${item})] [/]

這實(shí)際上是更詳細(xì)的壓縮版本:

[#th:block th:each="item : ${items}"]- [#th:block th:utext="${item}" /] [/th:block]

注意這個(gè)新語法是如何基于聲明為[#element…而不是。元素是開放的,比如[#元素…]和像[/element]一樣關(guān)閉的標(biāo)簽,獨(dú)立標(biāo)簽可以通過使用/最小化打開的元素來聲明,其方式幾乎等同于XML標(biāo)簽:[#element…/)。

?

標(biāo)準(zhǔn)方言只包含這些元素之一的處理器:已知的th:塊,盡管我們可以在方言中擴(kuò)展它并以通常的方式創(chuàng)建新元素。另外,第th:block元素([#th:block…]))……[/th:block])可以縮寫為空字符串([#…)……[/]),所以上面的block實(shí)際上等于:

[# th:each="item : ${items}"]- [# th:utext="${item}" /] [/]

假設(shè)[# th:utext="${item}" /]相當(dāng)于一個(gè)內(nèi)聯(lián)的未轉(zhuǎn)義表達(dá)式,我們可以使用它來減少代碼。這樣我們就得到了上面看到的第一個(gè)代碼片段:

[# th:each="item : ${items}"]- [(${item})] [/]

注意,文本語法需要完全的元素平衡(沒有未關(guān)閉的標(biāo)記)和引號屬性——它更像xml風(fēng)格,而不是html風(fēng)格。

讓我們來看看一個(gè)更完整的文本模板的例子,一個(gè)純文本電子郵件模板:

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

執(zhí)行之后,結(jié)果可能是這樣的:

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/kgThanks,The Thymeleaf Shop

另一個(gè)JAVASCRIPT模板模式的例子是一個(gè)greeting .js文件,我們將其作為文本模板處理,并從HTML頁面調(diào)用結(jié)果。注意,這不是HTML模板中的

var greeter = function() {var username = [[${session.user.name}]];[# th:each="salut : ${salutations}"] alert([[${salut}]] + " " + username);[/]};

執(zhí)行之后,結(jié)果可能是這樣的:

var greeter = function() {var username = "Bertrand \"Crunchy\" Pear";alert("Hello" + " " + username);alert("Ol\u00E1" + " " + username);alert("Hola" + " " + username);};

忽略元素屬性

為了避免與可能以其他模式(例如HTML模板中的文本模式內(nèi)聯(lián))處理的模板部分進(jìn)行交互,Thymeleaf 3.0允許轉(zhuǎn)義文本語法中元素的屬性。所以:

  • Attributes in?TEXT?template mode will be?HTML-unescaped.
  • Attributes in?JAVASCRIPT?template mode will be?JavaScript-unescaped.
  • Attributes in?CSS?template mode will be?CSS-unescaped.

所以這在文本模式模板中是完全可以的(注意>):

[# th:if="${120&lt;user.age}"]Congratulations![/]

當(dāng)然了。這在實(shí)際的文本模板中是沒有意義的,但是如果我們使用th:inline="text"塊處理HTML模板(其中包含上述代碼),并且希望確保瀏覽器不接受

13.2擴(kuò)展

這種語法的優(yōu)點(diǎn)之一是它與標(biāo)記語法一樣具有可擴(kuò)展性。開發(fā)人員仍然可以使用自定義元素和屬性定義自己的方言,對它們應(yīng)用前綴(可選),然后在文本模板模式中使用它們:

[#myorg:dosomething myorg:importantattr="211"]some text[/myorg:dosomething]

13.3 原型注釋

JAVASCRIPT和CSS模板模式(文本不可用)允許在特殊的注釋語法/*[+…+]*/使Thymeleaf在處理模板時(shí)自動取消注釋:

var x = 23;/*[+var msg = "This is a working application";+]*/var f = function() {...

13.4 文本解析器級注釋塊:刪除代碼

與僅使用原型的注釋塊類似,所有三種文本模板模式(文本、JAVASCRIPTCSS)都可以指示Thymeleaf刪除特殊/*[- *//* -]*/標(biāo)記之間的代碼,如下所示:

13.5 JavaScript and CSS 模板

如上一章所述,JavaScriptCSS內(nèi)聯(lián)提供了在JavaScript/CSS注釋中包含內(nèi)聯(lián)表達(dá)式的可能性,例如:

這是有效的JavaScript,一旦執(zhí)行可能像:

... var username = "John Apricot"; ...

在注釋中封裝內(nèi)聯(lián)表達(dá)式的相同技巧實(shí)際上可以用于整個(gè)文本模式語法:

/*[# th:if="${user.admin}"]*/alert('Welcome admin');/*[/]*/

當(dāng)模板靜態(tài)打開時(shí)(因?yàn)樗?/span>100%有效的JavaScript),以及當(dāng)模板運(yùn)行時(shí)(如果用戶是管理員),上面代碼中的警告將顯示出來。它等于:

[# th:if="${user.admin}"]alert('Welcome admin');[/]

這實(shí)際上是在模板解析期間將初始版本轉(zhuǎn)換為的代碼。

但是請注意,在注釋中包裝元素并不會清除它們所在的行(a;如內(nèi)聯(lián)輸出表達(dá)式所做的那樣。這種行為只保留給內(nèi)聯(lián)輸出表達(dá)式。

因此,Thymeleaf 3.0允許以自然模板的形式開發(fā)復(fù)雜的JavaScript腳本和CSS樣式表,作為原型和工作模板都有效。

14雜貨店(略)

15 配置

15.1 模板解析

為了我們的Thymes虛擬雜貨店,我們選擇了一個(gè)名為servletcontextITemplateResolver實(shí)現(xiàn),它允許我們從Servlet上下文中獲取模板作為資源。

除了讓我們能夠通過實(shí)現(xiàn)ITemplateResolver來創(chuàng)建自己的模板解析器之外,Thymeleaf還提供了四個(gè)開箱即用的實(shí)現(xiàn):

org.thymeleaf.templateresolverClassLoaderTemplateResolver,它將模板解析為類加載器資源,如:

return Thread.currentThread().getContextClassLoader().getResourceAsStream(template);

org.thymeleaf.templateresolverFileTemplateResolver,它將模板解析為文件系統(tǒng)中的文件,如:

return new FileInputStream(new File(template));

org.thymeleaf.templateresolverUrlTemplateResolver,它將模板解析為url(甚至是非本地的),比如:

return (new URL(template)).openStream();

org.thymeleaf.templateresolverStringTemplateResolver,它直接將模板解析為指定為模板的字符串(或模板名稱,在本例中顯然不僅僅是一個(gè)名稱):

return new StringReader(templateName);

ITemplateResolver的所有預(yù)綁定實(shí)現(xiàn)都允許相同的一組配置參數(shù),其中包括:

templateResolver.setPrefix("/WEB-INF/templates/"); templateResolver.setSuffix(".html");

允許使用與文件名不直接對應(yīng)的模板名稱的emplate別名。如果前綴/前綴和別名都存在,則在前綴/后綴之前應(yīng)用別名:

templateResolver.addTemplateAlias("adminHome","profiles/admin/home"); templateResolver.setTemplateAliases(aliasesMap);

讀取模板時(shí)應(yīng)用的編碼:

templateResolver.setEncoding("UTF-8");

使用的模板模式:

// Default is HTML templateResolver.setTemplateMode("XML");

模板緩存的默認(rèn)模式,以及定義特定模板是否可緩存的模式:

// Default is true templateResolver.setCacheable(false); templateResolver.getCacheablePatternSpec().addPattern("/users/*");

TTL(以毫秒為單位)表示源自此模板解析器的已解析模板緩存項(xiàng)。如果沒有設(shè)置,從緩存中刪除條目的唯一方法是超過緩存的最大大小(最老的條目將被刪除)

? // Default is no TTL (only cache size exceeded would remove entries) templateResolver.setCacheTTLMs(60000L);

?

Thymeleaf + Spring集成包提供了一個(gè)SpringResourceTemplateResolver實(shí)現(xiàn),它使用所有Spring基礎(chǔ)設(shè)施來訪問和讀取應(yīng)用程序中的資源,這是支持Spring的應(yīng)用程序中推薦的實(shí)現(xiàn)。

?

模板解析器鏈

此外,模板引擎可以指定多個(gè)模板解析器,在這種情況下,可以在它們之間建立一個(gè)模板解析順序,這樣,如果第一個(gè)解析器無法解析模板,就會請求第二個(gè)解析器,以此類推:

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);

當(dāng)應(yīng)用多個(gè)模板解析器時(shí),建議為每個(gè)模板解析器指定模式,以便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實(shí)現(xiàn)的特定功能。請注意,并不是所有的實(shí)現(xiàn)都能夠在解析之前確定模板的存在,因此總是可以認(rèn)為模板是可解析的,并打破解析鏈(不允許其他解析器檢查相同的模板),但隨后無法讀取真正的資源。

所有包含在核心Thymeleaf中的ITemplateResolver實(shí)現(xiàn)都包含一種機(jī)制,它允許我們在考慮可解析資源之前,讓解析器真正檢查資源是否存在。它是checkExistence標(biāo)志,工作方式如下:

ClassLoaderTemplateResolver classLoaderTemplateResolver = new ClassLoaderTemplateResolver(); classLoaderTemplateResolver.setOrder(Integer.valueOf(1)); classLoaderTempalteResolver.setCheckExistence(true);

這個(gè)checkExistence標(biāo)志強(qiáng)制解析器在解析階段對資源存在性執(zhí)行真正的檢查(如果exist check返回false,則調(diào)用鏈中的以下解析器)。雖然這聽起來可能在所有情況下,在大多數(shù)情況下,這將意味著雙重訪問資源本身(一次檢查存在,另一個(gè)時(shí)間閱讀),和可能是一個(gè)性能問題在某些場景中,例如遠(yuǎn)程url模板資源——一個(gè)潛在的性能問題,可能無論如何被使用在很大程度上減輕模板緩存(在這種情況下, ,模板只能解決他們第一次訪問)

15.2 消息解析器

我們沒有為雜貨應(yīng)用程序顯式地指定消息解析器實(shí)現(xiàn),正如前面所解釋的,這意味著所使用的實(shí)現(xiàn)是一個(gè)org.thymeleaf.messageresolverStandardMessageResolver對象。

StandardMessageResolverIMessageResolver接口的標(biāo)準(zhǔn)實(shí)現(xiàn),但是我們可以根據(jù)應(yīng)用程序的特定需求創(chuàng)建自己的接口。

默認(rèn)情況下,Thymeleaf + Spring集成包提供一個(gè)IMessageResolver實(shí)現(xiàn),它使用標(biāo)準(zhǔn)的Spring方式檢索外部化的消息,方法是使用在Spring應(yīng)用程序上下文中聲明的MessageSource bean

標(biāo)準(zhǔn)消息解析器

那么StandardMessageResolver如何查找特定模板中請求的消息呢?

如果模板名稱是home并且位于/WEB-INF/templates/home中。,而請求的語言環(huán)境是gl_ES,那么這個(gè)解析器將在以下文件中查找消息,順序如下:

  • /WEB-INF/templates/home_gl_ES.properties
  • /WEB-INF/templates/home_gl.properties
  • /WEB-INF/templates/home.properties

有關(guān)完整的消息解析機(jī)制如何工作的詳細(xì)信息,請參閱StandardMessageResolver類的JavaDoc文檔。

配置消息解析器

如果我們想向模板引擎中添加消息解析器(或更多),該怎么辦?容易:

// For setting only one templateEngine.setMessageResolver(messageResolver);// For setting more than one templateEngine.addMessageResolver(messageResolver);

為什么我們希望有多個(gè)消息解析器?與模板解析器的原因相同:消息解析器是有序的,如果第一個(gè)解析器不能解析特定的消息,將詢問第二個(gè)解析器,然后詢問第三個(gè)解析器,等等。

15.3 轉(zhuǎn)換服務(wù)

使我們能夠通過雙大括號語法(${{…}})執(zhí)行數(shù)據(jù)轉(zhuǎn)換和格式化操作的轉(zhuǎn)換服務(wù)實(shí)際上是標(biāo)準(zhǔn)方言的一個(gè)特性,而不是Thymeleaf模板引擎本身的特性。

因此,配置它的方法是將IStandardConversionService接口的自定義實(shí)現(xiàn)直接設(shè)置到正在配置到模板引擎中的StandardDialect實(shí)例中。如:

? IStandardConversionService customConversionService = ...StandardDialect dialect = new StandardDialect(); dialect.setConversionService(customConversionService);templateEngine.setDialect(dialect);

?

請注意,Thymeleaf -spring3Thymeleaf -spring4包包含SpringStandardDialect,并且該方言已經(jīng)預(yù)先配置了IStandardConversionService的實(shí)現(xiàn),該實(shí)現(xiàn)將Spring自己的轉(zhuǎn)換服務(wù)基礎(chǔ)結(jié)構(gòu)集成到Thymeleaf中。

?

15.4 日志

Thymeleaf非常關(guān)注日志記錄,并且總是試圖通過它的日志接口提供最大數(shù)量的有用信息。

所使用的日志庫是slf4j,它實(shí)際上充當(dāng)?shù)轿覀兛赡芟M趹?yīng)用程序中使用的任何日志實(shí)現(xiàn)(例如log4j)的橋梁。

Thymeleaf類將記錄跟蹤、調(diào)試和信息級別的信息,這取決于我們想要的詳細(xì)程度,除了一般的日志記錄,它還將使用三個(gè)與TemplateEngine類相關(guān)聯(lián)的特殊日志記錄器,我們可以為不同的目的分別配置它們:

  • org.thymeleaf.TemplateEngine.CONFIG?will output detailed configuration of the library during initialization.
  • org.thymeleaf.TemplateEngine.TIMER?will output information about the amount of time taken to process each template (useful for benchmarking!)
  • org.thymeleaf.TemplateEngine.cache?is the prefix for a set of loggers that output specific information about the caches. Although the names of the cache loggers are configurable by the user and thus could change, by default they are:
    • org.thymeleaf.TemplateEngine.cache.TEMPLATE_CACHE
    • org.thymeleaf.TemplateEngine.cache.EXPRESSION_CACHE

使用log4jThymeleaf日志記錄基礎(chǔ)結(jié)構(gòu)的一個(gè)示例配置可以是:

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工作由于一組解析器——標(biāo)記和文本模板解析成的事件序列(開放標(biāo)簽、文本結(jié)束標(biāo)記、評論等)和一系列的處理器——一個(gè)為每個(gè)類型的行為,需要應(yīng)用,修改模板解析事件序列為了創(chuàng)建我們期待的結(jié)果。通過結(jié)合原始模板與我們的數(shù)據(jù)。

默認(rèn)情況下,它還包括存儲已解析模板的緩存;在處理模板文件之前讀取和解析模板文件所產(chǎn)生的事件序列。這在web應(yīng)用程序中尤其有用,它基于以下概念:

17 離線模板

17.1 離線模板的概念(分離thymeleafdom

到目前為止,我們在雜貨店中使用的模板都是按照通常的方式完成的,將邏輯以屬性的形式插入到模板中。

但是,Thymeleaf還允許我們將模板標(biāo)記與其邏輯完全解耦,允許在HTMLXML模板模式中創(chuàng)建完全沒有邏輯的標(biāo)記模板。

其主要思想是模板邏輯將在一個(gè)單獨(dú)的邏輯文件中定義(更確切地說,是一個(gè)邏輯資源,因?yàn)樗恍枰且粋€(gè)文件)。默認(rèn)情況下,該邏輯資源將是與模板文件位于相同位置(例如文件夾)的另一個(gè)文件,具有相同的名稱,但是.th.xml擴(kuò)展名:

/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代碼。這是一個(gè)沒有Thymeleaf或模板知識的設(shè)計(jì)人員可以創(chuàng)建、編輯和/或理解的模板文件。或者由完全沒有Thymeleaf鉤子的外部系統(tǒng)提供的HTML片段。

現(xiàn)在讓我們通過創(chuàng)建額外的home.th.xml文件將home.html模板轉(zhuǎn)換為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>

在這里,我們可以在thlogic塊中看到許多標(biāo)記。這些標(biāo)記通過它們的sel屬性對原始模板的節(jié)點(diǎn)執(zhí)行屬性注入,這些sel屬性包含Thymeleaf標(biāo)記選擇器(實(shí)際上是AttoParser標(biāo)記選擇器)。

還請注意,標(biāo)記可以嵌套,以便追加它們的選擇器。例如,上面的sel="/tr[0]"將被處理為sel="#usersTable/tr[0]"。用戶名的選擇器將被處理為sel="#usersTable/tr[0]//td.username"。

因此,一旦合并,上述兩個(gè)文件將是相同的:

<!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>

這看起來更熟悉,而且實(shí)際上比創(chuàng)建兩個(gè)單獨(dú)的文件更簡單。但是解耦模板的優(yōu)點(diǎn)是,我們可以使模板完全獨(dú)立于Thymeleaf,因此從設(shè)計(jì)的角度來看,具有更好的可維護(hù)性。

當(dāng)然,設(shè)計(jì)人員或開發(fā)人員之間仍然需要一些契約例如,用戶需要id="usersTable"—但是在許多場景中,純html模板將是設(shè)計(jì)和開發(fā)團(tuán)隊(duì)之間更好的通信工件。

17.2 配置離線模板

開啟離線模板

默認(rèn)情況下,不會期望每個(gè)模板都具有解耦邏輯。相反,配置的模板解析器(ITemplateResolver的實(shí)現(xiàn))將需要專門將它們解析的模板標(biāo)記為使用解耦邏輯。

除了StringTemplateResolver(它不支持解耦邏輯)之外,ITemplateResolver的所有其他開箱即用實(shí)現(xiàn)都將提供一個(gè)名為usedeconpledlogic的標(biāo)志,該標(biāo)志將把該解耦器解析的所有模板標(biāo)記為所有或部分邏輯都可能位于單獨(dú)的資源中:

final ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver(servletContext); ... templateResolver.setUseDecoupledLogic(true);

混用在線/離線模板

在啟用時(shí),不需要解耦模板邏輯。當(dāng)啟用時(shí),這意味著引擎將查找包含解耦邏輯的資源,如果它存在,則解析它并將其與原始模板合并。如果解耦邏輯資源不存在,則不會拋出錯(cuò)誤。

同樣,在同一個(gè)模板中,我們可以混合耦合和解耦邏輯,例如在原始模板文件中添加一些Thymeleaf屬性,而將其他屬性留給單獨(dú)的解耦邏輯文件。最常見的情況是使用新的th:ref屬性(v3.0)

17.3 ref屬性

ref只是一個(gè)標(biāo)記屬性。從處理的角度來看,它什么也不做,只是在處理模板時(shí)消失,但它的有用之處在于它作為標(biāo)記引用,即它可以像標(biāo)記名稱或片段(th:fragment)一樣通過標(biāo)記選擇器中的名稱解析。

如果我們有一個(gè)選擇器

<attr sel="whatever" .../>

這將匹配:

  • Any?<whatever>?tags.
  • Any tags with a?th:fragment="whatever"?attribute.
  • Any tags with a?th:ref="whatever"?attribute.

例如,使用純html id屬性th:ref的優(yōu)點(diǎn)是什么?僅僅是因?yàn)槲覀兛赡懿幌M驑?biāo)記中添加這么多id和類屬性來充當(dāng)邏輯錨,這最終可能會污染我們的輸出。

同樣的道理,th:ref的缺點(diǎn)是什么?顯然,我們需要在模板中添加一些Thymeleaf邏輯(“邏輯”)

請注意th:ref屬性的這種適用性不僅適用于解耦的邏輯模板文件:它在其他類型的場景中也同樣適用,比如片段表達(dá)式(~{…})

17.4 離線模板的性能

影響非常小。當(dāng)一個(gè)已解析的模板被標(biāo)記為使用解耦邏輯而沒有被緩存時(shí),模板邏輯資源將首先被解析、解析并處理為內(nèi)存中指令的安全:基本上是要注入到每個(gè)標(biāo)記選擇器中的屬性列表。

但這是惟一需要的額外步驟,因?yàn)樵诖酥?#xff0c;將解析實(shí)際的模板,在解析模板時(shí),解析器本身將動態(tài)地注入這些屬性,這要感謝AttoParser中的節(jié)點(diǎn)選擇高級功能。因此,解析后的節(jié)點(diǎn)將從解析器中出來,就好像它們的注入屬性寫在原始模板文件中一樣。

這最大的好處是什么?當(dāng)將模板配置為緩存時(shí),它將被緩存,其中已經(jīng)包含注入的屬性。因此,對可緩存模板使用解耦模板的開銷,一旦它們被緩存,將絕對為零。

17.5 何時(shí)分離邏輯代碼和樣式(離線模板)

Thymeleaf解析與每個(gè)模板對應(yīng)的解耦邏輯資源的方式可由用戶配置。它由一個(gè)擴(kuò)展點(diǎn)確定

ideconpledtemplatelogicresolver,它提供了一個(gè)默認(rèn)的實(shí)現(xiàn):standarddeconpledtemplatelogicresolver

這個(gè)標(biāo)準(zhǔn)實(shí)現(xiàn)做什么?

首先,它對模板資源的基名稱應(yīng)用前綴和后綴(通過ITemplateResource#getBaseName()方法獲得)。前綴和后綴都可以配置,默認(rèn)情況下,前綴為空,后綴為.th.xml

其次,它要求模板資源通過ITemplateResource#relative(String relativeLocation)方法解析具有計(jì)算名稱的相對資源。

使用的ideconpledtemplatelogicresolver的具體實(shí)現(xiàn)可以在TemplateEngine上輕松配置:

final StandardDecoupledTemplateLogicResolver decoupledresolver = new StandardDecoupledTemplateLogicResolver(); decoupledResolver.setPrefix("../viewlogic/"); ... templateEngine.setDecoupledTemplateLogicResolver(decoupledResolver);

?

?

?

?

總結(jié)

以上是生活随笔為你收集整理的fundamentals\java\Thymeleaf的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。