详解模板引擎一
详解模板引擎一
- 引言
- 一、猜数字游戏案例
- 1. 约定好前后端交互方式
- 第一个交互接口
- 第二个交互接口
- 2. 如何实现
- 3. 代码实现
- 4. 展示结果
- (1) 抓包结果
- (2) 浏览器显示结果
- 5. 分析问题
- 二、应用模板引擎的第一个案例
- 1. html 模板文件
- 2. Servlet 代码
- 3. 展示结果
- (1) 抓包结果
- (2) 浏览器展示结果
- (3) 前后对比
- 三、理解应用模板引擎的代码
- html 模板文件
- Servlet 代码
- 1. TemplateEngine 类
- 2. ServletContextTemplateResolver 类
- 3. WebContext 类
- 四、改进猜数字游戏
- html 模板文件
- Servlet 代码
- 抓包结果
- 五、常用的 Thymeleaf 模板语法
- 六、th:each 应用案例
- html 模板文件
- Servlet 代码
- 展示结果
- 浏览器展示结果
- 抓包结果
- 分析代码
- 七、关于 Thymeleaf
- 准备工作
- Thymeleaf 的使用流程
- 总结
引言
在说模板引擎之前,我们先来看看一个猜数字游戏的案例。
一、猜数字游戏案例
1. 约定好前后端交互方式
第一个交互接口
GET 请求:打开 / 刷新页面的时候,可以看到猜数字的基本页面。( 输入框 + 按钮 )
。路径约定为:【 /guess 】
GET 响应:服务器初始化,并在后台生成一个随机数字,响应的内容是呈现出一个 html 页面。( 输入框 + 按钮 )
第二个交互接口
POST 请求:每猜一次数字,就发送一个 POST 请求,同样地,可以看到基本页面。
( 输入框 + 按钮 + 结果 + 次数)。路径约定为:【 /guess 】
POST 响应:服务器处理每一次猜数字的逻辑(猜大了 / 猜小了),响应的内容是呈现出一个 html 页面。( 输入框 + 按钮 + 结果 + 次数)
2. 如何实现
不需要写 ajax 代码,只需要在服务器端实现 Servlet 响应代码即可,之后,在浏览器上输入路径即可显示响应。
(1) 写一个 doGet 方法,处理第一个交互接口。
(2) 写一个 doPost 方法,处理第二个交互接口。
3. 代码实现
@WebServlet("/guess")
public class GuessNumberServlet extends HttpServlet {
private int toGuess = 0;
private int count = 0;
/**
* 刷新浏览器,获取页面的初始情况,并初始化,生成一个待猜的数字
*/
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html; charset = UTF-8");
// 1. 先生成一个待猜的数字
Random random = new Random();
// 生成的数字范围 [0, 100)
toGuess = random.nextInt(100);
count = 0;
// 2. 返回一个页面
StringBuilder html = new StringBuilder();
// 约定用 POST 进行提交,这里不能弄错了
html.append("<form action=\"guess\" method=\"POST\"> ");
html.append("<input type=\"text\" name=\"num\"> ");
html.append("<input type=\"submit\" value=\"提交\"> ");
html.append("</form>");
resp.getWriter().write(html.toString());
}
/**
* 处理每一次猜的过程,每猜一次数字,就相当于要发送 HTTP 请求一次,所以就像响应一次
*/
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html; charset = UTF-8");
// 1. 从请求中,读取用户提交的数字内容
String parameter = req.getParameter("num");
int num = Integer.parseInt(parameter);
String result = "";
// 2. 和 toGuess 进行比较
if (num < toGuess) {
result = "猜小了";
} else if (num > toGuess) {
result = "猜大了";
} else {
result = "猜对了";
}
// 3. 自增猜的次数
count++;
// 4. 构造一个结果页面,能够显示当前猜的结果
StringBuilder html = new StringBuilder();
html.append("<form action=\"guess\" method=\"POST\"> ");
html.append("<input type=\"text\" name=\"num\"> ");
html.append("<input type=\"submit\" value=\"提交\"> ");
html.append("</form>");
// 新增一些显示内容
html.append("<div>" + result + "</div>");
html.append("<div> 当前猜的次数: " + count + "次" + "</div>");
resp.getWriter().write(html.toString());
}
}
4. 展示结果
(1) 抓包结果
(2) 浏览器显示结果
5. 分析问题
在上面的代码中,可以看到,如果我们需要将响应以 html 页面的形式展示在浏览器 ( 客户端 ) 上,那么,就需要在 Servlet 代码中写上 html 格式的标签。然而,我们只能以字符串拼接的方式来达到 html 这样的页面效果。也就是说,我们没法做到真正的前后端分离。
基于以上的原因,模板引擎就能够有效解决这一问题,所以,模板引擎存在的意义也正是:能够将前后端代码做到前后分离。
二、应用模板引擎的第一个案例
1. html 模板文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>模板引擎</title>
</head>
<body>
<!-- 我们期望 h3 中的内容经 Servlet 代码中操作之后,能够将 ${message} 替换 -->
<h3 th:text="${message}"></h3>
</body>
</html>
2. Servlet 代码
@WebServlet("/helloThymeleaf")
public class HelloThymeleafServlet extends HttpServlet {
// 创建一个模板引擎对象
private TemplateEngine engine = new TemplateEngine();
@Override
public void init() throws ServletException {
// 完成 Thymeleaf 的初始化操作
// 初始化操作其实就只做了一件事,告诉模板引擎,它从哪些目录中加载什么样的文件,作为 HTML 模板
// 创建一个 模板解析器 对象
ServletContextTemplateResolver resolver = new ServletContextTemplateResolver(this.getServletContext());
// 让模板解析器,来加载模板文件
// 这里需要设置模板文件的前缀后缀,就是告诉模板引擎,需要加载哪些文件到内存中,以备后用
resolver.setPrefix("/WEB-INF/template/");
resolver.setSuffix(".html");
resolver.setCharacterEncoding("UTF-8");
// 把解析器对象,给设置到 engine 对象中
engine.setTemplateResolver(resolver);
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html; charset = UTF-8");
// 0. 在执行模板渲染之前,要先进行初始化
// 模板渲染:把刚才写的模板 html 代码中的 message 变量进行替换
// 初始化操作需要执行一次即可,放到 init 方法中实现
// 1. 先从参数中读取用户传过来的 message 值,
// 这里是 GET 请求,所以我们从 query string 中读取
String message = req.getParameter("message");
// 2. 把当前从请求中读取出来的 message 的值和模板中的 ${message} 关联起来
WebContext webContext = new WebContext(req, resp, this.getServletContext());
// 这相当于是,以一种键值对的方式,进行替换,将 value 替换成 key ( key : value )
webContext.setVariable("message", message);
// 3. 进行最终的渲染 (完成最终替换模板的过程)
// "demo" 即表示当前模板文件,表示 《demo.html》 去掉后缀的写法
String html = engine.process("demo", webContext);
System.out.println(html);
System.out.println();
resp.getWriter().write(html);
}
}
3. 展示结果
(1) 抓包结果
(2) 浏览器展示结果
(3) 前后对比
三、理解应用模板引擎的代码
html 模板文件
<h3 th:text="${message}"></h3>
【 th 】是 Thymeleaf 的缩写,即表示这个属性是由 Thymeleaf 提供的
【 text 】表示这里的类型是字符串,也可以按字面理解为字符串的意思
【 “${message}” 】表示一个具体的变量,其需要在 Java 代码中,将变量值进行替换
Servlet 代码
1. TemplateEngine 类
// 创建一个模板引擎对象
private TemplateEngine engine = new TemplateEngine();
TemplateEngine 是 Thymeleaf 中,最核心的类,功能就是渲染模板。
String html = engine.process("demo", webContext);
最后的一步,通过 process 方法,直接完成模板渲染。
2. ServletContextTemplateResolver 类
ServletContextTemplateResolver resolver = new ServletContextTemplateResolver(this.getServletContext());
resolver.setPrefix("/WEB-INF/template/");
resolver.setSuffix(".html");
resolver.setCharacterEncoding("UTF-8");
engine.setTemplateResolver(resolver);
虽然 ServletContextTemplateResolver 类,看起来很长,但它其实很好理解,它存在的意义实际上就是让模板解析器来加载模板文件。怎么告诉它呢?就是让它知道模板文件在哪个路径,包含哪些格式。前缀、后缀这是固定写法。
3. WebContext 类
WebContext webContext = new WebContext(req, resp, this.getServletContext());
webContext.setVariable("message", message);
WebContext 类,我们可以将其理解为一个键值对结构,它存在的主要意义:以一种键值对的方式,进行替换,将 value 替换成 key ( key : value )。
左边的 “message” 相当于 key,右边的 message 相当于 value.
左边的 “message” 就是 【 “${message}” 】,右边的 message 就是我们 Java 代码中的一个变量,我们可以为其赋值。
四、改进猜数字游戏
之前,我们实现猜数字游戏的时候,没有做到前后端代码分离,现在,我们尝试使用模板引擎试一下效果。
html 模板文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>猜数字</title>
</head>
<body>
<form action="guess2" method="POST">
<input type="text" name="num">
<input type="submit" value="猜数字">
</form>
<!-- th:if 是一个条件显示的逻辑,if 后面的表达式为真就显示,反之,就不显示 -->
<div th:if="${!newGame}">
<!-- 猜完游戏的时候,才会显示下面两行代码,第一次加载页面的时候,不显示 -->
<div th:text="${result}"></div>
<div th:text="${count}"></div>
</div>
</body>
</html>
Servlet 代码
@WebServlet("/guess2")
public class GuessNumberServlet2 extends HttpServlet {
private TemplateEngine engine = new TemplateEngine();
private int toGuess = 0;
private int count = 0;
/**
* 对模板引擎进行初始化操作
*/
@Override
public void init() throws ServletException {
ServletContextTemplateResolver resolver = new ServletContextTemplateResolver(this.getServletContext());
resolver.setPrefix("WEB-INF/template/");
resolver.setSuffix(".html");
resolver.setCharacterEncoding("UTF-8");
engine.setTemplateResolver(resolver);
}
/**
* 刷新浏览器,获取页面的初始情况,并初始化,生成一个待猜的数字
*/
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html; charset = UTF-8");
// 1. 先生成一个待猜的数字
Random random = new Random();
// 生成的数字范围 [0, 100)
toGuess = random.nextInt(100);
count = 0;
// 2. 返回一个页面
// 这里是开启一局新的游戏
WebContext webContext = new WebContext(req, resp, getServletContext());
webContext.setVariable("newGame", true);
engine.process("guessNum", webContext, resp.getWriter());
}
/**
* 处理每一次猜的过程,每猜一次数字,就相当于要发送 HTTP 请求一次,所以就像响应一次
*/
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html; charset = UTF-8");
// 1. 从请求中,读取用户提交的数字内容
String parameter = req.getParameter("num");
int num = Integer.parseInt(parameter);
String result = "";
// 2. 和 toGuess 进行比较
if (num < toGuess) {
result = "猜小了";
} else if (num > toGuess) {
result = "猜大了";
} else {
result = "猜对了";
}
// 3. 自增猜的次数
count++;
// 4. 构造一个结果页面,能够显示当前猜的结果
WebContext webContext = new WebContext(req, resp, getServletContext());
webContext.setVariable("newGame", false);
webContext.setVariable("result", result);
webContext.setVariable("count", count);
engine.process("guessNum", webContext, resp.getWriter());
}
}
抓包结果
五、常用的 Thymeleaf 模板语法
命令 | 功能 |
---|---|
th:text | 在标签体中展示表达式求值结果的文本内容 |
th:[HTML标签属性] | 设置任意的 HTML 标签属性的值 |
th:if | 当表达式的结果为真时则显示内容,否则不显示 |
th:each | 循环访问元素 |
六、th:each 应用案例
html 模板文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>电话本</title>
</head>
<body>
<ul>
<li th:each="person : ${persons}">
<span th:text="${person.name}"></span>
<span th:text="${person.phone}"></span>
</li>
</ul>
</body>
</html>
Servlet 代码
class Person {
public String name;
public String phone;
public Person(String name, String phone) {
this.name = name;
this.phone = phone;
}
}
@WebServlet("/each")
public class EachServlet extends HttpServlet {
private TemplateEngine engine = new TemplateEngine();
@Override
public void init() throws ServletException {
ServletContextTemplateResolver resolver = new ServletContextTemplateResolver(this.getServletContext());
resolver.setPrefix("WEB-INF/template/");
resolver.setSuffix(".html");
resolver.setCharacterEncoding("UTF-8");
engine.setTemplateResolver(resolver);
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html; charset = UTF-8");
List<Person> persons = new ArrayList<>();
persons.add(new Person("Jack", "123"));
persons.add(new Person("Rose", "456"));
persons.add(new Person("Ron", "789"));
persons.add(new Person("Bruce", "321"));
persons.add(new Person("Lisa", "654"));
WebContext webContext = new WebContext(req, resp, this.getServletContext());
webContext.setVariable("persons", persons);
engine.process("each", webContext, resp.getWriter());
}
}
展示结果
浏览器展示结果
抓包结果
HTTP 响应的正文 body,是经过模板渲染后的结果:
分析代码
这个代码的意思很简单,我们创建一个 Person 类,之后通过其构造函数,new 一些对象,并放入顺序表中,最后,以 html 的格式,展现在浏览器上。
当我们没有通过网络编程,只是经过 Java 单机代码写一个程序的时候,我们可以通过 for each 这样的增强循环,来遍历整个顺序表。
然而,当我们使用网络编程的时候,需要考虑到前后端交互,那么,服务器端的代码,不仅要考虑逻辑,也要考虑到客户端如何接收到一个明显的响应。
七、关于 Thymeleaf
需要明确:Java 中的模板引擎有很多种,Thymeleaf 只是其中一个模板引擎,而由于这是 Spring 官方推荐的,所以我们先使用 Thymeleaf,对之后学习 Spring 框架,也能够做到很好的连接了。
使用模板 Thymeleaf 这样的模板引擎,主要就是模板渲染这一步骤,它就像是为图片上色一样,这就很像美术课上,我们先得将一个物体的轮廓勾勒出来,再为其添上颜色。
如果没上过美术课的小伙伴,也没有关系,答题卡大家一定都用过,考试时,老师给大家发的空白答题卡就相当于一个模板文件,当我们涂空白答题卡的时候,就是在渲染模板。
准备工作
1. 先从 maven 仓库,引入依赖
2. 创建好工作目录
webapp / WEB-INF / template / 【模板文件…】
Thymeleaf 的使用流程
一、编写 html 模板文件,放到指定目录中
二、编写Servlet 代码
- 初始化一个 TemplateEngine 实例
- 创建一个 ServletContextTemplateResolver 实例,此外,指定需要加载的模板文件的路径、格式、字符集
- 创建一个 WebContext 实例,把模板中的变量和 Java 中的变量关联起来,也就是把被替换的变量和新变量关联起来。
- 使用 TemplateEngine 类 提供的 process 方法完成最终的模板渲染
总结
相对于 Java 其他的模板引擎,Thymeleaf 较为复杂。然而,我们还是得熟知它的用法。虽然刚开始学的时候,很多类不熟悉,但是多敲几遍代码,就可以很顺利地做出来。因为 Thymeleaf 和 JDBC 编程 一样,是一个固定的流程。
虽然,在上面的几个案例中,未将 html 分离的写法 和 使用模板引擎的写法,这两者看起来代码长度差不了多少,但如果是上百行 html 代码呢?难道我们都需要使用转义字符拼接吗?所以,模板引擎存在的最大意义,就是让 【 后端的 Java 代码 】 和 【 前端的 html, css, JS 代码 】做出分离。
此外,说来说去,其实并不是使用 Thymeleaf 相应的 API 麻烦。而是,当我们进行 Web 开发的时候,这本身就是一件麻烦的事情。
因为,当我们写一个 Java 的 “单机代码” 的时候,只需要程序员自己看着,就明白了逻辑。但是 Web 开发,就需要涉及到客户端与服务器之间的交互,最终的结果,我们需要将数据转化成一个较为简单的主观内容,展现给那些不懂代码的普通用户。那么,如何进行转化,如何进行处理请求并做出什么样的响应,这些问题,本身就是一个较为麻烦的事情。
这就好像:如果我们一个人在家里吃饭的时候,可以大口大口、毫无顾虑地吃饭,但在公共场合的时候,需要考虑到别人的感受,所以我们自己就要注意形象了。