Spring Boot之路(三)--Thymeleaf模板引擎

Thymeleaf

在实际的公司开发中,传统的JSP技术由于前段后端结合太过紧密,而且后端人员需要随时测试数据能不能渲染在界面上,这就需要前端与后端部门过于紧耦合,不利于实际开发,反而是模板引擎用的较多(当然也不排除一些老的公司需要维护公司以前的动态JSP网页)

thymeleaf最大的优势后缀为html,就是只需要浏览器就可以展现页面了,还有就是thymeleaf可以很好的和spring集成.

Thymeleaf是一个XML/XHTML/HTML5模板引擎,可用于Web与非Web环境中的应用开发。它是一个开源的Java库,基于Apache License 2.0许可,由Daniel Fernández创建,该作者还是Java加密库Jasypt的作者。

Thymeleaf提供了一个用于整合Spring MVC的可选模块,在应用开发中,你可以使用Thymeleaf来完全代替JSP或其他模板引擎,如Velocity、FreeMarker等。Thymeleaf的主要目标在于提供一种可被浏览器正确显示的、格式良好的模板创建方式,因此也可以用作静态建模。你可以使用它创建经过验证的XML与HTML模板。相对于编写逻辑或代码,开发者只需将标签属性添加到模板中即可。接下来,这些标签属性就会在DOM(文档对象模型)上执行预先制定好的逻辑。

静态资源访问

在我们开发Web应用的时候,需要引用大量的js、css、图片等静态资源。

默认配置

Spring Boot默认提供静态资源目录位置需置于classpath下,目录名需符合如下规则:

/static
/public
/resources
/META-INF/resources

举例:我们可以在src/main/resources/目录下创建static,在该位置放置一个图片文件。启动程序后,尝试访问http://localhost:8088/D.jpg。如能显示图片,配置成功。

在之前的示例中,我们都是通过@RestController来处理请求,所以返回的内容为json对象。那么如果需要渲染html页面的时候,要如何实现呢?

在动态HTML实现上Spring Boot依然可以完美胜任,并且提供了多种模板引擎的默认配置支持,所以在推荐的模板引擎下,我们可以很快的上手开发动态网站。

Spring Boot提供了默认配置的模板引擎(抛弃传统的JSP动态网页)主要有以下几种:

Thymeleaf
FreeMarker
Velocity
Groovy
Mustache

Spring Boot建议使用这些模板引擎,避免使用JSP,若一定要使用JSP将无法实现Spring Boot的多种特性,具体可见后文:支持JSP的配置

当你使用上述模板引擎中的任何一个,它们默认的模板配置路径为:src/main/resources/templates。当然也可以修改这个路径,具体如何修改,可在后续各模板引擎的配置属性中查询并修改。

示例:

<table>
  <thead>
    <tr>
      <th th:text="#{msgs.headers.name}">Name</td>
      <th th:text="#{msgs.headers.price}">Price</td>
    </tr>
  </thead>
  <tbody>
    <tr th:each="prod : ${allProducts}">
      <td th:text="${prod.name}">Oranges</td>
      <td th:text="${#numbers.formatDecimal(prod.price,1,2)}">0.99</td>
    </tr>
  </tbody>
</table>

可以看到Thymeleaf主要以属性的方式加入到html标签中,浏览器在解析html时,当检查到没有的属性时候会忽略,所以Thymeleaf的模板可以通过浏览器直接打开展现,这样非常有利于前后端的分离。

在Spring Boot中使用Thymeleaf,只需要引入下面依赖,并在默认的模板路径src/main/resources/templates下编写模板文件即可完成。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

在完成配置之后,举一个简单的例子,在快速入门工程的基础上,举一个简单的示例来通过Thymeleaf渲染一个页面。

package com.example.springboot.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class IndexController {

    @RequestMapping("/index")
    public String index(ModelMap map){

        map.addAttribute("hello", "i love you@com");
        return "index";
    }
}

相应的index.html界面代码为

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8"></meta>
<title>Insert title here</title>
</head>
<body>
  <h1 th:text="${hello}">Hello World</h1>
</body>
</html>

启动程序后,页面输入http://localhost:8088/index,所出现的显示数据是:i love you@com,并没有出现Hello World
展现的是IndexController中的hello的属性值,做到了不破坏HTML自身内容的数据逻辑分离。

