SpringBoot系列之Thymeleaf文法簡單介紹
@[toc]
Thymeleaf官方文檔已經有比較詳細的描述,是以本部落格隻挑部分比較重要的點看一下,還有介紹一下和SpringBoot怎麼內建使用
1、模闆引擎
引用百度百科的模闆引擎解釋:
模闆引擎(這裡特指用于Web開發的模闆引擎)是為了使使用者界面與業務資料(内容)分離而産生的,它可以生成特定格式的文檔,用于網站的模闆引擎就會生成一個标準的HTML文檔。
在JavaEE領域有幾中比較常用的模闆引擎,分别是Jsp、Velocity、Freemarker、Thymeleaf,不過對于前端頁面渲染效率來說,jsp其實還是最快的,Velocity次之。Thymeleaf雖然渲染效率不是很快,但是文法方面是比較輕巧的,Thymeleaf文法比Velocity輕巧,但是渲染效率不如Velocity
2、Thymeleaf簡介
2.1)、Thymeleaf定義
Thymeleaf是适用于Web和獨立環境的現代伺服器端Java模闆引擎,能夠處理HTML,XML,JavaScript,CSS甚至純文字。具體參考Thymeleaf官網
官網提供了線上文檔也有檔案格式的各種文檔
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiAnYldHL0FWby9mZvwFN4ETMfdHLkVGepZ2XtxSZ6l2clJ3LcV2Zh1Wa9M3clN2byBXLzN3btgHL9s2RkBnVHFmb1clWvB3MaVnRtp1XlBXe0xCMy81dvRWYoNHLwEzX5xCMx8FesU2cfdGLwMzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsQTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-cmbw5yNzcTMxcDOwcDZiRzNyUzYyYzX5EDMxQTM2AzLcdDMyIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjLyM3Lc9CX6MHc0RHaiojIsJye.png)
在這裡插入圖檔描述
2.2)、适用模闆
Thymeleaf适用于如下模闆:
- HTML
- XML
- TEXT
- JAVASCRIPT
- CSS
- RAW
有兩種标記模闆模式(HTML 和 XML)、三種文本模闆模式(TEXT、JAVASCRIPT 和 CSS)和一種無操作模闆模式 (RAW)。
ok,下面給出一些比較重要的知識點
3、重要知識點
3.1)、th:text和th:utext
這是很常用的text标簽,作用是Thymeleaf中設定文本的标簽,分為兩種,一種是th:text,另外一種是th:utext,兩種最重要的差別就是會不會對特殊字元進行轉義
- th:text:将所有特殊字元轉成字元
- th:utext:不會将特殊字元進行字元轉義
注意:這裡的特殊字元主要指html标簽,/n、/t、etc.這些字元是不支援的
寫個例子對比一下:
<span th:text="${'Welcome to our <b>fantastic</b> grocery store!'}"></span><br/>
<span th:utext="${'Welcome to our <b>fantastic</b> grocery store!'}"></span>
在這裡插入圖檔描述
3.2)、标準表達式
官方文檔裡有standard Expression Syntax這個章節,介紹的就是标準的表達式文法應用
- Simple expressions(簡單表達式文法):
- Variable Expressions: ${...} // 擷取周遊值,支援OGNL文法 etc.
- 擷取自定義對象的屬性值
- 擷取自定義的變量屬性值
- 使用内置的基本對象
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.
詳情參考Thymeleaf的附錄A
-
内置的工具類對象
官網已經給出比較詳細的介紹,詳細用法參考Thymeleaf附錄B
- 在這裡插入圖檔描述
- Selection Variable Expressions: *{...} // 標明對象,也就是擷取使用 th:object 屬性的表達式的值
- Message Expressions: #{...} //國際化内容 詳細用法參考我的部落格: SpringBoot系列之i18n國際化多語言支援教程
- Link URL Expressions: @{...} // 定義URL連結
<link th:href="@{/static/css/public.css}" rel="stylesheet" type="text/css" />
<link th:href="@{/static/css/index.css}" rel="stylesheet" type="text/css" />
<script type="text/javascript" th:src="@{/static/js/jquery-1.3.2.min.js}"></script>
<script type="text/javascript" th:src="@{/static/js/html5.js}"></script>
<script type="text/javascript" th:src="@{/static/js/popbox.js}"></script>
* Fragment Expressions: ~{...} //片段引用的表達式 eg: `<div th:insert="~{commons :: main}">....</div>`
- 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: + //連接配接操作
@{url/}+${id}
- 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:(條件運算,包括三元運算符etc.)
- If-then: (if) ? (then)
- If-then-else: (if) ? (then) : (else)
- Default: (value) ?: (defaultvalue)
- Special tokens:(特殊的令牌,也就是使用No-Operatio)
- No-Operation: _
- 在這裡插入圖檔描述
All these features can be combined and neste:
'User is of type ' + (${user.isAdmin()} ? 'Administrator' : (${user.type} ?: 'Unknown'))
翻譯過來意思是,這些文法都可以組合使用,這個章節是Thymeleaf一個重要的基本使用章節,本部落格對一些重要的知識點進行摘錄同時介紹一下在SpringBoot裡如何使用,當然自然沒有官方文檔詳細的,不過官方并沒有通過中文文檔,英文水準不好的話,閱讀起來比較困難,當然我也找了一篇國内翻譯過來的Thymeleaf中文文檔,讀者詳細的可以參考文檔
3.3)、Thymeleaf周遊
周遊是Thymeleaf很常用的例子,支援的屬性值有:
在這裡插入圖檔描述
下面還是給下例子,比較容易了解,如下代碼使用th:each,
th:each="item : ${items}"
<!--最新上架-->
<div class="first-pannel clearfix">
<div class="index-f clearfix">
<h3 class="index-f-head"> 最新上架 <span>每天都有上新,每天都有驚喜</span> </h3>
<div class="index-f-body">
<div class="top-sales newProduct">
<ul class="top-sales-list clearfix">
<li class="top-sales-item newProduct" th:each="item : ${items}">
<p class="item-img"> <a th:href="@{'/portal/item/toDetail/'+${item.spuId}+'/'+${item.skuId}}"><img th:src="@{${item.imgPath}}" /></a> </p>
<p class="item-buss"><a th:href="@{'/portal/item/toDetail/'+${item.spuId}+'/'+${item.skuId}}"></a></p>
<p class="item-name spec"><a th:href="@{'/portal/item/toDetail/'+${item.spuId}+'/'+${item.skuId}}" th:text="${item.itemName}"></a></p>
<p class="item-price spec"><em th:text="${item.mPrice}"></em>元</p>
</li>
</ul>
</div>
</div>
</div>
</div>
<!--最新上架//-->
3.4)、公共子產品抽取
在項目開發中經常遇到一些可以重用的頁面,這時候就可以Thymeleaf的Template Layout進行公共頁面的複用
本部落格以官方介紹的複用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>
-
引入公共的片段
<div th:insert="~{footer :: copy}"></div>
~{templatename::selector}:模闆名::選擇器
~{templatename::fragmentname}:模闆名::片段名
<div th:insert="footer :: copy"></div>
<div th:replace="footer :: copy"></div>
<div th:include="footer :: copy"></div
三種引入公共片段的th屬性:
- th:insert:将公共片段整個插入到聲明引入的元素中
- th:replace:将聲明引入的元素替換為公共片段
- th:include:将被引入的片段的内容包含進這個标簽中
效果對比:
<div>
<footer>
© 2011 The Good Thymes Virtual Grocery
</footer>
</div>
<footer>
© 2011 The Good Thymes Virtual Grocery
</footer>
<div>
© 2011 The Good Thymes Virtual Grocery
</div
3.5)、行内寫法介紹
所謂行内寫法就是沒寫在html對應的标簽裡的寫法,直接在頁面空白處,用[[....]]或者[(....)]的寫法,然後[[....]]和[(....)]的差別其實就等同于th:text和th:utext的差別,一個會進行轉義,一個不會轉義特殊字元
- [[....]]寫法:會轉義html标簽這些特殊字元(轉成字元)
-
[(....)]寫法:不會轉義html标簽這些特殊字元(按照其原意)
寫個例子就明白了:
- 在這裡插入圖檔描述
<span>
The message is [[${msg}]]
</span>
<br/>
<span>
The message is [(${msg})]
</span>
在這裡插入圖檔描述
3.6)、Thymeleaf文法規則
引用尚桂谷老師的歸納:
在這裡插入圖檔描述
4、SpringBoot內建
4.1)、Springboot內建Thymeleaf簡介
maven配置
因為引入了SpringBoot的parent工程,是以不需要寫版本号
<!-- Themeleaf -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
application.yml配置
注意:這裡的屬性大部分都可以不配置的,因為Springboot的自動配置因為做了很多自動配置,我們不配置,就使用預設的,不過下面的例子隻是給讀者了解一下有這些配置
#添加Thymeleaf配置,除了cache在項目沒上線前建議關了,其它配置都可以不用配的,本部落格隻是列舉一下有這些配置
thymeleaf:
# cache預設開啟的,這裡可以關了,項目上線之前,項目上線後可以開啟
cache: false
# 這個prefix可以注釋,因為預設就是templates的,您可以改成其它的自定義路徑
prefix: classpath:/templates/
suffix: .html
mode: HTML5
# 指定一下編碼為utf8
encoding: UTF-8
# context-type為text/html,也可以不指定,因為boot可以自動識别
content-type: text/html
ok,Springboot中Thymeleaf使用非常簡單,因為Springboot已經為我們做了很多自動配置,其實,yaml都不需要配置的,直接引入對應的jar,然後就可以直接使用,在resources資源檔案夾下面建立一個templates檔案夾,所有的html檔案都丢在這裡,靜态資源檔案也丢在resources資源檔案夾下面
建立一個html檔案,然後注意加上
<html xmlns:th="http://www.thymeleaf.org">
注意Thymeleaf文法要求比較嚴格
<meta charset="utf-8" >
,不如這樣寫是不可以的,必須加上斜杠的,
<meta charset="utf-8" />
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div>
<span th:text="${'Welcome to our <b>fantastic</b> grocery store!'}"></span><br/>
<span th:utext="${'Welcome to our <b>fantastic</b> grocery store!'}"></span>
</div>
<span>
The message is [[${msg}]]
</span>
<br/>
<span>
The message is [(${msg})]
</span>
</body>
</html>
4.2)、Thymeleaf自動配置源碼簡單分析
ok,然後為什麼我說直接引入對應pom配置就可以直接使用了?因為Springboot已經為項目做了很多自動配置,是以本部落格簡單跟一下源碼,了解一下SpringbootThymeleaf的自動配置
SpringBoot的自動配置類在ThymeleafAutoConfiguration裡
package org.springframework.boot.autoconfigure.thymeleaf;
....
@Configuration(proxyBeanMethods = false)//定義這是一個配置類
@EnableConfigurationProperties(ThymeleafProperties.class)//使用ThymeleafProperties屬性類的屬性
@ConditionalOnClass({ TemplateMode.class, SpringTemplateEngine.class })//指定TemplateMode、SpringTemplateEngine(模闆引擎類)起效的情況,整個配置類才起作用
@AutoConfigureAfter({ WebMvcAutoConfiguration.class, WebFluxAutoConfiguration.class })//必須在WebMvcAutoConfiguration(SpringMVC自動配置類,這個配置類會加載組裝所有的視圖解析器)、WebFluxAutoConfiguration類起效後,這個Thymeleaf自動配置類才起效
public class ThymeleafAutoConfiguration {
//沒有自定義的模闆解析器類的情況,使用預設的模闆解析器
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(name = "defaultTemplateResolver")
static class DefaultTemplateResolverConfiguration {
private static final Log logger = LogFactory.getLog(DefaultTemplateResolverConfiguration.class);
//Thymeleaf的properties配置
private final ThymeleafProperties properties;
private final ApplicationContext applicationContext;
DefaultTemplateResolverConfiguration(ThymeleafProperties properties, ApplicationContext applicationContext) {
this.properties = properties;
this.applicationContext = applicationContext;
}
//用PostConstruct注解,在依賴注入完成之後,實作類的初始化配置,這個方法主要是檢查模闆引擎的資源檔案路徑是否有
@PostConstruct
void checkTemplateLocationExists() {
boolean checkTemplateLocation = this.properties.isCheckTemplateLocation();
if (checkTemplateLocation) {
TemplateLocation location = new TemplateLocation(this.properties.getPrefix());
if (!location.exists(this.applicationContext)) {
logger.warn("Cannot find template location: " + location + " (please add some templates or check "
+ "your Thymeleaf configuration)");
}
}
}
//預設的Thymeleaf資源解析器
@Bean
SpringResourceTemplateResolver defaultTemplateResolver() {
SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
//資源解析器的所有配置
resolver.setApplicationContext(this.applicationContext);
resolver.setPrefix(this.properties.getPrefix());
resolver.setSuffix(this.properties.getSuffix());
resolver.setTemplateMode(this.properties.getMode());
if (this.properties.getEncoding() != null) {
resolver.setCharacterEncoding(this.properties.getEncoding().name());
}
resolver.setCacheable(this.properties.isCache());
Integer order = this.properties.getTemplateResolverOrder();
if (order != null) {
resolver.setOrder(order);
}
resolver.setCheckExistence(this.properties.isCheckTemplate());
return resolver;
}
}
//又是Thymeleaf的自動配置,自動配置模闆引擎SpringTemplateEngine
@Configuration(proxyBeanMethods = false)
protected static class ThymeleafDefaultConfiguration {
@Bean
@ConditionalOnMissingBean(ISpringTemplateEngine.class)
SpringTemplateEngine templateEngine(ThymeleafProperties properties,
ObjectProvider<ITemplateResolver> templateResolvers, ObjectProvider<IDialect> dialects) {
SpringTemplateEngine engine = new SpringTemplateEngine();
engine.setEnableSpringELCompiler(properties.isEnableSpringElCompiler());
engine.setRenderHiddenMarkersBeforeCheckboxes(properties.isRenderHiddenMarkersBeforeCheckboxes());
templateResolvers.orderedStream().forEach(engine::addTemplateResolver);
dialects.orderedStream().forEach(engine::addDialect);
return engine;
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnProperty(name = "spring.thymeleaf.enabled", matchIfMissing = true)
static class ThymeleafWebMvcConfiguration {
@Bean
@ConditionalOnEnabledResourceChain
@ConditionalOnMissingFilterBean(ResourceUrlEncodingFilter.class)
FilterRegistrationBean<ResourceUrlEncodingFilter> resourceUrlEncodingFilter() {
FilterRegistrationBean<ResourceUrlEncodingFilter> registration = new FilterRegistrationBean<>(
new ResourceUrlEncodingFilter());
registration.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ERROR);
return registration;
}
//比較重要的視圖解析器配置
@Configuration(proxyBeanMethods = false)
static class ThymeleafViewResolverConfiguration {
@Bean
@ConditionalOnMissingBean(name = "thymeleafViewResolver")
ThymeleafViewResolver thymeleafViewResolver(ThymeleafProperties properties,
SpringTemplateEngine templateEngine) {
ThymeleafViewResolver resolver = new ThymeleafViewResolver();
//設定模闆引擎
resolver.setTemplateEngine(templateEngine);
//字元編碼設定
resolver.setCharacterEncoding(properties.getEncoding().name());
resolver.setContentType(
appendCharset(properties.getServlet().getContentType(), resolver.getCharacterEncoding()));
resolver.setProducePartialOutputWhileProcessing(
properties.getServlet().isProducePartialOutputWhileProcessing());
resolver.setExcludedViewNames(properties.getExcludedViewNames());
resolver.setViewNames(properties.getViewNames());
// This resolver acts as a fallback resolver (e.g. like a
// InternalResourceViewResolver) so it needs to have low precedence
resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 5);
//Thymeleaf緩存
resolver.setCache(properties.isCache());
return resolver;
}
private String appendCharset(MimeType type, String charset) {
if (type.getCharset() != null) {
return type.toString();
}
LinkedHashMap<String, String> parameters = new LinkedHashMap<>();
parameters.put("charset", charset);
parameters.putAll(type.getParameters());
return new MimeType(type, parameters).toString();
}
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.REACTIVE)
@ConditionalOnProperty(name = "spring.thymeleaf.enabled", matchIfMissing = true)
static class ThymeleafReactiveConfiguration {
@Bean
@ConditionalOnMissingBean(ISpringWebFluxTemplateEngine.class)
SpringWebFluxTemplateEngine templateEngine(ThymeleafProperties properties,
ObjectProvider<ITemplateResolver> templateResolvers, ObjectProvider<IDialect> dialects) {
SpringWebFluxTemplateEngine engine = new SpringWebFluxTemplateEngine();
engine.setEnableSpringELCompiler(properties.isEnableSpringElCompiler());
engine.setRenderHiddenMarkersBeforeCheckboxes(properties.isRenderHiddenMarkersBeforeCheckboxes());
templateResolvers.orderedStream().forEach(engine::addTemplateResolver);
dialects.orderedStream().forEach(engine::addDialect);
return engine;
}
}
//ThymeleafWebFluxConfiguration自動配置
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.REACTIVE)
@ConditionalOnProperty(name = "spring.thymeleaf.enabled", matchIfMissing = true)
static class ThymeleafWebFluxConfiguration {
@Bean
@ConditionalOnMissingBean(name = "thymeleafReactiveViewResolver")
ThymeleafReactiveViewResolver thymeleafViewResolver(ISpringWebFluxTemplateEngine templateEngine,
ThymeleafProperties properties) {
ThymeleafReactiveViewResolver resolver = new ThymeleafReactiveViewResolver();
resolver.setTemplateEngine(templateEngine);
mapProperties(properties, resolver);
mapReactiveProperties(properties.getReactive(), resolver);
// This resolver acts as a fallback resolver (e.g. like a
// InternalResourceViewResolver) so it needs to have low precedence
resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 5);
return resolver;
}
private void mapProperties(ThymeleafProperties properties, ThymeleafReactiveViewResolver resolver) {
PropertyMapper map = PropertyMapper.get();
map.from(properties::getEncoding).to(resolver::setDefaultCharset);
resolver.setExcludedViewNames(properties.getExcludedViewNames());
resolver.setViewNames(properties.getViewNames());
}
private void mapReactiveProperties(Reactive properties, ThymeleafReactiveViewResolver resolver) {
PropertyMapper map = PropertyMapper.get();
map.from(properties::getMediaTypes).whenNonNull().to(resolver::setSupportedMediaTypes);
map.from(properties::getMaxChunkSize).asInt(DataSize::toBytes).when((size) -> size > 0)
.to(resolver::setResponseMaxChunkSizeBytes);
map.from(properties::getFullModeViewNames).to(resolver::setFullModeViewNames);
map.from(properties::getChunkedModeViewNames).to(resolver::setChunkedModeViewNames);
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(LayoutDialect.class)
static class ThymeleafWebLayoutConfiguration {
@Bean
@ConditionalOnMissingBean
LayoutDialect layoutDialect() {
return new LayoutDialect();
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(DataAttributeDialect.class)
static class DataAttributeDialectConfiguration {
@Bean
@ConditionalOnMissingBean
DataAttributeDialect dialect() {
return new DataAttributeDialect();
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ SpringSecurityDialect.class })
static class ThymeleafSecurityDialectConfiguration {
@Bean
@ConditionalOnMissingBean
SpringSecurityDialect securityDialect() {
return new SpringSecurityDialect();
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Java8TimeDialect.class)
static class ThymeleafJava8TimeDialect {
@Bean
@ConditionalOnMissingBean
Java8TimeDialect java8TimeDialect() {
return new Java8TimeDialect();
}
}
}
ThymeleafProperties是SpringBoot的屬性配置類,使用ConfigurationProperties注解進行屬性映射
@ConfigurationProperties(prefix = "spring.thymeleaf")
public class ThymeleafProperties {
private static final Charset DEFAULT_ENCODING = StandardCharsets.UTF_8;
//預設的模闆資源路徑
public static final String DEFAULT_PREFIX = "classpath:/templates/";
//預設解析html資源
public static final String DEFAULT_SUFFIX = ".html";
/**
* Whether to check that the template exists before rendering it.
*/
private boolean checkTemplate = true;
/**
* Whether to check that the templates location exists.
*/
private boolean checkTemplateLocation = true;
/**
* Prefix that gets prepended to view names when building a URL.
*/
private String prefix = DEFAULT_PREFIX;
/**
* Suffix that gets appended to view names when building a URL.
*/
private String suffix = DEFAULT_SUFFIX;
/**
* Template mode to be applied to templates. See also Thymeleaf's TemplateMode enum.
*/
//預設模式也是html的
private String mode = "HTML";
/**
* Template files encoding.
*/
private Charset encoding = DEFAULT_ENCODING;
/**
* Whether to enable template caching.
*/
//預設開啟緩存,項目沒上線建議通過配置關閉,然後按F9就可以自動編譯,避免影響調試
private boolean cache = true;
....
}
ok,然後簡單跟一下視圖解析器的源碼:Thymeleaf視圖解析器類的關鍵代碼,建立視圖view的方法,如圖,也是根據viewname進行重定向
在這裡插入圖檔描述
從上面方法可以看出進行重定向或者forward等等方法,然後調一下redirect的,看看RedirectView類,翻下源碼,找到如下關鍵代碼:
在這裡插入圖檔描述
同樣在這個類裡,進行了狀态碼設定,請求頭設定,然後response.sendRedirect(encodedURL);
在這裡插入圖檔描述
而forward的是通過如圖方法進行頁面跳轉: