文章目录
8.1 Thymeleaf简介
8.1.1 Thymeleaf的同行
JSP、Freemarker、Velocity等等,它们有一个共同的名字:服务器端模板技术。
8.1.2 Thymeleaf优势
- SpringBoot官方推荐使用的视图模板技术,和SpringBoot完美整合。
- 不经过服务器运算仍然可以直接查看原始值,对前端工程师更友好。
8.1.3 物理视图和逻辑视图
-
物理视图
在Servlet中,将请求转发到一个HTML页面文件时,使用的完整的转发路径就是物理视图。
/pages/user/login_success.html
如果我们把所有的HTML页面都放在某个统一的目录下,那么转发地址就会呈现出明显的规律:
/pages/user/login.html
/pages/user/login_success.html
/pages/user/regist.html
/pages/user/regist_success.html
……
路径的开头都是:/pages/user/
路径的结尾都是:.html
所以,路径开头的部分我们称之为视图前缀,路径结尾的部分我们称之为视图后缀。
-
逻辑视图
物理视图=视图前缀+逻辑视图+视图后缀
上面的例子中:
视图前缀 逻辑视图 视图后缀 物理视图 /pages/user/ login .html /pages/user/login.html /pages/user/ login_success .html /pages/user/login_success.html
8.2 在服务器端引入Thymeleaf环境
- 加入jar包
-
配置上下文参数
物理视图=视图前缀+逻辑视图+视图后缀
<!-- 在上下文参数中配置视图前缀和视图后缀 --> <context-param> <param-name>view-prefix</param-name> <param-value>/WEB-INF/view/</param-value> </context-param> <context-param> <param-name>view-suffix</param-name> <param-value>.html</param-value> </context-param>
说明:param-value中设置的前缀、后缀的值不是必须叫这个名字,可以根据实际情况和需求进行修改。
为什么要放在WEB-INF目录下?
原因:WEB-INF目录不允许浏览器直接访问,所以我们的视图模板文件放在这个目录下,是一种保护。以免外界可以随意访问视图模板文件。
访问WEB-INF目录下的页面,都必须通过Servlet转发过来,简单说就是:不经过Servlet访问不了。
这样就方便我们在Servlet中检查当前用户是否有权限访问。
那放在WEB-INF目录下之后,重定向进不去怎么办?
重定向到Servlet,再通过Servlet转发到WEB-INF下。
-
创建Servlet基类
这个类大家直接复制粘贴即可,将来使用框架后,这些代码都将被取代。
import org.thymeleaf.TemplateEngine; import org.thymeleaf.context.WebContext; import org.thymeleaf.templatemode.TemplateMode; import org.thymeleaf.templateresolver.ServletContextTemplateResolver; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; public class ViewBaseServlet extends HttpServlet { private TemplateEngine templateEngine; @Override public void init() throws ServletException { // 1.获取ServletContext对象 ServletContext servletContext = this.getServletContext(); // 2.创建Thymeleaf解析器对象 ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver(servletContext); // 3.给解析器对象设置参数 // ①HTML是默认模式,明确设置是为了代码更容易理解 templateResolver.setTemplateMode(TemplateMode.HTML); // ②设置前缀 String viewPrefix = servletContext.getInitParameter("view-prefix"); templateResolver.setPrefix(viewPrefix); // ③设置后缀 String viewSuffix = servletContext.getInitParameter("view-suffix"); templateResolver.setSuffix(viewSuffix); // ④设置缓存过期时间(毫秒) templateResolver.setCacheTTLMs(60000L); // ⑤设置是否缓存 templateResolver.setCacheable(true); // ⑥设置服务器端编码方式 templateResolver.setCharacterEncoding("utf-8"); // 4.创建模板引擎对象 templateEngine = new TemplateEngine(); // 5.给模板引擎对象设置模板解析器 templateEngine.setTemplateResolver(templateResolver); } protected void processTemplate(String templateName, HttpServletRequest req, HttpServletResponse resp) throws IOException { // 1.设置响应体内容类型和字符集 resp.setContentType("text/html;charset=UTF-8"); // 2.创建WebContext对象 WebContext webContext = new WebContext(req, resp, getServletContext()); // 3.处理模板数据 templateEngine.process(templateName, webContext, resp.getWriter()); } }
-
HelloWorld
-
创建index.html
-
在index.html编写超链接
<a href="/view/TestThymeleafServlet">初步测试Thymeleaf</a>
-
创建Servlet
<servlet> <servlet-name>TestThymeleafServlet</servlet-name> <servlet-class>com.atguigu.thymeleaf.servlet.TestThymeleafServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>TestThymeleafServlet</servlet-name> <url-pattern>/TestThymeleafServlet</url-pattern> </servlet-mapping>
-
修改Servlet继承的父类
-
在doGet()方法中跳转到Thymeleaf页面
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 1.声明当前请求要前往的视图名称 String viewName = "target"; // 2.调用ViewBaseServlet父类中的解析视图模板的方法 super.processTemplate(viewName, request, response); }
-
Thymeleaf页面(target.htm)
<!DOCTYPE html> <!-- 在html标签内加入Thymeleaf名称空间的声明 --> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <!-- 在p标签的基础上,使用Thymeleaf的表达式,解析了一个URL地址 --> <p th:text="@{'/aaa/bbb/ccc'}">Thymeleaf将在这里显示一个解析出来的URL地址</p> </body> </html>
-
8.3 基本语法:th名称空间
8.4 基本语法:表达式语法
8.4.1 修改标签文本值
代码示例:
<p th:text="标签体新值">标签体原始值</p>
- th:text作用
- 不经过服务器解析,直接用浏览器打开HTML文件,看到的是『标签体原始值』
- 经过服务器解析,Thymeleaf引擎根据th:text属性指定的『标签体新值』去替换『标签体原始值』
8.4.2 修改指定属性值
代码示例:
<input type="text" name="username" th:value="文本框新值" value="文本框旧值" />
语法:任何HTML标签原有的属性,前面加上『th:』就都可以通过Thymeleaf来设定新值。
8.4.3 解析URL地址
-
基本语法
代码示例:
<p th:text="@{/aaa/bbb/ccc}">标签体原始值</p>
经过解析后得到:
/view/aaa/bbb/ccc
所以@{}的作用是在字符串前附加『上下文路径』
这个语法的好处是:实际开发过程中,项目在不同环境部署时,Web应用的名字有可能发生变化。所以上下文路径不能写死。而通过@{}动态获取上下文路径后,不管怎么变都不怕啦!
-
使用URL地址解析
如果我们直接访问index.html本身,那么index.html是不需要通过Servlet,当然也不经过模板引擎,所以index.html上的Thymeleaf的任何表达式都不会被解析。
解决办法:通过Servlet访问index.html,这样就可以让模板引擎渲染页面了:
进一步的好处:
通过上面的例子我们看到,所有和业务功能相关的请求都能够确保它们通过Servlet来处理,这样就方便我们统一对这些请求进行特定规则的限定。
-
给URL地址后面附加请求参数
- 带参数的URL表达式:/fruit.do?id=1&&name=桃子
- Thymeleaf表达式:@{/fruit.do(id=1,name=‘桃子’)}
8.4.4 直接执行表达式
Servlet代码:
request.setAttribute("reqAttrName", "<span>hello-value</span>");
页面代码:
<p>有转义效果:[[${reqAttrName}]]</p>
<p>无转义效果:[(${reqAttrName})]</p>
执行效果:
<p>有转义效果:<span>hello-value</span></p>
<p>无转义效果:<span>hello-value</span></p>
8.5 基本语法:访问域对象
8.5.1 域对象
在Thymeleaf中,有三个域对象
- request:请求域
- session:会话域
- application:应用域
在JSP中,有四个域对象
- pageContext
- request:请求域
- session:会话域
- application:应用域
8.5.2 在Servlet中将数据存入属性域
-
操作请求域
Servlet中代码:
String requestAttrName = "helloRequestAttr"; String requestAttrValue = "helloRequestAttr-VALUE"; request.setAttribute(requestAttrName, requestAttrValue);
Thymeleaf表达式:
<p th:text="${helloRequestAttr}">request field value</p>
-
操作会话域
Servlet中代码:
// ①通过request对象获取session对象 HttpSession session = request.getSession(); // ②存入数据 session.setAttribute("helloSessionAttr", "helloSessionAttr-VALUE");
Thymeleaf表达式:
<p th:text="${session.helloSessionAttr}">这里显示会话域数据</p>
-
操作应用域
Servlet中代码:
// ①通过调用父类的方法获取ServletContext对象 ServletContext servletContext = getServletContext(); // ②存入数据 servletContext.setAttribute("helloAppAttr", "helloAppAttr-VALUE");
Thymeleaf表达式:
<p th:text="${application.helloAppAttr}">这里显示应用域数据</p>
8.6 基本语法:获取请求参数
8.6.1 一个名字一个值
页面代码:
<p th:text="${param.username}">这里替换为请求参数的值</p>
页面显示效果:
8.6.2 一个名字多个值
页面代码:
<p th:text="${param.team}">这里替换为请求参数的值</p>
页面显示效果:
8.7 基本语法:内置对象
所谓内置对象其实就是在表达式中可以直接使用的对象。
8.7.1 基本内置对象
用法举例:
<h3>表达式的基本内置对象</h3>
<p th:text="${#request.getClass().getName()}">这里显示#request对象的全类名</p>
<p th:text="${#request.getContextPath()}">调用#request对象的getContextPath()方法</p>
<p th:text="${#request.getAttribute('helloRequestAttr')}">调用#request对象的getAttribute()方法,读取属性域</p>
基本思路:
- 如果不清楚这个对象有哪些方法可以使用,那么就通过getClass().getName()获取全类名,再回到Java环境查看这个对象有哪些方法
- 内置对象的方法可以直接调用
- 调用方法时需要传参的也可以直接传入参数
8.7.2 公共内置对象
Servlet中将List集合数据存入请求域:
request.setAttribute("aNotEmptyList", Arrays.asList("aaa","bbb","ccc"));
request.setAttribute("anEmptyList", new ArrayList<>());
页面代码:
<p>#list对象isEmpty方法判断集合整体是否为空aNotEmptyList:<span th:text="${#lists.isEmpty(aNotEmptyList)}">测试#lists</span></p>
<p>#list对象isEmpty方法判断集合整体是否为空anEmptyList:<span th:text="${#lists.isEmpty(anEmptyList)}">测试#lists</span></p>
公共内置对象对应的源码位置:
8.8 基本语法:${}中的表达式本质是OGNL
OGNL:Object-Graph Navigation Language 对象-图导航语言
从根对象触发,通过特定的语法,逐层访问对象的各种属性。
OGNL语法
在Thymeleaf环境下,${}中的表达式可以从下列元素开始:
- 访问属性域的起点
- 请求域属性名
- session
- application
- param
- 内置对象
- #request
- #session
- #lists
- #strings
属性访问语法
- 访问对象属性:使用getXxx()、setXxx()方法定义的属性
- 对象.属性名
- 访问List集合或数组
- 集合或数组[下标]
- 访问Map集合
- Map集合.key
- Map集合[‘key’]
8.9 分之与迭代
8.9.1 分支
-
if和unless
让标记了th:if、th:unless的标签根据条件决定是否显示。
示例的实体类:
public class Employee { private Integer empId; private String empName; private Double empSalary; ... }
示例的Servlet代码:
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 1.创建ArrayList对象并填充 List<Employee> employeeList = new ArrayList<>(); employeeList.add(new Employee(1, "tom", 500.00)); employeeList.add(new Employee(2, "jerry", 600.00)); employeeList.add(new Employee(3, "harry", 700.00)); // 2.将集合数据存入请求域 request.setAttribute("employeeList", employeeList); // 3.调用父类方法渲染视图 super.processTemplate("list", request, response); }
示例的HTML代码:
<table> <tr> <th>员工编号</th> <th>员工姓名</th> <th>员工工资</th> </tr> <tr th:if="${#lists.isEmpty(employeeList)}"> <td colspan="3">抱歉!没有查询到你搜索的数据!</td> </tr> <tr th:if="${not #lists.isEmpty(employeeList)}"> <td colspan="3">有数据!</td> </tr> <tr th:unless="${#lists.isEmpty(employeeList)}"> <td colspan="3">有数据!</td> </tr> </table>
if配合not关键词和unless配合原表达式效果是一样的,看自己的喜好。
-
switch
<h3>测试switch</h3> <div th:switch="${user.memberLevel}"> <p th:case="level-1">银牌会员</p> <p th:case="level-2">金牌会员</p> <p th:case="level-3">白金会员</p> <p th:case="level-4">钻石会员</p> </div>
8.9.2 迭代
<h3>测试each</h3>
<table>
<thead>
<tr>
<th>员工编号</th>
<th>员工姓名</th>
<th>员工工资</th>
</tr>
</thead>
<tbody th:if="${#lists.isEmpty(employeeList)}">
<tr>
<td colspan="3">抱歉!没有查询到你搜索的数据!</td>
</tr>
</tbody>
<tbody th:if="${not #lists.isEmpty(employeeList)}">
<!-- 遍历出来的每一个元素的名字 : ${要遍历的集合} -->
<tr th:each="employee : ${employeeList}">
<td th:text="${employee.empId}">empId</td>
<td th:text="${employee.empName}">empName</td>
<td th:text="${employee.empSalary}">empSalary</td>
</tr>
</tbody>
</table>
在迭代过程中,可以参考下面的说明使用迭代属性:
8.10 基本语法:包含其他模板文件
应用场景:抽取各个页面的公共部分。
代码实例:
使用th:fragment来给这个片段命名:
<div th:fragment="header">
<p>被抽取出来的头部内容</p>
</div>
包含到有需要的页面
语法 | 效果 |
---|---|
th:insert | 把目标的代码片段整个插入到当前标签内部 |
th:replace | 用目标的代码替换当前标签 |
th:include | 把目标的代码片段去除最外层标签,然后再插入到当前标签内部 |
页面代码:
<!-- 代码片段所在页面的逻辑视图 :: 代码片段的名称 -->
<div id="badBoy" th:insert="segment :: header">
div标签的原始内容
</div>
<div id="worseBoy" th:replace="segment :: header">
div标签的原始内容
</div>
<div id="worstBoy" th:include="segment :: header">
div标签的原始内容
</div>