@[toc] 虽然如今流行先后端分离,可是后端模版在一些关键地方仍是很是有用的,例如邮件模版、代码模版等。固然也不排除一些古老的项目后端依然使用动态模版。javascript
Thymeleaf 简洁漂亮、容易理解,而且完美支持 HTML5,能够直接打开静态页面,同时不新增标签,只需加强属性,这样也下降了学习成本。css
所以松哥今天花点时间和你们仔细分享一下 Thymeleaf。html
1. Thymeleaf 简介
Thymeleaf 是新一代 Java 模板引擎,它相似于 Velocity、FreeMarker 等传统 Java 模板引擎,可是与传统 Java 模板引擎不一样的是,Thymeleaf 支持 HTML 原型。前端
它既可让前端工程师在浏览器中直接打开查看样式,也可让后端工程师结合真实数据查看显示效果,同时,SpringBoot 提供了 Thymeleaf 自动化配置解决方案,所以在 SpringBoot 中使用 Thymeleaf 很是方便。java
事实上, Thymeleaf 除了展现基本的 HTML ,进行页面渲染以外,也能够做为一个 HTML 片断进行渲染,例如咱们在作邮件发送时,可使用 Thymeleaf 做为邮件发送模板。jquery
另外,因为 Thymeleaf 模板后缀为 .html
,能够直接被浏览器打开,所以,预览时很是方便。git
2. 整合 Spring Boot
2.1 基本用法
Spring Boot 中整合 Thymeleaf 很是容易,只须要建立项目时添加 Thymeleaf 便可:web
建立完成后,pom.xml 依赖以下:spring
<dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-thymeleaf</artifactid> </dependency> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-web</artifactid> </dependency>
固然,Thymeleaf 不只仅能在 Spring Boot 中使用,也可使用在其余地方,只不过 Spring Boot 针对 Thymeleaf 提供了一整套的自动化配置方案,这一套配置类的属性在 org.springframework.boot.autoconfigure.thymeleaf.ThymeleafProperties
中,部分源码以下:后端
@ConfigurationProperties(prefix = "spring.thymeleaf") public class ThymeleafProperties { private static final Charset DEFAULT_ENCODING = StandardCharsets.UTF_8; public static final String DEFAULT_PREFIX = "classpath:/templates/"; public static final String DEFAULT_SUFFIX = ".html"; private boolean checkTemplate = true; private boolean checkTemplateLocation = true; private String prefix = DEFAULT_PREFIX; private String suffix = DEFAULT_SUFFIX; private String mode = "HTML"; private Charset encoding = DEFAULT_ENCODING; private boolean cache = true; //... }
- 首先经过
@ConfigurationProperties
注解,将application.properties
前缀为spring.thymeleaf
的配置和这个类中的属性绑定。 - 前三个
static
变量定义了默认的编码格式、视图解析器的前缀、后缀等。 - 从前三行配置中,能够看出来,
Thymeleaf
模板的默认位置在resources/templates
目录下,默认的后缀是html
。 - 这些配置,若是开发者不本身提供,则使用 默认的,若是本身提供,则在
application.properties
中以spring.thymeleaf
开始相关的配置。
而咱们刚刚提到的,Spring Boot 为 Thymeleaf 提供的自动化配置类,则是 org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration
,部分源码以下:
@Configuration @EnableConfigurationProperties(ThymeleafProperties.class) @ConditionalOnClass({ TemplateMode.class, SpringTemplateEngine.class }) @AutoConfigureAfter({ WebMvcAutoConfiguration.class, WebFluxAutoConfiguration.class }) public class ThymeleafAutoConfiguration { }
能够看到,在这个自动化配置类中,首先导入 ThymeleafProperties
,而后 @ConditionalOnClass
注解表示当当前系统中存在 TemplateMode
和 SpringTemplateEngine
类时,当前的自动化配置类才会生效,即只要项目中引入了 Thymeleaf
相关的依赖,这个配置就会生效。
这些默认的配置咱们几乎不须要作任何更改就能够直接使用了。若是开发者有特殊需求,则能够在 application.properties 中配置以 spring.thymeleaf 开头的属性便可。
接下来咱们就能够建立 Controller 了,实际上引入 Thymeleaf 依赖以后,咱们能够不作任何配置。新建的 IndexController 以下:
@Controller public class IndexController { @GetMapping("/index") public String index(Model model) { List<user> users = new ArrayList<>(); for (int i = 0; i < 10; i++) { User u = new User(); u.setId((long) i); u.setName("javaboy:" + i); u.setAddress("深圳:" + i); users.add(u); } model.addAttribute("users", users); return "index"; } } public class User { private Long id; private String name; private String address; //省略 getter/setter }
在 IndexController
中返回逻辑视图名+数据,逻辑视图名为 index
,意思咱们须要在 resources/templates
目录下提供一个名为 index.html
的 Thymeleaf
模板文件。
- 建立 Thymeleaf
<meta charset="UTF-8"> <title>Title</title> <table border="1"> <tbody><tr> <td>编号</td> <td>用户名</td> <td>地址</td> </tr> <tr th:each="user : ${users}"> <td th:text="${user.id}"></td> <td th:text="${user.name}"></td> <td th:text="${user.address}"></td> </tr> </tbody></table>
在 Thymeleaf
中,经过 th:each
指令来遍历一个集合,数据的展现经过 th:text
指令来实现,
注意 index.html
最上面引入 thymeleaf
名称空间(最新版并没有强制要求)。
配置完成后,就能够启动项目了,访问 /index 接口,就能看到集合中的数据了:
2.2 手动渲染
前面咱们说的是返回一个 Thymeleaf 模板,咱们也能够手动渲染 Thymeleaf 模板,这个通常在邮件发送时候有用,例如我在 resources/templates 目录下新建一个邮件模板,以下:
<meta charset="UTF-8"> <title>Title</title> <p>hello 欢迎 <span th:text="${username}"></span>加入 XXX 集团,您的入职信息以下:</p> <table border="1"> <tbody><tr> <td>职位</td> <td th:text="${position}"></td> </tr> <tr> <td>薪水</td> <td th:text="${salary}"></td> </tr> </tbody></table> <img src="https://oscimg.oschina.net/oscnet/javaboy.jpg" alt="">
这一个 HTML 模板中,有几个变量,咱们要将这个 HTML 模板渲染成一个 String 字符串,再把这个字符串经过邮件发送出去,那么如何手动渲染呢?
@Autowired TemplateEngine templateEngine; @Test public void test1() throws MessagingException { Context context = new Context(); context.setVariable("username", "javaboy"); context.setVariable("position", "Java工程师"); context.setVariable("salary", 99999); String mail = templateEngine.process("mail", context); //省略邮件发送 }
- 渲染时,咱们须要首先注入一个 TemplateEngine 对象,这个对象就是在 Thymeleaf 的自动化配置类中配置的(即当咱们引入 Thymeleaf 的依赖以后,这个实例就有了)。
- 而后构造一个 Context 对象用来存放变量。
- 调用 process 方法进行渲染,该方法的返回值就是渲染后的 HTML 字符串,而后咱们将这个字符串发送出去。
3. Thymeleaf 细节
前面两个案例让小伙伴们大体上理解了在 Spring Boot 中要如何使用 Thymeleaf,接下来,松哥将详细介绍 Thymeleaf 自己的一些具体用法。
3.1 标准表达式语法
3.1.1 简单表达式
${...}
直接使用 th:xx = "${}"
获取对象属性。这个在前面的案例中已经演示过了,再也不赘述。
*{...}
能够像 ${...}
同样使用,也能够经过 th:object
获取对象,而后使用 th:xx = "*{}"
获取对象属性,这种简写风格极为清爽,推荐你们在实际项目中使用。
<table border="1" th:object="${user}"> <tbody><tr> <td>用户名</td> <td th:text="*{username}"></td> </tr> <tr> <td>地址</td> <td th:text="*{address}"></td> </tr> </tbody></table>
#{...}
一般的国际化属性:#{...}
用于获取国际化语言翻译值。
在 resources 目录下新建两个文件:messages.properties 和 messages_zh_CN.properties,内容以下:
messages.properties:
message = javaboy
messages_zh_CN.properties:
message = 江南一点雨
而后在 thymeleaf 中引用 message,系统会根据浏览器的语言环境显示不一样的值:
<div th:text="#{message}"></div>
@{...}
- 引用绝对 URL:
<script type="text/javascript" th:src="@{http://localhost:8080/hello.js}"></script>
等价于:
<script type="text/javascript" src="http://localhost:8080/hello.js"></script>
- 上下文相关的 URL:
首先在 application.properties 中配置 Spring Boot 的上下文,以便于测试:
server.servlet.context-path=/myapp
引用路径:
<script type="text/javascript" th:src="@{/hello.js}"></script>
等价于:
<script type="text/javascript" src="/myapp/hello.js"></script>
- 相对 URL:
这个相对是指相对于服务器的 URL,例如以下引用:
<script type="text/javascript" th:src="@{~/hello.js}"></script>
等价于:
<script type="text/javascript" src="/hello.js"></script>
应用程序的上下文 /myapp 将被忽略。
- 协议相对 URL:
<script type="text/javascript" th:src="@{//localhost:8080/hello.js}"></script>
等价于:
<script type="text/javascript" src="//localhost:8080/hello.js"></script>
- 带参数的 URL:
<script type="text/javascript" th:src="@{//localhost:8080/hello.js(name='javaboy',age=99)}"></script>
等价于:
<script type="text/javascript" th:src="//localhost:8080/hello.js?name=javaboy&age=99"></script>
~{...}
片断表达式是 Thymeleaf 的特点之一,细粒度能够达到标签级别,这是 JSP 没法作到的。片断表达式拥有三种语法:
~{ viewName }
:表示引入完整页面~{ viewName ::selector}
:表示在指定页面寻找片断,其中 selector 可为片断名、jquery选择器等~{ ::selector}
: 表示在当前页寻找
举个简单例子。
在 resources/templates 目录下新建 my_fragment.html 文件,内容以下:
<div th:fragment="javaboy_link"><a href="http://www.javaboy.org">www.javaboy</a></div> <div th:fragment="itboyhub_link"><a href="http://www.itboyhub.com">www.itboyhub.com</a></div>
这里有两个 div,经过 th:fragment 来定义片断,两个 div 分别具备不一样的名字。
而后在另一个页面中引用该片断:
<table border="1" th:object="${user}" th:fragment="aaa"> <tbody><tr> <td>用户名</td> <td th:text="*{username}"></td> </tr> <tr> <td>地址</td> <td th:text="*{address}"></td> </tr> </tbody></table> <hr> <div th:replace="my_fragment.html"></div> <hr> <div th:replace="~{my_fragment.html::javaboy_link}"></div> <hr> <div th:replace="~{::aaa}"></div>
经过 th:replace 来引用片断。第一个表示引用完整的 my_fragment.html
页面;第二个表示引用 my_fragment.html
中的名为 javaboy_link
的片断;第三个表示引用当前页面名为 aaa 的片断,也就是上面那个 table。
3.1.2 字面量
这些是一些能够直接写在表达式中的字符,主要有以下几种:
- 文本字面量: 'one text', 'Another one!',…
- 数字字面量: 0, 34, 3.0, 12.3,…
- 布尔字面量: true, false
- Null字面量: null
- 字面量标记:one, sometext, main,…
案例:
<div th:text="'这是 文本字面量(有空格)'"></div> <div th:text="javaboy"></div> <div th:text="99"></div> <div th:text="true"></div>
若是文本是英文,而且不包含空格、逗号等字符,能够不用加单引号。
3.1.3 文本运算
文本可使用 +
进行拼接。
<div th:text="'hello '+'javaboy'"></div> <div th:text="'hello '+${user.username}"></div>
若是字符串中包含变量,也可使用另外一种简单的方式,叫作字面量置换,用 |
代替 '...' + '...'
,以下:
<div th:text="|hello ${user.username}|"></div> <div th:text="'hello '+${user.username}+' '+|Go ${user.address}|"></div>
3.1.4 算术运算
算术运算有:+
, -
, *
, /
和 %
。
<div th:with="age=(99*99/99+99-1)"> <div th:text="${age}"></div> </div>
th:with 定义了一个局部变量 age,在其所在的 div 中可使用该局部变量。
3.1.5 布尔运算
- 二元运算符:and, or
- 布尔非(一元运算符):!, not
案例:
<div th:with="age=(99*99/99+99-1)"> <div th:text="9 eq 9 or 8 ne 8"></div> <div th:text="!(9 eq 9 or 8 ne 8)"></div> <div th:text="not(9 eq 9 or 8 ne 8)"></div> </div>
3.1.6 比较和相等
表达式里的值可使用 >
, <
, >=
和 <=
符号比较。==
和 !=
运算符用于检查相等(或者不相等)。注意 XML
规定 <
和 >
标签不能用于属性值,因此应当把它们转义为 <
和 >
。
若是不想转义,也可使用别名:gt (>);lt (<);ge (>=);le (<=);not (!)。还有 eq (==), neq/ne (!=)。
举例:
<div th:with="age=(99*99/99+99-1)"> <div th:text="${age} eq 197"></div> <div th:text="${age} ne 197"></div> <div th:text="${age} ge 197"></div> <div th:text="${age} gt 197"></div> <div th:text="${age} le 197"></div> <div th:text="${age} lt 197"></div> </div>
3.1.7 条件运算符
相似于咱们 Java 中的三目运算符。
<div th:with="age=(99*99/99+99-1)"> <div th:text="(${age} ne 197)?'yes':'no'"></div> </div>
其中,: 后面的部分能够省略,若是省略了,又同时计算结果为 false 时,将返回 null。
3.1.8 内置对象
基本内置对象:
- #ctx:上下文对象。
- #vars: 上下文变量。
- #locale:上下文区域设置。
- #request:(仅在 Web 上下文中)HttpServletRequest 对象。
- #response:(仅在 Web 上下文中)HttpServletResponse 对象。
- #session:(仅在 Web 上下文中)HttpSession 对象。
- #servletContext:(仅在 Web 上下文中)ServletContext 对象。
在页面能够访问到上面这些内置对象,举个简单例子:
<div th:text="${#session.getAttribute("name")}"></div>
实用内置对象:
- #execInfo:有关正在处理的模板的信息。
- #messages:在变量表达式中获取外部化消息的方法,与使用#{...}语法得到的方式相同。
- #uris:转义URL / URI部分的方法
- #conversions:执行配置的转换服务(若是有)的方法。
- #dates:java.util.Date对象的方法:格式化,组件提取等
- #calendars:相似于#dates可是java.util.Calendar对象。
- #numbers:用于格式化数字对象的方法。
- #strings:String对象的方法:contains,startsWith,prepending / appending等
- #objects:通常对象的方法。
- #bools:布尔评估的方法。
- #arrays:数组方法。
- #lists:列表的方法。
- #sets:集合的方法。
- #maps:地图方法。
- #aggregates:在数组或集合上建立聚合的方法。
- #ids:处理可能重复的id属性的方法(例如,做为迭代的结果)。
这是一些内置对象以及工具方法,使用方式也都比较容易,若是使用的是 IntelliJ IDEA,都会自动提示对象中的方法,很方便。
举例:
<div th:text="${#execInfo.getProcessedTemplateName()}"></div> <div th:text="${#arrays.length(#request.getAttribute('names'))}"></div>
3.2 设置属性值
这个是给 HTML 元素设置属性值。能够一次设置多个,多个之间用 ,
分隔开。
例如:
<img th:attr="src=@{/1.png},title=${user.username},alt=${user.username}" src="">
会被渲染成:
<img src="/myapp/1.png" title="javaboy" alt="javaboy">
固然这种设置方法不太美观,可读性也很差。Thymeleaf 还支持在每个原生的 HTML 属性前加上 th: 前缀的方式来使用动态值,像下面这样:
<img th:src="@{/1.png}" th:alt="${user.username}" th:title="${user.username}" src="">
这种写法看起来更清晰一些,渲染效果和前面一致。
上面案例中的 alt 和 title 则是两个特殊的属性,能够一次性设置,像下面这样:
<img th:src="@{/1.png}" th:alt-title="${user.username}" src="">
这个等价于前文的设置。
3.3 遍历
数组/集合/Map/Enumeration/Iterator 等的遍历也算是一个很是常见的需求,Thymeleaf 中经过 th:each 来实现遍历,像下面这样:
<table border="1"> <tbody><tr th:each="u : ${users}"> <td th:text="${u.username}"></td> <td th:text="${u.address}"></td> </tr> </tbody></table>
users 是要遍历的集合/数组,u 则是集合中的单个元素。
遍历的时候,咱们可能须要获取遍历的状态,Thymeleaf 也对此提供了支持:
- index:当前的遍历索引,从0开始。
- count:当前的遍历索引,从1开始。
- size:被遍历变量里的元素数量。
- current:每次遍历的遍历变量。
- even/odd:当前的遍历是偶数次仍是奇数次。
- first:当前是否为首次遍历。
- last:当前是否为最后一次遍历。
u 后面的 state 表示遍历状态,经过遍历状态能够引用上面的属性。
<table border="1"> <tbody><tr th:each="u,state : ${users}"> <td th:text="${u.username}"></td> <td th:text="${u.address}"></td> <td th:text="${state.index}"></td> <td th:text="${state.count}"></td> <td th:text="${state.size}"></td> <td th:text="${state.current}"></td> <td th:text="${state.even}"></td> <td th:text="${state.odd}"></td> <td th:text="${state.first}"></td> <td th:text="${state.last}"></td> </tr> </tbody></table>
3.4 分支语句
只显示奇数次的遍历,可使用 th:if,以下:
<table border="1"> <tbody><tr th:each="u,state : ${users}" th:if="${state.odd}"> <td th:text="${u.username}"></td> <td th:text="${u.address}"></td> <td th:text="${state.index}"></td> <td th:text="${state.count}"></td> <td th:text="${state.size}"></td> <td th:text="${state.current}"></td> <td th:text="${state.even}"></td> <td th:text="${state.odd}"></td> <td th:text="${state.first}"></td> <td th:text="${state.last}"></td> </tr> </tbody></table>
th:if 不只仅只接受布尔值,也接受其余类型的值,例如以下值都会断定为 true:
- 若是值是布尔值,而且为 true。
- 若是值是数字,而且不为 0。
- 若是值是字符,而且不为 0。
- 若是值是字符串,而且不为 “false”, “off” 或者 “no”。
- 若是值不是布尔值,数字,字符或者字符串。
可是若是值为 null,th:if 会求值为 false。
th:unless 的断定条件则与 th:if 彻底相反。
<table border="1"> <tbody><tr th:each="u,state : ${users}" th:unless="${state.odd}"> <td th:text="${u.username}"></td> <td th:text="${u.address}"></td> <td th:text="${state.index}"></td> <td th:text="${state.count}"></td> <td th:text="${state.size}"></td> <td th:text="${state.current}"></td> <td th:text="${state.even}"></td> <td th:text="${state.odd}"></td> <td th:text="${state.first}"></td> <td th:text="${state.last}"></td> </tr> </tbody></table>
这个显示效果则与上面的彻底相反。
当可能性比较多的时候,也可使用 switch:
<table border="1"> <tbody><tr th:each="u,state : ${users}"> <td th:text="${u.username}"></td> <td th:text="${u.address}"></td> <td th:text="${state.index}"></td> <td th:text="${state.count}"></td> <td th:text="${state.size}"></td> <td th:text="${state.current}"></td> <td th:text="${state.even}"></td> <td th:text="${state.odd}"></td> <td th:text="${state.first}"></td> <td th:text="${state.last}"></td> <td th:switch="${state.odd}"> <span th:case="true">odd</span> <span th:case="*">even</span> </td> </tr> </tbody></table>
th:case="*"
则表示默认选项。
3.5 本地变量
这个咱们前面已经涉及到了,使用 th:with 能够定义一个本地变量。
3.6 内联
咱们可使用属性将数据放入页面模版中,可是不少时候,内联的方式看起来更加直观一些,像下面这样:
<div>hello [[${user.username}]]</div>
用内联的方式去作拼接也显得更加天然。
[[...]]
对应于 th:text (结果会是转义的 HTML),[(...)]
对应于 th:utext,它不会执行任何的 HTML 转义。
像下面这样:
<div th:with="str='hello <strong>javaboy</strong>'"> <div>[[${str}]]</div> <div>[(${str})]</div> </div>
最终的显示效果以下:
不过内联方式有一个问题。咱们使用 Thymeleaf 的一大优点在于不用动态渲染就能够直接在浏览器中看到显示效果,当咱们使用属性配置的时候确实是这样,可是若是咱们使用内联的方式,各类表达式就会直接展现在静态网页中。
也能够在 js 或者 css 中使用内联,以 js 为例,使用方式以下:
<script th:inline="javascript"> var username=[[${user.username}]] console.log(username) </script>
js 中须要经过 th:inline="javascript"
开启内联。
4. 小结
好啦,Thymeleaf 跟你们也介绍的差很少了,应付平常的工做应该是能够了。对 Thymeleaf 感兴趣的小伙伴,也能够看看它的官方文档: https://www.thymeleaf.org。
最后,松哥还搜集了 50+ 个项目需求文档,想作个项目练练手的小伙伴不妨看看哦~
需求文档地址:https://gitee.com/lenve/javadoc</user>