整合完数据源MyBatis、SpringMVC之后,我们继续从后往前,到了页面展示的整合部分,之前我们的SSM框架使用的是JSP做为前端页面,在更早的Servlet时代用的也是JSP页面,但是时代变了,因为有了SpringBoot,JSP不再是前端页面的最佳选择了,本篇Blog就来介绍下一个SpringBoot官方推荐的默认模板引擎。
模板引擎概念
模板引擎(这里特指用于Web开发的模板引擎)是为了使用户界面与业务数据(内容)分离而产生的,它可以生成特定格式的文档,用于网站的模板引擎就会生成一个标准的HTML文档,其实JSP就是一个模板引擎。
通俗的讲,模板引擎的作用就是当我们创建一个前端页面时,有些值是动态的,需要写表达式从后端获取。模板引擎按照数据进行表达式解析、填充到页面指定的位置,然后把这个数据最终生成目标页面返回,不管是JSP还是我们本篇提到的Thymeleaf,抑或是其他模板引擎,功能作用都是一样的,不同模板引擎之间语法不同而已。
SpringBoot为什么不使用JSP
JSP作为模板引擎的功能很强大,但是有两个原因导致SpringBoot不能使用JSP作为模板引擎
- SpringBoot用Jar打包而非War,springboot无法与jsp作为视图解析器一起正常使用的主要原因是因为Tomcat使用了硬编码的文件模式,当使用Jar打包时不会从WEB-INF文件夹中复制JSP文件,导致无法发现和解析JSP
- SpringBoot使用嵌入式Tomcat,默认情况下,嵌入式Tomcat软件包(在springboot中用于创建可执行jar)不包含JSP文件格式
所以可以理解为目前SpringBoot没有对JSP有很好的支持,而且更方便的选择还有很多,所以就抛弃了JSP转而使用Thymeleaf
Thymeleaf
Thymeleaf 是一款用于渲染 XML/XHTML/HTML5 内容的模板引擎。它与 JSP,Velocity,FreeMaker
等模板引擎类似,也可以轻易地与 Spring MVC 、SpringBoot等 Web 框架集成。与其它模板引擎相比,Thymeleaf 最大的特点是,即使不启动 Web 应用,也可以直接在浏览器中打开并正确显示模板页面 。
1 Thymeleaf 基本特点
与 Velocity、FreeMarker 等传统 Java 模板引擎不同,Thymeleaf 支持 HTML 原型,其文件后缀为.html
:
- 当直接被浏览器打开时浏览器会忽略未定义的 Thymeleaf 标签属性,展示 thymeleaf 模板的静态页面效果;
- 当通过 Web 应用程序访问时,Thymeleaf 会动态地替换掉静态内容,使页面动态显示
Thymeleaf 通过在 html 标签中,增加额外属性来达到模板+数据的展示方式
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <!--th:text 为 Thymeleaf 属性,用于在展示文本--> <h1 th:text="能看到我说明你是在web模式下启动">直接打开浏览器你是看不到我的动态数据的</h1> </body> </html>
例如我们直接从浏览器打开该文件:
通过Web程序启动的效果等我们通过SpringBoot整合Thymeleaf之后再查看。Thymeleaf整体具备如下特点:
- 动静结合:Thymeleaf 既可以直接使用浏览器打开,查看页面的静态效果,也可以通过 Web 应用程序进行访问,查看动态页面效果。
- 开箱即用:Thymeleaf 提供了 Spring 标准方言以及一个与 SpringMVC 完美集成的可选模块,可以快速的实现表单绑定、属性编辑器、国际化等功能。
- 多方言支持:它提供了 Thymeleaf 标准和 Spring 标准两种方言,可以直接套用模板实现 JSTL、 OGNL 表达式;必要时,开发人员也可以扩展和创建自定义的方言。
- 与 SpringBoot 完美整合:SpringBoot 为 Thymeleaf 提供了的默认配置,并且还为 Thymeleaf 设置了视图解析器,因此 Thymeleaf 可以与 Spring Boot 完美整合。
在我看来其实最大的原因就是Thymeleaf能与SpringBoot完美结合,这让我们使用起来很方便。
2 Thymeleaf 语法规则
在使用 Thymeleaf 之前,首先要在页面的 html 标签中声明名称空间
<html lang="en" xmlns:th="http://www.thymeleaf.org">
在 html 标签中声明此名称空间,可避免编辑器出现 html 验证错误,但这一步并非必须进行的,即使我们不声明该命名空间,也不影响 Thymeleaf 的使用,但我们最好配置上,防止不必要的错误。
Thymeleaf 语法分为:标准表达式语法,th 属性。
1 标准表达式语法
Thymeleaf 模板引擎支持多种表达式,可类比JSP中的EL表达式。
- 变量表达式:
${...}
- 选择变量表达式:
*{...}
- 链接表达式:
@{...}
- 国际化表达式:
#{...}
- 片段引用表达式:
~{...}
接下来我们分别试验下标准表达式语法的用法:
1 变量表达式
变量表达式也依据获取内容的不同分为如下三类:
获取对象的属性和方法
使用变量表达式可以获取对象的属性和方法,例如,获取 person 对象的 lastName 属性,表达式形式如下:
${person.lastName}
使用内置的基本对象
使用变量表达式还可以使用内置基本对象,获取内置对象的属性,调用内置对象的方法。 Thymeleaf 中常用的内置基本对象如下:
#ctx :上下文对象; #vars :上下文变量; #locale:上下文的语言环境; #request:HttpServletRequest 对象(仅在 Web 应用中可用); #response:HttpServletResponse 对象(仅在 Web 应用中可用); #session:HttpSession 对象(仅在 Web 应用中可用); #servletContext:ServletContext 对象(仅在 Web 应用中可用)
例如,我们通过以下 2 种形式,都可以获取到 session 对象中的 map 属性
${#session.getAttribute('map')} ${session.map}
使用内置的工具对象
除了能使用内置的基本对象外,变量表达式还可以使用一些内置的工具对象
strings:字符串工具对象,常用方法有:equals、equalsIgnoreCase、length、trim、toUpperCase、toLowerCase、indexOf、substring、replace、startsWith、endsWith,contains 和 containsIgnoreCase 等; numbers:数字工具对象,常用的方法有:formatDecimal 等; bools:布尔工具对象,常用的方法有:isTrue 和 isFalse 等; arrays:数组工具对象,常用的方法有:toArray、length、isEmpty、contains 和 containsAll 等; lists/sets:List/Set 集合工具对象,常用的方法有:toList、size、isEmpty、contains、containsAll 和 sort 等; maps:Map 集合工具对象,常用的方法有:size、isEmpty、containsKey 和 containsValue 等; dates:日期工具对象,常用的方法有:format、year、month、hour 和 createNow 等
例如,我们可以使用内置工具对象 strings 的 equals 方法,来判断字符串与对象的某个属性是否相等,代码如下
${#strings.equals('tml',name)}
2 选择变量表达式
选择变量表达式与变量表达式功能基本一致,只是在变量表达式的基础上增加了与 th:object 的配合使用。当使用 th:object 存储一个对象后,我们可以在其后代中使用选择变量表达式*{...}
获取该对象中的属性,其中,*
即代表该对象
<div th:object="${session.user}" > <p th:text="*{fisrtName}">firstname</p> </div>
3 链接表达式
不管是静态资源的引用,还是 form 表单的请求,凡是链接都可以用链接表达式 @{...}
。 链接表达式的形式结构如下: 无参请求:@{/xxx}
,有参请求:@{/xxx(k1=v1,k2=v2)}
例如使用链接表达式引入 css 样式表,代码如下
<link href="asserts/css/signin.css" th:href="@{/asserts/css/signin.css}" rel="stylesheet">
4 国际化表达式
国际化表达式一般用于国际化的场景
<div th:text="#{msg}" >
5 片段引用表达式
片段引用表达式用于在模板页面中引用其他的模板片段,该表达式支持以下 2 中语法结构:
- 推荐:
~{templatename::fragmentname}
- 支持:
~{templatename::#id}
以上语法结构说明如下:
- templatename:模版名,Thymeleaf 会根据模版名解析完整径:
/resources/templates/templatename.html
,要注意文件的路径。 - fragmentname:片段名,Thymeleaf 通过
th:fragment
声明定义代码块,即:<div th:fragment="fragmentname">
- id:HTML 的 id 选择器,使用时要在前面加上 # 号,不支持 class 选择器
在公共页面抽取部分再详细叙述
2 th属性语法
Thymeleaf 还提供了大量的 th 属性,有点类似于JSTL标签。这些属性可以直接在 HTML 标签中使用,其中常用 th 属性及其示例如下表
3 Thymeleaf 公共页面抽取
在 Web 项目中,通常会存在一些公共页面片段(重复代码),例如头部导航栏、侧边菜单栏和公共的 js css 等。我们一般会把这些公共页面片段抽取出来,存放在一个独立的页面中,然后再由其他页面根据需要进行引用,这样可以消除代码重复,使页面更加简洁
1 抽取公共页面
Thymeleaf 作为一种优雅且高度可维护的模板引擎,同样支持公共页面的抽取和引用。我们可以将公共页面片段抽取出来,存放到一个独立的页面中,并使用 Thymeleaf 提供的 th:fragment 属性为这些抽取出来的公共页面片段命名,将公共页面片段抽取出来,存放在 commons.html 中,代码如下
<div th:fragment="fragment-name" id="fragment-id"> <span>公共页面片段</span> </div>
2 引用公共页面
在 Thymeleaf 中,我们可以使用以下 3 个属性,将公共页面片段引入到当前页面中。
th:insert
:将代码块片段整个插入到使用了 th:insert 属性的 HTML 标签中;th:replace
:将代码块片段整个替换使用了 th:replace 属性的 HTML 标签中;th:include
:将代码块片段包含的内容插入到使用了 th:include 属性的 HTML 标签中。
使用上 3 个属性引入页面片段,都可以通过以下 2 种方式实现。
~{templatename::#id}
:模板名::选择器名~{templatename::fragmentname}
:模板名::片段名
也就是我们上边提到的片段引用表达式。在页面 fragment.html 中引入 commons.html 中声明的页面片段,可以通过以下方式实现
<!--th:insert 片段名引入--> <div th:insert="commons::fragment-name"></div> <!--th:insert id 选择器引入--> <div th:insert="commons::#fragment-id"></div> ------------------------------------------------ <!--th:replace 片段名引入--> <div th:replace="commons::fragment-name"></div> <!--th:replace id 选择器引入--> <div th:replace="commons::#fragment-id"></div> ------------------------------------------------ <!--th:include 片段名引入--> <div th:include="commons::fragment-name"></div> <!--th:include id 选择器引入--> <div th:include="commons::#fragment-id"></div>
启动 Spring Boot,使用浏览器访问 fragment.html,查看源码,结果如下
<!--th:insert 片段名引入--> <div> <div id="fragment-id"> <span>公共页面片段</span> </div> </div> <!--th:insert id 选择器引入--> <div> <div id="fragment-id"> <span>公共页面片段</span> </div> </div> ------------------------------------------------ <!--th:replace 片段名引入--> <div id="fragment-id"> <span>公共页面片段</span> </div> <!--th:replace id 选择器引入--> <div id="fragment-id"> <span>公共页面片段</span> </div> ------------------------------------------------ <!--th:include 片段名引入--> <div> <span>公共页面片段</span> </div> <!--th:include id 选择器引入--> <div> <span>公共页面片段</span> </div>