模板引擎,终于不用在后端拼接前端代码了
如果想要在Servlet项目中直接返回html
页面,最原始的方式就是返回字符串,然后设置响应报头Content-Type: text/html
让浏览器进行html渲染。但是在后端通过字符串的方式写前端代码效率不仅低,而且容易出错。因此就需要使用模板引擎。
模板引擎的作用就是在前端代码中添加一些占位符,然后让后端的模板引擎去填写这些占位符。这样就可以不同在后端手动拼接前端代码的字符串了。
这里模板解析器使用的是Thymeleaf
,因为这个是Spring
中推荐使用的。可以在maven
仓库中找到。
常用的Thymeleaf模板语法
th:text
:在标签中展示该表达式中的结果th:[HTML 属性]
:设置标签的属性th:if
:当表达式结果为true
,则显示内容。否则不显示内容。(注意:是显示/不显示,不是渲染/不渲染。渲染/不渲染是对于dom节点来说的,而显示/不显示只是节点中内容是否显示,而节点还是会生成的)th:each
:循环渲染元素
Thymeleaf
中的模板语法有很多,这里只罗列几个最常见的,其他的语法可以现用现查。
案例:使用th:text模板语法将url中的参数动态渲染到界面中
- Servlet代码
@WebServlet("/helloThymeleaf")
public class HelloThymeleafServlet extends HttpServlet {
// 1.创建模板引擎,用于渲染网页
private TemplateEngine engine = new TemplateEngine();
@Override
public void init() throws ServletException {
// 2.创建模板解析器,用于找到模板文件
// 每一个webapp中都会创建一个ServerContext对象,多个Servlet共享一个Context对象
// 通过Context对象创建一个webapp中唯一的一个模板解析器,模板解析器就是用来找到模板文件的
ServletContextTemplateResolver resolver = new ServletContextTemplateResolver(this.getServletContext());
// 通过设定模板文件的前缀和后缀,判定模板是哪些文件
resolver.setPrefix("/WEB-INF/template/");
resolver.setSuffix(".html");
resolver.setCharacterEncoding("utf-8");
// 3.将模板引擎和模板解析器关联在一起,即模板引擎需要渲染出模板
engine.setTemplateResolver(resolver);
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获得url中的参数
String message = req.getParameter("message");
// 4.使用webContext将模板中的参数进行替换,其实就相当于是一个哈希表
WebContext webContext = new WebContext(req, resp, this.getServletContext());
webContext.setVariable("message", message);
// 5.模板引擎渲染模板
engine.process("hello", webContext, resp.getWriter());
// 上面这句话也可以拆开来写
// String html = engine.process("hello", webContext);
// resp.getWriter().write(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>Thymeleaf</title>
</head>
<body>
<!-- 通过模板引擎进行传递参数message,然后动态渲染 -->
<h3 th:text="${message}"></h3>
</body>
</html>
注意:模板文件一定要放在webapp
下的WEB-INF
文件夹下的template
(自己创建)文件夹下。
总结模板引擎套路
- 创建
TemplateEngine
模板引擎,之后使用该对象的process()
渲染模板 - 创建
ServletContextTemplateResolver
模板解析器,用于找到模板文件文职,并加载模板文件, - 创建
WebContext
对象,存储前端变量和Java变量之间的映射关系,之后可以配合模板解析器动态渲染模板。
此外,需要注意模板文件的目录位置和模板文件中Thymeleaf
的写法。如果目录位置或者模板语法出错,服务器会就返回500错误。
使用th:if,th:text模板语法完成一个Web版的猜数字界面
- Servlet代码
@WebServlet("/guessNum")
public class GuessNumServlet extends HttpServlet {
private int guessNum = 0; // 随机数字
private int count = 0; // 累计猜数字次数
// 创建模板引擎
private TemplateEngine engine = new TemplateEngine();
// 在init()中创建模板解析器
@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");
Random random = new Random();
guessNum = random.nextInt(100) + 1; // [1, 100]
// 建立映射关系
WebContext webContext = new WebContext(req, resp, this.getServletContext());
webContext.setVariable("guessNum", true);
// 渲染模板
engine.process("guessNum", webContext, resp.getWriter());
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 比较数字大小
resp.setContentType("text/html; charset=utf-8");
String parameter = req.getParameter("num");
int num = Integer.parseInt(parameter);
String result = "";
if (num > guessNum) {
result = "猜大了";
} else if (num < guessNum) {
result = "猜小了";
} else {
result = "猜对了";
}
count ++;
// 建立映射关系
WebContext webContext = new WebContext(req, resp, this.getServletContext());
webContext.setVariable("newGame", false);
webContext.setVariable("result", result);
webContext.setVariable("count", count);
// 渲染模板
engine.process("guessNum", webContext, resp.getWriter());
}
}
- 前端代码
<!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="guessNum" method="POST">
<input type="text" name="num">
<br> <input type="submit" value="提交">
</form>
<div th:if="${!newGame}">
<div th:text="${result}"></div>
<div th:text="${count}"></div>
</div></body>
</html>
模板引擎存在的最大意义就是让Java代码和前端代码分离开来了。这样使得代码的阅读星提高了,出错率降低了。
案例:使用th:each模板语法在前端渲染出多个人的姓名和手机号
- 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(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<Person>();
persons.add(new Person("张三", "123"));
persons.add(new Person("李四", "456"));
persons.add(new Person("王五", "789"));
WebContext webContext = new WebContext(req, resp, getServletContext());
webContext.setVariable("persons", persons);
// 如果使用engine.process("thymeleafEach", webContext, resp.getWriter());的话,
// 异常会被“消化”,因此如果模板语法出错或者Servlet代码出错服务器是不会响应异常的
// engine.process("thymeleafEach", webContext, resp.getWriter());
// 如果先使用engine.process("thymeleafEach", webContext);然后通过resp.getWriter().write()渲染的话
// 那么如果代码出错,服务器就会显示异常。
// 因此推荐当服务器出错的话,使用这种写法比较好
String html = engine.process("thymeleafEach", webContext);
resp.getWriter().write(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>Document</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>
- 实验结果
编写Thymeleaf如果出错,经常出现问题的地方
一般Thymeleaf
出现问题,都是模板语法出现问题,或者是**前后端的变量没有关联(WebContext出现问题)**上。
如果使用engine.process("模板", wecContext, resp.getWriter())
,这个方法会自动处理抛出来的异常,所以如果模板出现问题也不会抛出异常。
如果先通过engine.process("模板", webContext)
将html
页面的字符串获得,然后使用resp.getWriter().write()
方法的话,那么模板出现问题,服务器就会出现异常,并返回500
页面,通过500
页面中的错误提示,就可以很快定位到问题所在。