注意:我们建立模板测试或者是实际开发中,我们都需要严格遵守thymeleaf的HTML5页面规范,不然会报错
包括我上面写的标签则需要双标签来自正常结束才可以,否则页面将出现错误,你可以使用严格的标签,也就是每个标签都有结束标签,这种可能比较麻烦。

那么如何在thymeleaf中声明使用非严格的html5规范呢?

maven添加依赖

<dependency>
        <groupId>net.sourceforge.nekohtml</groupId>
        <artifactId>nekohtml</artifactId>
        <version>1.9.22</version>
</dependency>

application.properties属性文件中添加

spring.thymeleaf.mode = LEGACYHTML5

声明thymeleaf使用非严格的html即可。最后更新下maven仓库即可生效。

关于Thymeleaf的默认参数配置

如有需要修改默认配置的时候,只需复制下面要修改的属性到application.properties中,并修改成需要的值,如修改模板文件的扩展名,修改默认的模板路径等。

-Enable template caching.
spring.thymeleaf.cache=true
-Check that the templates location exists.
spring.thymeleaf.check-template-location=true
-Content-Type value.
spring.thymeleaf.content-type=text/html
-Enable MVC Thymeleaf view resolution.
spring.thymeleaf.enabled=true
-Template encoding.
spring.thymeleaf.encoding=UTF-8
-Comma-separated list of view names that should be excluded from resolution.
spring.thymeleaf.excluded-view-names=
-Template mode to be applied to templates. See also StandardTemplateModeHandlers.
spring.thymeleaf.mode=HTML5
-Prefix that gets prepended to view names when building a URL.
spring.thymeleaf.prefix=classpath:/templates/
-Suffix that gets appended to view names when building a URL.
spring.thymeleaf.suffix=.html spring.thymeleaf.template-resolver-order= # Order of the template resolver in the chain. spring.thymeleaf.view-names= # Comma-separated list of view names that can be resolved.

关于Thymeleaf语法

命名空间

对于Thymeleaf语法来说,很多资料中会写应用Thymeleaf语法需要先在标签中引入
在html中引入此命名空间,可避免编辑器出现html验证错误,虽然加不加命名空间对Thymeleaf的功能没有任何影响。

<html xmlns:th="http://www.thymeleaf.org">

才可以使用Thymeleaf语法,但是我在上面的案例中并没有声明html标签的属性也可以用,也让我很费解。(不过为了一些不必要的麻烦还是把这个声明加上)

输出内容

例如:

Controller:
    map.addAttribute("hello", "i love you@com");
HTML:
    <h1 th:text="${hello}">Hello World</h1>

1. th:text  用来将内容输出到所在标签的body中。
2. ${hello} 用来引用hello属性值
3. 可以用th:utext 用来显示“unescaped ” 的html内容。(unescaped即非转义字符)

例如:

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

`#{home.welcome} 用来引入数据home对象中的 welcome属性。`

标准表达式语法

它们分为四类:

1.变量表达式
2.选择或星号表达式
3.文字国际化表达式
4.URL表达式

变量表达式

变量表达式即OGNL表达式或Spring EL表达式(在Spring术语中也叫model attributes)。如下所示:

${session.user.name}
${hello}

它们将以HTML标签的一个属性来表示:

<span th:text="${book.author.name}">  
<li th:each="book : ${books}">  

选择(星号)表达式

选择表达式很像变量表达式,不过它们用一个预先选择的对象来代替上下文变量容器(map)来执行,如下:

*{customer.name}

被指定的object由th:object属性定义:

<div th:object="${book}">  
  ...  
  <span th:text="*{title}">...</span>  
  ...  
</div>  

文字国际化表达式

文字国际化表达式允许我们从一个外部文件获取区域文字信息(.properties),用Key索引Value,还可以提供一组参数(可选).

`#{main.title} `
`#{message.entrycreated(${entryId})} `

可以在模板文件中找到这样的表达式代码:

<table>  
  ...  
  <th th:text="#{header.address.city}">...</th>  
  <th th:text="#{header.address.country}">...</th>  
  ...  
</table> 

URL表达式

URL表达式指的是把一个有用的上下文或回话信息添加到URL,这个过程经常被叫做URL重写。
@{/order/list}

URL还可以设置参数:
@{/order/details(id=${orderId})}

相对路径:
@{../documents/report}

让我们看这些表达式:

<form th:action="@{/createOrder}">  
<a href="main.html" th:href="@{/main}">

变量表达式和星号表达有什么区别吗?

如果不考虑上下文的情况下,两者没有区别;星号语法评估在选定对象上表达,而不是整个上下文
什么是选定对象?就是父标签的值,如下:(相当于星号表达式会去全局中找这个属性,如果找不到则默认不显示)

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

完全等价于

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

当然,美元符号和星号语法可以混合使用:

<div th:object="${session.user}">
      <p>Name: <span th:text="*{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>

表达式支持的语法

  • 字面(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)

所有这些特征可以被组合并嵌套:

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

常用th标签都有那些?

logo
logo
logo

几种常用的使用方法

几种Thymeleaf在实际开发中的应用(需要掌握的)

  • 赋值、字符串拼接

    description


注意:th:text=””,双引号里面才是正确输出的内容,拼接的字符串要用‘’单引号,
也有更加简易的拼接字符串的方法,用||符号

<h1 th:text="|hello,*{hello}|">Hello World</h1>
  • 条件判断 If/Unless

Thymeleaf中使用th:if和th:unless属性进行条件判断,下面的例子中,标签只有在th:if中条件成立时才显示:

<a th:if="${myself=='yes'}" ></a>
<a th:unless=${session.user != null} th:href="@{/login}" >Login</a>

th:unless于th:if恰好相反,只有表达式中的条件不成立,才会显示其内容。

也可以使用 (if) ? (then) : (else) 这种语法来判断显示的内容

  • for 循环

    <tr  th:each="collect,iterStat : ${collects}"> 
       <th scope="row" th:text="${collect.id}">1</th>
       <td >
          <img th:src="${collect.webLogo}"/>
       </td>
       <td th:text="${collect.url}">Mark</td>
       <td th:text="${collect.title}">Otto</td>
       <td th:text="${collect.description}">@mdo</td>
       <td th:text="${terStat.index}">index</td>
    </tr>
    

iterStat称作状态变量,属性有:

index:当前迭代对象的index(从0开始计算)
count: 当前迭代对象的index(从1开始计算)
size:被迭代对象的大小
current:当前迭代变量
even/odd:布尔值,当前循环是否是偶数/奇数(从0开始计算)
first:布尔值,当前循环是否是第一个
last:布尔值,当前循环是否是最后一个

  • URL

URL在Web应用模板中占据着十分重要的地位,需要特别注意的是Thymeleaf对于URL的处理是通过语法@{…}来处理的。
如果需要Thymeleaf对URL进行渲染,那么务必使用th:href,th:src等属性,下面是一个例子

<!-- Will produce 'http://localhost:8080/standard/unread' (plus rewriting) -->
 <a  th:href="@{/standard/{type}(type=${type})}">view</a>

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

设置背景

<div th:style="'background:url(' + @{/<path-to-image>} + ');'"></div>

根据属性值改变背景

<div class="media-object resource-card-image"  th:style="'background:url(' + @{(${collect.webLogo}=='' ? 'img/favicon.png' : ${collect.webLogo})} + ')'" ></div>

几点说明:

上例中URL最后的(orderId=${o.id}) 表示将括号内的内容作为URL参数处理,该语法避免使用字符串拼接,大大提高了可读性
@{…}表达式中可以通过{orderId}访问Context中的orderId变量
@{/order}是Context相关的相对路径,在渲染时会自动添加上当前Web应用的Context名字,假设context名字为app,那么结果应该是/app/order

  • 内联js

内联文本:[[…]]内联文本的表示方式,使用时,必须先用th:inline=”text/javascript/none”激活,th:inline可以在父级标签内使用,甚至作为body的标签。内联文本尽管比th:text的代码少,不利于原型显示。

<script th:inline="javascript">
/*<![CDATA[*/
...
var username = /*[[${sesion.user.name}]]*/ 'Sebastian';
var size = /*[[${size}]]*/ 0;
...
/*]]>*/
</script>

js附加代码:

/*[+
var msg = 'This is a working application';
 +]*/

js移除代码:

/*[- */
var msg = 'This is a non-working template';
/* -]*/
  • 内嵌变量

为了模板更加易用,Thymeleaf还提供了一系列Utility对象(内置于Context中),可以通过#直接访问:

dates : java.util.Date的功能方法类。
calendars : 类似#dates,面向java.util.Calendar
numbers : 格式化数字的功能方法类
strings : 字符串对象的功能类,contains,startWiths,prepending/appending等等。
objects: 对objects的功能类操作。
bools: 对布尔值求值的功能方法。
arrays:对数组的功能类方法。
lists: 对lists功能类方法
sets
maps

下面用一段代码来举例一些常用的方法:

1.dates

/*
 * Format date with the specified pattern
 * Also works with arrays, lists or sets
 */
${#dates.format(date, 'dd/MMM/yyyy HH:mm')}
${#dates.arrayFormat(datesArray, 'dd/MMM/yyyy HH:mm')}
${#dates.listFormat(datesList, 'dd/MMM/yyyy HH:mm')}
${#dates.setFormat(datesSet, 'dd/MMM/yyyy HH:mm')}

/*
 * Create a date (java.util.Date) object for the current date and time
 */
${#dates.createNow()}

/*
 * Create a date (java.util.Date) object for the current date (time set to 00:00)
 */
${#dates.createToday()}

2.strings

/*
 * Check whether a String is empty (or null). Performs a trim() operation before check
 * Also works with arrays, lists or sets
 */
${#strings.isEmpty(name)}
${#strings.arrayIsEmpty(nameArr)}
${#strings.listIsEmpty(nameList)}
${#strings.setIsEmpty(nameSet)}

/*
 * Check whether a String starts or ends with a fragment
 * Also works with arrays, lists or sets
 */
${#strings.startsWith(name,'Don')}                  // also array*, list* and set*
${#strings.endsWith(name,endingFragment)}           // also array*, list* and set*

/*
 * Compute length
 * Also works with arrays, lists or sets
 */
${#strings.length(str)}

/*
 * Null-safe comparison and concatenation
 */
${#strings.equals(str)}
${#strings.equalsIgnoreCase(str)}
${#strings.concat(str)}
${#strings.concatReplaceNulls(str)}

/*
 * Random
 */
${#strings.randomAlphanumeric(count)}
  • 使用thymeleaf布局

使用thymeleaf布局非常的方便

定义代码片段

<footer th:fragment="copy"> 
&copy; 2016
</footer>

在页面任何地方引入:

<body> 
  <div th:include="footer :: copy"></div>
  <div th:replace="footer :: copy"></div>
 </body>‘

th:include 和 th:replace区别,include只是加载,replace是替换

返回的HTML如下:

<body> 
   <div> &copy; 2016 </div> 
  <footer>&copy; 2016 </footer> 
</body>

下面是一个常用的后台页面布局,将整个页面分为头部,尾部、菜单栏、隐藏栏,点击菜单只改变content区域的页面

<body class="layout-fixed">
  <div th:fragment="navbar"  class="wrapper"  role="navigation">
    <div th:replace="fragments/header :: header">Header</div>
    <div th:replace="fragments/left :: left">left</div>
    <div th:replace="fragments/sidebar :: sidebar">sidebar</div>
    <div layout:fragment="content" id="content" ></div>
    <div th:replace="fragments/footer :: footer">footer</div>
  </div>
</body>

任何页面想使用这样的布局值只需要替换中见的 content模块即可

<html xmlns:th="http://www.thymeleaf.org" layout:decorator="layout">
  <body>
     <section layout:fragment="content">
   ...

也可以在引用模版的时候传参

<head th:include="layout :: htmlhead" th:with="title='Hello'"></head>

layout 是文件地址,如果有文件夹可以这样写 fileName/layout:htmlhead
htmlhead 是指定义的代码片段 如 th:fragment=”copy”

开源云收藏项目的github源码:https://github.com/cloudfavorites/favorites-web
里面用的技术包括springboot集成了tomcat和thymeleaf,源码测试里都是详细的项目功能介绍。

还有就是Springboot虽然不建议使用JSP技术,但是若有相应的要求也可以使用。具体自查。

坚持原创技术分享,您的支持将鼓励我继续创作!
+