fundamentals\java\Thymeleaf
Thymeleaf
翻譯自:https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html
up主用google蝦譯的,和up主完全無關,強裂建議直接看原文。
主要就是幾個符號啦:
消息:#{}
表達式有作用域:${}?
表達式無作用域:*{}
url:@{}
相對地址:~{}???
js內轉義特殊字符:[[${})]
js內原生格式:[(${})],
dom屬性:th:each th:object th:href th:url
內置函數(shù):#ctx #request ......
Thymeleaf也沒有什么大不了的。
目錄
Thymeleaf
1簡介
1.1什么是Thymeleaf?
1.2 Thymeleaf可以處理什么樣的模板?
1.3方言:標準方言
2 設計良好的虛擬雜貨店
2.1雜貨店網站
2.2創(chuàng)建和配置模板引擎
模板解析器
模板引擎
3 文本
3.1多語言的歡迎
使用th:文本和外部化文本
上下文
執(zhí)行模板引擎
3.2更多關于文本和變量的信息
非轉義的文本
使用變量
4標準表達式語法
4.1 消息
?4.2 變量
基本對象表達式
擴展表達式對象
4.3表達式的作用域(星號語法)
4.4 URL鏈接
4.5 模板
4.6 常量
4.7 追加文字
4.8 常量替換
4.9 數(shù)學計算
4.10 比較和相等
4.11 條件表達式
4.12 默認值
4.13 無操作
4.14 數(shù)據(jù)轉換和格式化
4.15 預處理
5 設置屬性
5.1 設置任意屬性
5.2特定屬性賦值
5.3 一次性設置多個值
5.4 追加和追加在前面
5.5 布爾值屬性
5.6 設置任意屬性的值
5.7 支持HTML5屬性
6 迭代
6.1 基礎迭代
th:each
可以被迭代的值
6.2 迭代狀態(tài)
6.3 延遲加載
7 條件表達式
7.1 簡單條件:if 和 unless
7.2 Switch表達式
8 布局模板
8.1 引用模板
定義和引用模板
模板規(guī)格
不使用?th:fragment引用模板
th:insert??th:replace?(and?th:include)區(qū)別
8.2 模板參數(shù)
沒有片段參數(shù)的片段局部變量
模板內斷言
8.3 靈活的布局:不僅僅是片段插入
使用空模板
使用_操作符
模板的高級條件引用
8.4 刪除多余的靜態(tài)html代碼
8.5 布局繼承
9 本地參數(shù)
10屬性優(yōu)先級
11 注釋
11.1. 標志HTML/XML注釋
11.2. Thymeleaf 注釋模塊
11.3. Thymeleaf原型注釋
11.4.th:block
12 Thymeleaf嵌入
12.1 表達式嵌入
嵌入vs非嵌入
禁止嵌入
12.2 文本嵌入
12.3 JavaScript 嵌入
JavaScript 非嵌入寫法
嵌入求值和js序列號
12.4 CSS嵌入
高級特性
13 文本模式
13.1 文本語法
忽略元素屬性
13.2擴展
13.3 原型注釋
13.4 文本解析器級注釋塊:刪除代碼
13.5 JavaScript and CSS 模板
14雜貨店(略)
15 配置
15.1 模板解析器
模板解析器鏈
15.2 消息解析器
標準消息解析器
配置消息解析器
15.3 轉換服務
15.4 日志
16 模板緩存
17 離線模板
17.1 離線模板的概念(分離thymeleaf和dom)
17.2 配置離線模板
開啟離線模板
混用在線/離線模板
17.3 ref屬性
17.4 離線模板的性能
17.5 何時分離邏輯代碼和樣式(離線模板)
1簡介
1.1什么是Thymeleaf?
Thymeleaf是一個用于web和獨立環(huán)境的現(xiàn)代服務器端Java模板引擎,能夠處理HTML、XML、JavaScript、CSS甚至純文本。
Thymeleaf的主要目標是提供一種優(yōu)雅且易于維護的創(chuàng)建模板的方法。為了實現(xiàn)這一點,它基于自然模板的概念,以一種不影響模板作為設計原型使用的方式將其邏輯注入模板文件中。這改善了設計的交流,并彌合了設計和開發(fā)團隊之間的差距。
從一開始,Thymeleaf的設計就考慮到了Web標準——尤其是HTML5——如果需要的話,它允許您創(chuàng)建完全驗證模板。
1.2 Thymeleaf可以處理什么樣的模板?
開箱即用,Thymeleaf允許您處理六種模板,每一種都被稱為模板模式:
- HTML
- XML
- TEXT
- JAVASCRIPT
- CSS
- RAW
有兩種標記模板模式(HTML和XML)、三種文本模板模式(文本、JAVASCRIPT和CSS)和無操作模板模式(RAW)。
HTML模板模式將允許任何類型的HTML輸入,包括HTML5、HTML 4和XHTML。不執(zhí)行驗證或格式良好性檢查,模板代碼/結構將在輸出中得到最大程度的尊重。
XML模板模式將允許XML輸入。在這種情況下,代碼應該是格式良好的—沒有未關閉的標記,沒有未引用的屬性,等等—如果發(fā)現(xiàn)違反格式良好的情況,解析器將拋出異常。注意,不會執(zhí)行(針對DTD或XML模式)驗證。
文本模板模式將允許對非標記性質的模板使用特殊語法。此類模板的示例可能是文本電子郵件或模板化文檔。注意,HTML或XML模板也可以作為文本處理,在這種情況下,它們不會被解析為標記,并且每個標記、DOCTYPE、注釋等都將被視為純文本。
JAVASCRIPT模板模式將允許在Thymeleaf應用程序中處理JAVASCRIPT文件。這意味著能夠像在HTML文件中那樣在JavaScript文件中使用模型數(shù)據(jù),但是使用特定于JavaScript的集成,如專門轉義或自然腳本。JAVASCRIPT模板模式被認為是文本模式,因此使用與文本模板模式相同的特殊語法。
CSS模板模式將允許處理Thymeleaf應用程序中涉及的CSS文件。與JAVASCRIPT模式類似,CSS模板模式也是文本模式,使用來自文本模板模式的特殊處理語法。
原始模板模式將完全不處理模板。它用于將未接觸的資源(文件、URL響應等)插入正在處理的模板中。例如,可以將HTML格式的外部不受控制的資源包含到應用程序模板中,并且安全地知道這些資源可能包含的任何Thymeleaf代碼都不會被執(zhí)行。
1.3方言:標準方言
略
2 設計良好的虛擬雜貨店
本文所示示例的源代碼以及本指南的后續(xù)章節(jié)可以在Good中找到
Thymes Virtual Grocery GitHub repository.
2.1雜貨店網站
略
我們的應用程序還有一個非常簡單的服務層,由包含如下方法的服務對象組成:
public class ProductService {...public List<Product> findAll() {return ProductRepository.getInstance().findAll();}public Product findById(Integer id) {return ProductRepository.getInstance().findById(id);}}在web層,我們的應用程序將有一個過濾器,根據(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接口的實現(xiàn),從服務中檢索數(shù)據(jù),并使用ITemplateEngine對象處理模板
最后,它會是這樣的:
但是首先讓我們看看模板引擎是如何初始化的。
2.2創(chuàng)建和配置模板引擎
我們過濾器中的process(…)方法包含這一行:
ITemplateEngine templateEngine = this.application.getTemplateEngine();這意味著GTVGApplication類負責創(chuàng)建和配置Thymeleaf應用程序中最重要的對象之一:TemplateEngine實例(ITemplateEngine接口的實現(xiàn))。
我們的org.thymeleaf。TemplateEngine對象是這樣初始化的:
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);模板解析器是實現(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); }這些對象負責決定如何訪問我們的模板,在這個GTVG應用程序中,是org.thymeleaf.templateresolver。servletcontext意味著我們將從Servlet上下文中檢索模板文件作為資源:一個應用程序范圍的javax.servlet。ServletContext對象,它存在于每個Java web應用程序中,并解析來自web應用程序根目錄的資源。
但這并不是關于模板解析器的全部內容,因為我們可以在其上設置一些配置參數(shù)。第一,模板模式:
templateResolver.setTemplateMode(TemplateMode.HTML);HTML是servlet context ttemplateresolver的默認模板模式,但是無論如何建立它都是一個很好的實踐,這樣我們的代碼就可以清楚地記錄發(fā)生了什么。
templateResolver.setPrefix("/WEB-INF/templates/"); templateResolver.setSuffix(".html");前綴和后綴修改我們將傳遞給引擎的模板名稱,以獲得要使用的實際資源名稱。
使用此配置,模板名稱“product/list”對應于:
servletContext.getResourceAsStream("/WEB-INF/templates/product/list.html")可選地,通過cacheTTLMs屬性在模板解析器上配置已解析模板可以駐留在緩存中的時間:
templateResolver.setCacheTTLMs(3600000L);如果達到最大緩存大小,并且它是當前緩存的最古老的條目,那么在到達TTL之前,仍然可以從緩存中驅逐模板。
用戶可以通過實現(xiàn)ICacheManager接口或修改StandardCacheManager對象來管理默認緩存來定義緩存行為和大小。
關于模板解析器還有很多要了解的,但是現(xiàn)在讓我們來看看模板引擎對象的創(chuàng)建。
模板引擎
模板引擎對象是org.thymeleaf的實現(xiàn)。ITemplateEngine接口。這些實現(xiàn)之一是由Thymeleaf核心提供的:org.thymeleaf。TemplateEngine,我們在這里創(chuàng)建一個實例:
templateEngine = new TemplateEngine(); templateEngine.setTemplateResolver(templateResolver);很簡單,不是嗎?我們只需要創(chuàng)建一個實例并將模板解析器設置為它。
模板解析器是TemplateEngine需要的惟一必需參數(shù),不過后面還將介紹其他許多參數(shù)(消息解析器、緩存大小等)。現(xiàn)在,這就是我們所需要的。
我們的模板引擎現(xiàn)在已經準備好了,我們可以開始使用Thymeleaf創(chuàng)建頁面。
3 文本
3.1多語言的歡迎
使用th:文本和外部化文本
略
上下文
略
執(zhí)行模板引擎
略
3.2更多關于文本和變量的信息
非轉義的文本
<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標準表達式語法
<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 消息
如我們所知,……消息表達式允許我們鏈接這個:
<p th:utext="#{home.welcome}">Welcome to our grocery store!</p>消息鍵本身可以來自一個變量:
<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>實際上等于
ctx.getVariable("today");但是OGNL允許我們創(chuàng)建更強大的表達式,這就是它的原理:
<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方法導航只是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('-')}基本對象表達式
當在上下文變量上計算OGNL表達式時,為了獲得更高的靈活性,表達式可以使用一些對象。這些對象將被引用(按照OGNL標準),從#符號開始:
- #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>. ?擴展表達式對象
除了這些基本對象之外,Thymeleaf還將提供一組實用程序對象,幫助我們在表達式中執(zhí)行常見的任務。
- #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表達式的作用域(星號語法)
變量表達式不僅可以寫成${…},也可以寫成*{…}。
但是有一個重要的區(qū)別:星號語法對所選對象的表達式求值,而不是對整個上下文求值。也就是說,只要沒有選擇對象,美元和星號語法的作用是完全相同的。
當對象選擇就緒時,所選對象也將作為#對象表達式變量提供給美元表達式:
<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鏈接
由于它們的重要性,url在web應用程序模板中是一等公民,Thymeleaf標準方言為它們提供了一種特殊的語法,即@語法:@{…}
有不同類型的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
這些表達式的實際處理以及它們對將要輸出的url的轉換是由org.thymeleaf.linkbuilder的實現(xiàn)完成的。注冊到正在使用的ITemplateEngine對象中的ILinkBuilder接口。
默認情況下,該接口的單個實現(xiàn)注冊為org.thymeleaf.linkbuilder類。StandardLinkBuilder,這對于離線(非web)和基于Servlet API的web場景都足夠了。其他場景(如與非servletapi web框架的集成)可能需要link builder接口的特定實現(xiàn)。
讓我們使用這個新語法。滿足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是一個修飾符屬性:一旦處理,它將計算要使用的鏈接URL,并將該值設置為標記的href屬性。
我們可以為URL參數(shù)使用表達式(正如您可以在orderId=${o.id}中看到的那樣)。還將自動執(zhí)行所需的url參數(shù)編碼操作。
如果需要幾個參數(shù),則用逗號分隔:@{/order/process(execId=${execId},execType='FAST')}
URL路徑中也允許使用變量模板:@{/order/{orderId}/details(orderId=${orderId})}
以/(例如:/order/details)開頭的相對url將自動以應用程序上下文名稱作為前綴。
如果沒有啟用cookie,或者還不知道,可以向相對url添加一個“;jsessionid=…”后綴,以便保留會話。這稱為URL重寫,Thymeleaf允許您通過使用Servlet API中的response.encodeURL(…)機制為每個URL插入自己的重寫過濾器。
th:href屬性允許我們(可選地)在模板中有一個可工作的靜態(tài)href屬性,這樣在直接為原型目的打開模板鏈接時,瀏覽器就可以繼續(xù)導航。
與消息語法(#{…})一樣,URL基也可以是計算另一個表達式的結果:
<a th:href="@{${url}(orderId=${o.id})}">view</a> <a th:href="@{'/details/'+${user.login}(orderId=${o.id})}">view</a>4.5 模板
最常見的用法是使用th:insert或th:replace插入片段(在后面的小節(jié)中將詳細介紹):
<div th:insert="~{commons :: main}">...</div>在本教程的后面,有一個完整的部分專門討論模板布局,包括對片段表達式的更深入的解釋。
4.6 常量
略。
4.7 追加文字
文本,無論它們是文本還是變量或消息表達式的計算結果,都可以使用+運算符輕松地附加:
<span th:text="'The name of the user is ' + ${user.name}">4.8 常量替換
文字替換允許對包含變量值的字符串進行簡單的格式化,而不需要在字符串后面加上'…”+“…”。
這些替換必須被豎條(|)包圍,例如:
<span th:text="|Welcome to our application, ${user.name}!|">等于:
<span th:text="'Welcome to our application, ' + ${user.name} + '!'">文字替代可以與其他類型的表達式結合:
<span th:text="${onevar} + ' ' + |${twovar}, ${threevar}|">?
只有變量/消息表達式(${…},{…}, #{…})允許在|…|文字替換。沒有其他的文字('…'),布爾/數(shù)字符號,條件表達式等。
4.9 數(shù)學計算
還有一些算術運算:+、-、*、/和%。
<div th:with="isEven=(${prodStat.count} % 2 == 0)">注意,這些運算符也可以應用于OGNL變量表達式本身(在這種情況下,將由OGNL而不是Thymeleaf標準表達式引擎執(zhí)行):
4.10 比較和相等
表達式中的值可以與>、<、>=和<=符號進行比較,并且可以使用==和!=操作符檢查是否相等(或是否不相等)。注意,XML規(guī)定不應該在屬性值中使用<和>符號,因此應該用<?和>。
<div th:if="${prodStat.count} > 1">?
<span th:text="'Execution mode is ' + ( (${execMode} == 'dev')? 'Development' : 'Production')"> ?一個更簡單的選擇可能是使用這些操作符中存在的文本別名:gt(>)、lt(<)、ge(>=)、le(<=),而不是(!)。eq (==), neq/ne (!=)
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>Else表達式也可以省略,如果條件為false,則返回null值:
<tr th:class="${row.even}? 'alt'">... </tr>4.12 默認值
<div th:object="${session.user}">...<p>Age: <span th:text="*{age}?: '(no age specified)'">27</span>.</p> </div>4.13 無操作
無操作令牌由下劃線符號(_)表示。
這個令牌背后的思想是指定表達式的期望結果是什么都不做,也就是說,就像根本不存在可處理屬性(例如th:text)一樣。
在其他可能性中,這允許開發(fā)人員使用原型文本作為默認值。例如,代替:
<span th:text="${user.name} ?: 'no user authenticated'">...</span>……我們可以直接使用“no user authenticated”作為原型文本,這使得代碼從設計角度來說更加簡潔和通用:
<span th:text="${user.name} ?: _">no user authenticated</span>4.14 數(shù)據(jù)轉換和格式化
Thymeleaf為變量(${…})和選擇(*{…})表達式定義了一個雙括號語法,它允許我們通過配置的轉換服務應用數(shù)據(jù)轉換。
基本上是這樣的:
<td th:text="${{user.lastAccessDate}}">...</td>注意到雙括號了嗎?:$ {{…}}。它指示Thymeleaf傳遞用戶的結果。轉換服務的lastAccessDate表達式,并要求它在寫入結果之前執(zhí)行格式化操作(轉換為字符串)。
假設用戶。lastAccessDate類型為java.util.Calendar,如果一個轉換服務(IStandardConversionService的實現(xiàn))已經注冊,并且包含一個有效的日歷->字符串轉換,那么它將被應用。
IStandardConversionService (StandardConversionService類)的默認實現(xiàn)只是在任何轉換為字符串的對象上執(zhí)行. tostring()。有關如何注冊自定義轉換服務實現(xiàn)的更多信息,請參閱配置部分的更多信息。
官方的Thymeleaf -spring3和Thymeleaf -spring4集成包透明地將Thymeleaf的轉換服務機制與Spring自己的轉換服務基礎設施集成在一起,這樣在Spring配置中聲明的轉換服務和格式化程序將自動提供給${{…}}和{{…}}表達式。
4.15 預處理
除了這些用于表達處理的特性外,Thymeleaf還具有預處理表達式的特性。
<p th:text="${__#{article.text('textVar')}__}">Some text here...</p>注意,法語語言環(huán)境的預處理步驟將創(chuàng)建以下等價的代碼:
<p th:text="${@myapp.translator.Translator@translateToFrench(textVar)}">Some text here...</p>預處理字符串可以使用\_\_在屬性中轉義。
5 設置屬性
本章將解釋如何在標記中設置(或修改)屬性值
5.1 設置任意屬性
然后輸入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>但是如果我們想一次設置多個屬性呢?XML規(guī)則不允許在標記中兩次設置屬性,因此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}"/>是一段相當難看的標記。在屬性值中指定賦值可能非常實用,但如果必須一直這樣做,那么它不是創(chuàng)建模板的最優(yōu)雅的方法。
Thymeleaf同意您的觀點,這就是為什么th:attr很少用于模板中。通常,您將使用其他th:*屬性,其任務是設置特定的標記屬性(而不是像th:attr這樣的任何屬性)。
例如,要設置value屬性,可以使用th:value:
<input type="submit" value="Subscribe!" th:value="#{subscribe.submit}"/>這看起來好多了!讓我們嘗試對表單標記中的action屬性執(zhí)行相同的操作:
<form action="subscribe.html" th:action="@{/subscribe}">5.3 一次性設置多個值
有兩個非常特殊的屬性叫做th:alt-title和th:lang-xmllang,可以用于將兩個屬性同時設置為相同的值。具體地說:
- th:alt-title?will set?alt?and?title.
- th:lang-xmllang?will set?lang?and?xml:lang.
5.4 追加和追加在前面
Thymeleaf還提供th:attrappend和th:attrprepend屬性,它們將對現(xiàn)有屬性值求值的結果追加(后綴)或prepend(前綴)。
例如,您可能想要將要添加(不是設置,只是添加)的CSS類的名稱存儲在上下文變量中的一個按鈕中,因為要使用的特定CSS類將取決于用戶以前所做的事情:
<input type="button" value="Do it!" class="btn" th:attrappend="class=${' ' + cssStyle}" />5.5 布爾值屬性
HTML有布爾屬性的概念,沒有值的屬性,一個值的前綴表示值為“true”。在XHTML中,這些屬性只取1個值,也就是它本身。
<input type="checkbox" name="option2" checked /> <!-- HTML -->標準方言包括允許您通過計算條件來設置這些屬性的屬性,因此,如果計算為true,屬性將被設置為其固定值,如果計算為false,屬性將不會被設置:
<input type="checkbox" name="active" th:checked="${user.active}" />5.6 設置任意屬性的值
Thymeleaf提供了一個默認的屬性處理器,允許我們設置任何屬性的值,即使在標準方言中沒有為它定義特定的th:*處理器。
<span th:whatever="${user.name}">...</span>將會導致
<span whatever="John Apricot">...</span>5.7 支持HTML5屬性
還可以使用一種完全不同的語法,以一種更加html友好的方式將處理器應用到模板中。
<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中編寫定制屬性的標準方法,不要求開發(fā)人員使用任何名稱空間名稱,如th:*。Thymeleaf使這種語法自動適用于所有方言(不僅僅是標準方言)。
還有一種語法可以指定定制標記:{prefix}-{name},它遵循W3C定制元素規(guī)范(W3C Web組件規(guī)范的一部分)。例如,這可以用于th:block元素(或者也可以用于th塊),后面的部分將對此進行解釋。
重要提示:這個語法是對名稱空間th:* one的補充,它不會替換它。將來完全不打算廢棄帶有名稱空間的語法。
6 迭代
到目前為止,我們已經創(chuàng)建了一個主頁、一個用戶個人資料頁面以及一個允許用戶訂閱我們的時事通訊的頁面……但是我們的產品呢?為此,我們將需要一種迭代集合中的項的方法來構建我們的產品頁面。
6.1 基礎迭代
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}屬性值的意思是“對于計算${prods}的結果中的每個元素,使用名為prod的變量中的當前元素重復這個模板片段”。讓我們給我們看到的每樣東西命名:
- We will call?${prods}?the?iterated expression?or?iterated variable.
- We will call?prod?the?iteration variable?or simply?iter variable.
可以被迭代的值
java.util。List類不是Thymeleaf中惟一可以用于迭代的值。有一個相當完整的對象集,被th:each屬性認為是可迭代的:
- 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時,Thymeleaf提供了一種用于跟蹤迭代狀態(tài)的機制:狀態(tài)變量。
狀態(tài)變量定義在th:每個屬性中,包含以下數(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屬性的標記所定義的代碼片段中。
如果您沒有顯式地設置狀態(tài)變量,Thymeleaf將始終為您創(chuàng)建一個狀態(tài)變量,方法是將Stat添加到迭代變量的名稱后面:
6.3 延遲加載
有時我們可能希望優(yōu)化數(shù)據(jù)集合的檢索(例如從數(shù)據(jù)庫中檢索),以便只有在真正要使用這些集合時才檢索這些集合。
?
實際上,這可以應用于任何數(shù)據(jù)塊,但是考慮到內存集合可能具有的大小,檢索要迭代的集合是本場景中最常見的情況。
?
為了支持這一點,Thymeleaf提供了一種延遲加載上下文變量的機制。實現(xiàn)ILazyContextVariable接口的上下文變量——很可能是通過擴展其LazyContextVariable默認實現(xiàn)實現(xiàn)的——將在執(zhí)行時解析。例如:
context.setVariable("users",new LazyContextVariable<List<User>>() {@Overrideprotected List<User> loadValue() {return databaseRepository.findAllUsers();}});這個變量可以在不知道它是否懶惰的情況下使用,代碼如下:
<li th:each="u : ${users}" th:text="${u.name}">user name</li>但同時,如果條件在代碼中計算為false,則永遠不會被初始化(它的loadValue()方法將永遠不會被調用),例如:
<li th:each="u : ${users}" th:text="${u.name}">user name</li>7 條件表達式
7.1 簡單條件:if 和 unless
有時候,您需要模板的一個片段只出現(xiàn)在滿足特定條件的結果中。
例如,假設我們希望在product表中顯示一列,其中包含每個產品的評論數(shù)量,如果有任何評論,則顯示到該產品的評論詳細信息頁面的鏈接。
為了做到這一點,我們將使用th:if屬性:
th:if="${not #lists.isEmpty(prod.comments)}">view</a>這里有很多東西值得一看,所以讓我們來看看這條重要的線:
這將創(chuàng)建一個到comments頁面的鏈接(帶有URL /product/comments),并將prodId參數(shù)設置為產品的id,但只有在產品有任何注釋時才會這樣做。
讓我們來看看結果標記:
<a href="/gtvg/product/comments?prodId=2">view</a>請注意,th:if屬性不僅計算布爾條件。它的功能稍微超出了這一范圍,它將按照以下規(guī)則計算指定的表達式為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有一個逆屬性,th:unless,我們可以在前面的例子中使用,而不是在OGNL表達式中使用not:
th:unless="${#lists.isEmpty(prod.comments)}">view</a>7.2 Switch表達式
還有一種方法可以有條件地顯示內容,它使用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需要我們?yōu)榘x這些部分,“fragments”,這可以使用th:fragment屬性來完成。
假設我們想為所有的雜貨頁面添加一個標準的版權頁腳,因此我們創(chuàng)建了一個/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>模板規(guī)格
片段表達式的語法非常簡單。有三種不同的格式:
"~{templatename::selector}"
包含在名為templatename的模板上應用指定標記選擇器生成的片段。注意,選擇器可以僅僅是一個片段名稱,所以您可以像上面的~{footer:: copy}中那樣指定像~{templatename::fragmentname}這樣簡單的東西。
?
標記選擇器語法由底層的AttoParser解析庫定義,類似于XPath表達式或CSS選擇器。有關更多信息,請參見附錄C。
?
"~{templatename}"
包含名為templatename的完整模板。
注意,在th:insert/th:replace標記中使用的模板名稱必須由模板引擎當前使用的模板解析器解析。
~{::selector}"?or?"~{this::selector}"
插入來自相同模板的片段,匹配選擇器。如果在表達式出現(xiàn)的模板上沒有找到,則會將模板調用(插入)的堆棧遍歷到原始處理的模板(根),直到選擇器在某個級別上匹配為止。?
上面例子中的templatename和selector都可以是全功能的表達式(甚至是條件語句!)
<div th:insert="footer :: (${user.isAdmin}? #{footer.admin} : #{footer.normaluser})"></div>片段可以包括任何th:*屬性。一旦片段被包含到目標模板中(具有th:insert/th:replace屬性的那個),這些屬性將被計算,并且它們將能夠引用在這個目標模板中定義的任何上下文變量。
?
這種片段處理方法的一大優(yōu)點是,您可以將片段編寫在瀏覽器完全可顯示的頁面中,頁面具有完整甚至有效的標記結構,同時仍然能夠使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?(and?th:include)區(qū)別
th:insert和th:replace(以及th:include, 3.0以后就不推薦了)之間的區(qū)別是什么?
th:insert?是最簡單的:它將簡單地插入指定的片段作為其宿主標記的主體。
T,h:replace 實際上用指定的片段替換其主機標記。
th:include 類似于th:insert,但它只插入片段的內容,而不是插入片段。
像這樣的HTML片段:
<footer th:fragment="copy">© 2011 The Good Thymes Virtual Grocery </footer>Insert
<div><footer>© 2011 The Good Thymes Virtual Grocery</footer></div>Replace
<footer>© 2011 The Good Thymes Virtual Grocery</footer>Include
<div>© 2011 The Good Thymes Virtual Grocery </div>8.2 模板參數(shù)
為了為模板片段創(chuàng)建更類似函數(shù)的機制,使用th:fragment定義的片段可以指定一組參數(shù):
<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>注意最后一個選項中的順序并不重要:
沒有片段參數(shù)的片段局部變量
即使沒有這樣的參數(shù)來定義片段:
<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}">請注意,片段的這種局部變量規(guī)范——無論它是否具有參數(shù)簽名——不會導致在執(zhí)行之前清空上下文。片段仍然能夠像當前一樣訪問調用模板中使用的每個上下文變量。
模板內斷言
斷言屬性可以指定一個以逗號分隔的表達式列表,這些表達式應該被求值,并為每個求值生成true,如果不是,則引發(fā)異常。
<div th:assert="${onevar},(${twovar} != 43)">...</div>這對于驗證片段簽名的參數(shù)很方便:
<header th:fragment="contentheader(title)" th:assert="${!#strings.isEmpty(title)}">...</header>8.3 靈活的布局:不僅僅是片段插入
多虧了片段表達式,我們可以為片段指定參數(shù),這些片段不是文本、數(shù)字、bean對象……而是標記的片段。
這允許我們以這樣一種方式創(chuàng)建片段,通過調用模板中的標記來豐富片段,從而形成非常靈活的模板布局機制。
注意下面片段中標題和鏈接變量的使用:
<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> ...結果將使用我們調用模板中實際的
... <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>注意片段(鏈接)的第二個參數(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></head> ...使用_操作符
如果我們只想讓片段使用其當前標記作為默認值,那么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片段的第一個參數(shù))是如何設置為no-op(_)的,這將導致片段的這一部分根本不執(zhí)行(title = no-operation):
<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片段和無操作標記的可用性允許我們以一種非常簡單和優(yōu)雅的方式執(zhí)行片段的條件插入。
例如,我們可以只在用戶是管理員的情況下插入我們的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 刪除多余的靜態(tài)html代碼
回到示例應用程序,讓我們重溫產品列表模板的最后一個版本:
<tr th:each="prod : ${prods}" th:class="${prodStat.odd}? 'odd'">這段代碼作為模板很好,但是作為靜態(tài)頁面(當瀏覽器直接打開而不需要Thymeleaf處理它時)就不是一個很好的原型。
為什么?因為,盡管瀏覽器可以完美地顯示該表,但該表只有一行,而這一行有模擬數(shù)據(jù)。作為一個原型,它看起來不夠真實……我們應該有多個產品,我們需要更多行。
所以讓我們添加一些:
我們需要一種方法在模板處理期間刪除這兩行。
讓我們使用th:remove第二個和第三個<tr>標記上的屬性:
<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.
這個幾乎是最重要的價值有什么用呢?它將讓我們保存一些th:remove="all"當原型:
第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,因此不會執(zhí)行刪除操作。
8.5 布局繼承
為了能夠使用單個文件作為布局,可以使用片段。一個簡單的布局的例子,標題和內容使用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的片段,其中標題和內容作為參數(shù)。下面的示例中提供的片段表達式將在繼承它的頁面上替換這兩個表達式。
<!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標記將被layout替換,但是在布局中標題和內容將分別被title和section塊替換。
如果需要,布局可以由幾個片段組成,如頁眉和頁腳。
9 本地參數(shù)
Thymeleaf調用局部變量,這些變量是為模板的特定片段定義的,并且僅可用于該片段內的求值。
我們已經看到的一個例子是產品列表頁面中的prod iter變量:
<tr th:each="prod : ${prods}">... </tr>該prod變量僅在<tr>標記的范圍內可用。明確地:
對于在該標記中執(zhí)行的優(yōu)先級低于th:each的任何其他th:*屬性,它都是可用的(這意味著它們將在th:each之后執(zhí)行)。
該prod變量僅在<tr>標記的范圍內可用。明確地:
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變量被創(chuàng)建為一個局部變量,并添加到來自上下文的變量映射中,這樣它就可以與上下文中聲明的任何其他變量一起進行計算,但只能在包含標記的范圍內。
您可以使用通常的多重賦值語法同時定義多個變量:
<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>讓我們用這個在我們的雜貨店的主頁上!還記得我們編寫的輸出格式化日期的代碼嗎?
<p>Today is: <span th:text="${#calendars.format(today,'dd MMMM yyyy')}">13 february 2011</span> </p>現(xiàn)在,讓我們使用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的優(yōu)先級高于th:text,我們可以在span標記中解決所有問題:
<p>Today is: <span th:with="df=#{date.format}" th:text="${#calendars.format(today,df)}">13 February 2011</span> </p>你可能會想:優(yōu)先?我們還沒說呢!別擔心,這正是下一章的內容。
10屬性優(yōu)先級
當您在同一個標記中編寫多個th:*屬性時會發(fā)生什么?例如:
<li th:each="item : ${items}" th:text="${item.description}">Item description here...</li>我們希望th:每個屬性執(zhí)行之前th:文本,這樣我們得到我們想要的結果,但考慮到HTML / XML標準不給任何意義的順序編寫一個標簽的屬性,屬性的優(yōu)先級機制必須建立自己為了確保這將正常工作。
因此,所有的Thymeleaf屬性都定義了一個數(shù)字優(yōu)先級,它確定了它們在標記中執(zhí)行的順序。這個順序是:
| Order | Feature | Attributes |
| 1 | Fragment inclusion | th:insert |
| 2 | Fragment iteration | th:each |
| 3 | Conditional evaluation | th:if |
| 4 | Local variable definition | th:object |
| 5 | General attribute modification | th:attr |
| 6 | Specific attribute modification | th:value |
| 7 | Text (tag body modification) | th:text |
| 8 | Fragment specification | th:fragment |
| 9 | Fragment removal | th:remove |
這種優(yōu)先機制意味著,如果屬性位置顛倒,上面的迭代片段將給出完全相同的結果(盡管它的可讀性稍微差一些):
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將刪除<!——/*和*/——>,所以這些注釋塊也可以用于靜態(tài)打開模板時顯示代碼,知道當Thymeleaf處理它時,它會被刪除:
<!--/*--> <div>you can see me only before Thymeleaf processes me!</div> <!--*/-->11.3. Thymeleaf原型注釋
Thymeleaf允許在靜態(tài)打開模板時(即作為原型)將特殊的注釋塊定義為注釋,但在執(zhí)行模板時將其視為普通標記。
<span>hello!</span> <!--/*/<div th:text="${...}">...</div> /*/--> <span>goodbye!</span>Thymeleaf的解析系統(tǒng)將簡單地刪除<!—/*/和/*/—>標記,但不包括其內容,因此將不注釋這些內容。所以在執(zhí)行模板時,Thymeleaf會看到:
<span>hello!</span><div th:text="${...}">...</div><span>goodbye!</span>與解析器級別的注釋塊一樣,這個特性與方言無關。
11.4.th:block
標準方言中包含的惟一元素處理器(不是屬性)是th:block。
block僅僅是一個屬性容器,它允許模板開發(fā)人員指定他們想要的任何屬性。Thymeleaf將執(zhí)行這些屬性,然后使塊(而不是其內容)消失。
因此,它可能很有用,例如,當創(chuàng)建迭代表時,每個元素需要一個以上的<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(不需要在中添加禁止的
塊),并且在瀏覽器中作為原型靜態(tài)打開時仍然可以正常工作!
12 Thymeleaf嵌入
12.1 表達式嵌入
盡管標準方言允許我們使用標記屬性做幾乎所有的事情,但是在某些情況下,我們更喜歡直接將表達式寫入HTML文本。例如,我們可以這樣寫:
<p>Hello, [[${session.user.name}]]!</p>……不是這樣的:
<p>Hello, <span th:text="${session.user.name}">Sebastian</span>!</p>之間的表達式[[…]]或[(…)]在Thymeleaf中被認為是內聯(lián)表達式,在它們內部,我們可以使用在th:text或th:utext屬性中也有效的任何類型的表達式。
注意,然而[[……]]對應于th:text(即結果為html轉義),[(…)]對應于th:utext,不執(zhí)行任何html轉義。因此,對于msg = '這是這樣的變量,很好!',給定這個片段:
<p>The message is "[(${msg})]"</p>嵌入vs非嵌入
如果您來自以這種方式輸出文本為標準的其他模板引擎,您可能會問:為什么我們不從一開始就這樣做呢?它的代碼比所有那些th:text屬性都要少!
那么,在這里要小心,因為盡管您可能會發(fā)現(xiàn)內聯(lián)非常有趣,但是您應該始終記住,當靜態(tài)打開內聯(lián)表達式時,它們將在HTML文件中逐字顯示,所以您可能再也不能將它們用作設計原型了!
瀏覽器如何在不使用內聯(lián)的情況下靜態(tài)顯示我們的代碼片段……
Hello, Sebastian!- 并利用這
就設計的實用性而言,……是相當清楚的。
禁止嵌入
但是可以禁用此機制,因為在某些情況下,我們確實希望輸出[[…]]或[(…)]序列,但不將其內容作為表達式處理。為此,我們將使用th:inline="none":
<p th:inline="none">A double array looks like this: [[1, 2, 3], [4, 5]]!</p>12.2 文本嵌入
文本內聯(lián)非常類似于我們剛才看到的表達式內聯(lián)功能,但是它實際上增加了更多的功能。它必須顯式啟用th:inline="text"。
文本內聯(lián)不僅允許我們使用我們剛才看到的相同的內聯(lián)表達式,而且實際上還可以像處理文本模板模式中的模板那樣處理標記體,這允許我們執(zhí)行基于文本的模板邏輯(不僅僅是輸出表達式)。
我們將在下一章的文本模板模式中看到更多關于這一點的內容。
12.3 JavaScript 嵌入
JavaScript內聯(lián)允許在HTML模板模式下處理的模板中更好地集成JavaScript
與文本內聯(lián)一樣,這實際上相當于在JAVASCRIPT模板模式下像處理模板一樣處理腳本內容,因此文本模板模式的所有功能(參見下一章)就在手邊。然而,在本節(jié)中,我們將重點介紹如何使用它將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內聯(lián)不僅輸出所需的文本,還用引號和JavaScript—轉義其內容—將表達式結果作為格式良好的JavaScript文本輸出。
其次,這是因為我們將${session.user.name}表達式作為轉義輸出,即使用一個雙尖括號表達式:[[${session.user.name}]]。如果我們使用unescape:
<script th:inline="javascript">...var username = [(${session.user.name})];... </script>結果會是這樣的:
<script th:inline="javascript">...var username = Sebastian "Fruity" Applejuice;... </script>這是格式錯誤的JavaScript代碼。但是,如果要通過追加內聯(lián)表達式來構建腳本的某些部分,輸出未轉義的內容可能是我們所需要的,所以手邊有這個工具是很好的。
JavaScript 非嵌入寫法
前面提到的JavaScript內聯(lián)機制的智能遠遠不止應用JavaScript特有的轉義和將表達式結果作為有效的文本輸出。
例如,我們可以將(轉義)內聯(lián)表達式包裝在JavaScript注釋中,如:
<script th:inline="javascript">...var username = /*[[${session.user.name}]]*/ "Gertrud Kiwifruit";... </script>而Thymeleaf會忽略我們在注釋后面和分號之前寫的所有東西(在本例中是“Gertrud Kiwifruit”),所以執(zhí)行這個的結果看起來就像我們沒有使用包裝注釋的時候:
<script th:inline="javascript">...var username = "Sebastian \"Fruity\" Applejuice";... </script>但是再仔細看看原始模板代碼:
<script th:inline="javascript">...var username = /*[[${session.user.name}]]*/ "Gertrud Kiwifruit";... </script>注意這是有效的JavaScript代碼。當您以靜態(tài)方式(而不是在服務器上執(zhí)行)打開模板文件時,它將完美地執(zhí)行。
所以我們這里有一種做JavaScript自然模板的方法!
嵌入求值和js序列號
關于JavaScript內聯(lián)需要注意的一件重要事情是,這個表達式的求值是智能的,不限于字符串。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}表達式將求值為user對象,Thymeleaf將其正確轉換為Javascript語法:
<script th:inline="javascript">...var user = {"age":null,"firstName":"John","lastName":"Apricot","name":"John Apricot","nationality":"Antarctica"};... </script>這種JavaScript序列化的實現(xiàn)方式是通過org.thymeleaf.standard.serializer的實現(xiàn)實現(xiàn)的。IStandardJavaScriptSerializer接口,可以在模板引擎使用的標準方言的實例中配置該接口。
這個JS序列化機制的默認實現(xiàn)將在類路徑中查找Jackson庫,如果存在,將使用它。如果不是,它將應用一個內置的序列化機制,該機制覆蓋大多數(shù)場景的需求,并產生類似的結果(但靈活性較差)。
12.4 CSS嵌入
Thymeleaf還允許在CSS
<style th:inline="css">... </style>例如,我們有兩個變量被設置為兩個不同的字符串值:
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內聯(lián)也有一些智能,就像JavaScript一樣。具體地說,通過轉義表達式(如[[${classname}]])輸出的表達式將轉義為CSS標識符。這就是為什么我們的classname = 'main elems'在上面的代碼片段中變成了main\ elems。
高級特性
與之前對javascript的解釋類似,CSS內聯(lián)還允許我們的<style>標記靜態(tài)和動態(tài)地工作,即通過將內聯(lián)表達式包裝在注釋中作為CSS自然模板。見:
<style th:inline="css">.main\ elems {text-align: /*[[${align}]]*/ left;} </style>13 文本模式
13.1 文本語法
三種Thymeleaf模板模式被認為是文本模式:文本、JAVASCRIPT和CSS。這將它們與標記模板模式(HTML和XML)區(qū)分開來。
文本模板模式和標記模式之間的關鍵區(qū)別在于,在文本模板中沒有以屬性形式插入邏輯的標記,因此我們必須依賴其他機制。
第一個也是最基本的機制是內聯(lián),我們在上一章已經詳細介紹過了。內聯(lián)語法是在文本模板模式下輸出表達式結果的最簡單方法,因此這是一個非常有效的文本電子郵件模板。
Dear [(${name})],Please find attached the results of the report you requestedwith name "[(${report.name})]".Sincerely,The Reporter.即使沒有標記,上面的示例也是一個完整而有效的Thymeleaf模板,可以在文本模板模式下執(zhí)行。
但是為了包含比輸出表達式更復雜的邏輯,我們需要一種新的非基于標記的語法:
[# th:each="item : ${items}"]- [(${item})] [/]這實際上是更詳細的壓縮版本:
[#th:block th:each="item : ${items}"]- [#th:block th:utext="${item}" /] [/th:block]注意這個新語法是如何基于聲明為[#element…而不是。元素是開放的,比如[#元素…]和像[/element]一樣關閉的標簽,獨立標簽可以通過使用/最小化打開的元素來聲明,其方式幾乎等同于XML標簽:[#element…/)。
?
標準方言只包含這些元素之一的處理器:已知的th:塊,盡管我們可以在方言中擴展它并以通常的方式創(chuàng)建新元素。另外,第th:block元素([#th:block…]))……[/th:block])可以縮寫為空字符串([#…)……[/]),所以上面的block實際上等于:
[# th:each="item : ${items}"]- [# th:utext="${item}" /] [/]假設[# th:utext="${item}" /]相當于一個內聯(lián)的未轉義表達式,我們可以使用它來減少代碼。這樣我們就得到了上面看到的第一個代碼片段:
[# th:each="item : ${items}"]- [(${item})] [/]注意,文本語法需要完全的元素平衡(沒有未關閉的標記)和引號屬性——它更像xml風格,而不是html風格。
讓我們來看看一個更完整的文本模板的例子,一個純文本電子郵件模板:
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í)行之后,結果可能是這樣的:
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另一個JAVASCRIPT模板模式的例子是一個greeting .js文件,我們將其作為文本模板處理,并從HTML頁面調用結果。注意,這不是HTML模板中的
var greeter = function() {var username = [[${session.user.name}]];[# th:each="salut : ${salutations}"] alert([[${salut}]] + " " + username);[/]};執(zhí)行之后,結果可能是這樣的:
var greeter = function() {var username = "Bertrand \"Crunchy\" Pear";alert("Hello" + " " + username);alert("Ol\u00E1" + " " + username);alert("Hola" + " " + username);};忽略元素屬性
為了避免與可能以其他模式(例如HTML模板中的文本模式內聯(lián))處理的模板部分進行交互,Thymeleaf 3.0允許轉義文本語法中元素的屬性。所以:
- 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<user.age}"]Congratulations![/]當然了。這在實際的文本模板中是沒有意義的,但是如果我們使用th:inline="text"塊處理HTML模板(其中包含上述代碼),并且希望確保瀏覽器不接受
13.2擴展
這種語法的優(yōu)點之一是它與標記語法一樣具有可擴展性。開發(fā)人員仍然可以使用自定義元素和屬性定義自己的方言,對它們應用前綴(可選),然后在文本模板模式中使用它們:
[#myorg:dosomething myorg:importantattr="211"]some text[/myorg:dosomething]13.3 原型注釋
JAVASCRIPT和CSS模板模式(文本不可用)允許在特殊的注釋語法/*[+…+]*/使Thymeleaf在處理模板時自動取消注釋:
var x = 23;/*[+var msg = "This is a working application";+]*/var f = function() {...13.4 文本解析器級注釋塊:刪除代碼
與僅使用原型的注釋塊類似,所有三種文本模板模式(文本、JAVASCRIPT和CSS)都可以指示Thymeleaf刪除特殊/*[- */和/* -]*/標記之間的代碼,如下所示:
13.5 JavaScript and CSS 模板
如上一章所述,JavaScript和CSS內聯(lián)提供了在JavaScript/CSS注釋中包含內聯(lián)表達式的可能性,例如:
…這是有效的JavaScript,一旦執(zhí)行可能像:
... var username = "John Apricot"; ...在注釋中封裝內聯(lián)表達式的相同技巧實際上可以用于整個文本模式語法:
/*[# th:if="${user.admin}"]*/alert('Welcome admin');/*[/]*/當模板靜態(tài)打開時(因為它是100%有效的JavaScript),以及當模板運行時(如果用戶是管理員),上面代碼中的警告將顯示出來。它等于:
[# th:if="${user.admin}"]alert('Welcome admin');[/]…這實際上是在模板解析期間將初始版本轉換為的代碼。
但是請注意,在注釋中包裝元素并不會清除它們所在的行(在a;如內聯(lián)輸出表達式所做的那樣。這種行為只保留給內聯(lián)輸出表達式。
因此,Thymeleaf 3.0允許以自然模板的形式開發(fā)復雜的JavaScript腳本和CSS樣式表,作為原型和工作模板都有效。
14雜貨店(略)
15 配置
15.1 模板解析器
為了我們的Thymes虛擬雜貨店,我們選擇了一個名為servletcontext的ITemplateResolver實現(xiàn),它允許我們從Servlet上下文中獲取模板作為資源。
除了讓我們能夠通過實現(xiàn)ITemplateResolver來創(chuàng)建自己的模板解析器之外,Thymeleaf還提供了四個開箱即用的實現(xiàn):
org.thymeleaf.templateresolver。ClassLoaderTemplateResolver,它將模板解析為類加載器資源,如:
return Thread.currentThread().getContextClassLoader().getResourceAsStream(template);org.thymeleaf.templateresolver。FileTemplateResolver,它將模板解析為文件系統(tǒng)中的文件,如:
return new FileInputStream(new File(template));org.thymeleaf.templateresolver。UrlTemplateResolver,它將模板解析為url(甚至是非本地的),比如:
return (new URL(template)).openStream();org.thymeleaf.templateresolver。StringTemplateResolver,它直接將模板解析為指定為模板的字符串(或模板名稱,在本例中顯然不僅僅是一個名稱):
return new StringReader(templateName);ITemplateResolver的所有預綁定實現(xiàn)都允許相同的一組配置參數(shù),其中包括:
templateResolver.setPrefix("/WEB-INF/templates/"); templateResolver.setSuffix(".html");允許使用與文件名不直接對應的模板名稱的emplate別名。如果前綴/前綴和別名都存在,則在前綴/后綴之前應用別名:
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實現(xiàn),它使用所有Spring基礎設施來訪問和讀取應用程序中的資源,這是支持Spring的應用程序中推薦的實現(xiàn)。
?
模板解析器鏈
此外,模板引擎可以指定多個模板解析器,在這種情況下,可以在它們之間建立一個模板解析順序,這樣,如果第一個解析器無法解析模板,就會請求第二個解析器,以此類推:
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實現(xiàn)的特定功能。請注意,并不是所有的實現(xiàn)都能夠在解析之前確定模板的存在,因此總是可以認為模板是可解析的,并打破解析鏈(不允許其他解析器檢查相同的模板),但隨后無法讀取真正的資源。
所有包含在核心Thymeleaf中的ITemplateResolver實現(xiàn)都包含一種機制,它允許我們在考慮可解析資源之前,讓解析器真正檢查資源是否存在。它是checkExistence標志,工作方式如下:
ClassLoaderTemplateResolver classLoaderTemplateResolver = new ClassLoaderTemplateResolver(); classLoaderTemplateResolver.setOrder(Integer.valueOf(1)); classLoaderTempalteResolver.setCheckExistence(true);這個checkExistence標志強制解析器在解析階段對資源存在性執(zhí)行真正的檢查(如果exist check返回false,則調用鏈中的以下解析器)。雖然這聽起來可能在所有情況下,在大多數(shù)情況下,這將意味著雙重訪問資源本身(一次檢查存在,另一個時間閱讀),和可能是一個性能問題在某些場景中,例如遠程url模板資源——一個潛在的性能問題,可能無論如何被使用在很大程度上減輕模板緩存(在這種情況下, ,模板只能解決他們第一次訪問)
15.2 消息解析器
我們沒有為雜貨應用程序顯式地指定消息解析器實現(xiàn),正如前面所解釋的,這意味著所使用的實現(xiàn)是一個org.thymeleaf.messageresolver。StandardMessageResolver對象。
StandardMessageResolver是IMessageResolver接口的標準實現(xiàn),但是我們可以根據(jù)應用程序的特定需求創(chuàng)建自己的接口。
默認情況下,Thymeleaf + Spring集成包提供一個IMessageResolver實現(xiàn),它使用標準的Spring方式檢索外部化的消息,方法是使用在Spring應用程序上下文中聲明的MessageSource bean。
標準消息解析器
那么StandardMessageResolver如何查找特定模板中請求的消息呢?
如果模板名稱是home并且位于/WEB-INF/templates/home中。,而請求的語言環(huán)境是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 轉換服務
使我們能夠通過雙大括號語法(${{…}})執(zhí)行數(shù)據(jù)轉換和格式化操作的轉換服務實際上是標準方言的一個特性,而不是Thymeleaf模板引擎本身的特性。
因此,配置它的方法是將IStandardConversionService接口的自定義實現(xiàn)直接設置到正在配置到模板引擎中的StandardDialect實例中。如:
? IStandardConversionService customConversionService = ...StandardDialect dialect = new StandardDialect(); dialect.setConversionService(customConversionService);templateEngine.setDialect(dialect);?
請注意,Thymeleaf -spring3和Thymeleaf -spring4包包含SpringStandardDialect,并且該方言已經預先配置了IStandardConversionService的實現(xiàn),該實現(xiàn)將Spring自己的轉換服務基礎結構集成到Thymeleaf中。
?
15.4 日志
Thymeleaf非常關注日志記錄,并且總是試圖通過它的日志接口提供最大數(shù)量的有用信息。
所使用的日志庫是slf4j,它實際上充當?shù)轿覀兛赡芟M趹贸绦蛑惺褂玫娜魏稳罩緦崿F(xiàn)(例如log4j)的橋梁。
Thymeleaf類將記錄跟蹤、調試和信息級別的信息,這取決于我們想要的詳細程度,除了一般的日志記錄,它還將使用三個與TemplateEngine類相關聯(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
使用log4j的Thymeleaf日志記錄基礎結構的一個示例配置可以是:
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=TRACE16 模板緩存
Thymeleaf工作由于一組解析器——標記和文本模板解析成的事件序列(開放標簽、文本結束標記、評論等)和一系列的處理器——一個為每個類型的行為,需要應用,修改模板解析事件序列為了創(chuàng)建我們期待的結果。通過結合原始模板與我們的數(shù)據(jù)。
默認情況下,它還包括存儲已解析模板的緩存;在處理模板文件之前讀取和解析模板文件所產生的事件序列。這在web應用程序中尤其有用,它基于以下概念:
17 離線模板
17.1 離線模板的概念(分離thymeleaf和dom)
到目前為止,我們在雜貨店中使用的模板都是按照通常的方式完成的,將邏輯以屬性的形式插入到模板中。
但是,Thymeleaf還允許我們將模板標記與其邏輯完全解耦,允許在HTML和XML模板模式中創(chuàng)建完全沒有邏輯的標記模板。
其主要思想是模板邏輯將在一個單獨的邏輯文件中定義(更確切地說,是一個邏輯資源,因為它不需要是一個文件)。默認情況下,該邏輯資源將是與模板文件位于相同位置(例如文件夾)的另一個文件,具有相同的名稱,但是.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或模板知識的設計人員可以創(chuàng)建、編輯和/或理解的模板文件。或者由完全沒有Thymeleaf鉤子的外部系統(tǒng)提供的HTML片段。
現(xiàn)在讓我們通過創(chuàng)建額外的home.th.xml文件將home.html模板轉換為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塊中看到許多標記。這些標記通過它們的sel屬性對原始模板的節(jié)點執(zhí)行屬性注入,這些sel屬性包含Thymeleaf標記選擇器(實際上是AttoParser標記選擇器)。
還請注意,標記可以嵌套,以便追加它們的選擇器。例如,上面的sel="/tr[0]"將被處理為sel="#usersTable/tr[0]"。用戶名的選擇器將被處理為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>這看起來更熟悉,而且實際上比創(chuàng)建兩個單獨的文件更簡單。但是解耦模板的優(yōu)點是,我們可以使模板完全獨立于Thymeleaf,因此從設計的角度來看,具有更好的可維護性。
當然,設計人員或開發(fā)人員之間仍然需要一些契約—例如,用戶需要id="usersTable"—但是在許多場景中,純html模板將是設計和開發(fā)團隊之間更好的通信工件。
17.2 配置離線模板
開啟離線模板
默認情況下,不會期望每個模板都具有解耦邏輯。相反,配置的模板解析器(ITemplateResolver的實現(xiàn))將需要專門將它們解析的模板標記為使用解耦邏輯。
除了StringTemplateResolver(它不支持解耦邏輯)之外,ITemplateResolver的所有其他開箱即用實現(xiàn)都將提供一個名為usedeconpledlogic的標志,該標志將把該解耦器解析的所有模板標記為所有或部分邏輯都可能位于單獨的資源中:
final ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver(servletContext); ... templateResolver.setUseDecoupledLogic(true);混用在線/離線模板
在啟用時,不需要解耦模板邏輯。當啟用時,這意味著引擎將查找包含解耦邏輯的資源,如果它存在,則解析它并將其與原始模板合并。如果解耦邏輯資源不存在,則不會拋出錯誤。
同樣,在同一個模板中,我們可以混合耦合和解耦邏輯,例如在原始模板文件中添加一些Thymeleaf屬性,而將其他屬性留給單獨的解耦邏輯文件。最常見的情況是使用新的th:ref屬性(在v3.0中)。
17.3 ref屬性
ref只是一個標記屬性。從處理的角度來看,它什么也不做,只是在處理模板時消失,但它的有用之處在于它作為標記引用,即它可以像標記名稱或片段(th:fragment)一樣通過標記選擇器中的名稱解析。
如果我們有一個選擇器
<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)點是什么?僅僅是因為我們可能不希望向標記中添加這么多id和類屬性來充當邏輯錨,這最終可能會污染我們的輸出。
同樣的道理,th:ref的缺點是什么?顯然,我們需要在模板中添加一些Thymeleaf邏輯(“邏輯”)。
請注意th:ref屬性的這種適用性不僅適用于解耦的邏輯模板文件:它在其他類型的場景中也同樣適用,比如片段表達式(~{…})。
17.4 離線模板的性能
影響非常小。當一個已解析的模板被標記為使用解耦邏輯而沒有被緩存時,模板邏輯資源將首先被解析、解析并處理為內存中指令的安全:基本上是要注入到每個標記選擇器中的屬性列表。
但這是惟一需要的額外步驟,因為在此之后,將解析實際的模板,在解析模板時,解析器本身將動態(tài)地注入這些屬性,這要感謝AttoParser中的節(jié)點選擇高級功能。因此,解析后的節(jié)點將從解析器中出來,就好像它們的注入屬性寫在原始模板文件中一樣。
這最大的好處是什么?當將模板配置為緩存時,它將被緩存,其中已經包含注入的屬性。因此,對可緩存模板使用解耦模板的開銷,一旦它們被緩存,將絕對為零。
17.5 何時分離邏輯代碼和樣式(離線模板)
Thymeleaf解析與每個模板對應的解耦邏輯資源的方式可由用戶配置。它由一個擴展點確定
ideconpledtemplatelogicresolver,它提供了一個默認的實現(xiàn):standarddeconpledtemplatelogicresolver。
這個標準實現(xiàn)做什么?
首先,它對模板資源的基名稱應用前綴和后綴(通過ITemplateResource#getBaseName()方法獲得)。前綴和后綴都可以配置,默認情況下,前綴為空,后綴為.th.xml。
其次,它要求模板資源通過ITemplateResource#relative(String relativeLocation)方法解析具有計算名稱的相對資源。
使用的ideconpledtemplatelogicresolver的具體實現(xiàn)可以在TemplateEngine上輕松配置:
final StandardDecoupledTemplateLogicResolver decoupledresolver = new StandardDecoupledTemplateLogicResolver(); decoupledResolver.setPrefix("../viewlogic/"); ... templateEngine.setDecoupledTemplateLogicResolver(decoupledResolver);?
?
?
?
總結
以上是生活随笔為你收集整理的fundamentals\java\Thymeleaf的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Cognex Designer中的数据显
- 下一篇: 2020-8-31 收藏一个api 日历