• 详解模板引擎一


    引言

    在说模板引擎之前,我们先来看看一个猜数字游戏的案例。

    一、猜数字游戏案例

    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("
    "); html.append(" "); html.append(" "); html.append(""
    ); 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("
    "); html.append(" "); html.append(" "); html.append(""
    ); // 新增一些显示内容 html.append("
    " + result + "
    "
    ); html.append("
    当前猜的次数: " + count + "次" + "
    "
    ); resp.getWriter().write(html.toString()); } }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68

    4. 展示结果

    (1) 抓包结果

    1

    2

    (2) 浏览器显示结果

    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 th:text="${message}">h3>
    body>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    3. 展示结果

    (1) 抓包结果

    1

    (2) 浏览器展示结果

    2

    (3) 前后对比

    3

    三、理解应用模板引擎的代码

    html 模板文件

    <h3 th:text="${message}">h3>
    
    • 1

    【 th 】是 Thymeleaf 的缩写,即表示这个属性是由 Thymeleaf 提供的
    【 text 】表示这里的类型是字符串,也可以按字面理解为字符串的意思
    【 “${message}” 】表示一个具体的变量,其需要在 Java 代码中,将变量值进行替换

    Servlet 代码

    1. TemplateEngine 类

    // 创建一个模板引擎对象
    private TemplateEngine engine = new TemplateEngine();
    
    • 1
    • 2

    TemplateEngine 是 Thymeleaf 中,最核心的类,功能就是渲染模板。

    String html = engine.process("demo", webContext);
    
    • 1

    最后的一步,通过 process 方法,直接完成模板渲染。

    5

    2. ServletContextTemplateResolver 类

    ServletContextTemplateResolver resolver = new ServletContextTemplateResolver(this.getServletContext());
    
    resolver.setPrefix("/WEB-INF/template/");
    resolver.setSuffix(".html");
    resolver.setCharacterEncoding("UTF-8");
    
    engine.setTemplateResolver(resolver);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    虽然 ServletContextTemplateResolver 类,看起来很长,但它其实很好理解,它存在的意义实际上就是让模板解析器来加载模板文件。怎么告诉它呢?就是让它知道模板文件在哪个路径,包含哪些格式。前缀、后缀这是固定写法。

    3. WebContext 类

    WebContext webContext = new WebContext(req, resp, this.getServletContext());
    webContext.setVariable("message", message);
    
    • 1
    • 2

    WebContext 类,我们可以将其理解为一个键值对结构,它存在的主要意义:以一种键值对的方式,进行替换,将 value 替换成 key ( key : value )。

    左边的 “message” 相当于 key,右边的 message 相当于 value.
    左边的 “message” 就是 【 “${message}” 】,右边的 message 就是我们 Java 代码中的一个变量,我们可以为其赋值。

    2

    四、改进猜数字游戏

    之前,我们实现猜数字游戏的时候,没有做到前后端代码分离,现在,我们尝试使用模板引擎试一下效果。

    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>
    
        
        <div th:if="${!newGame}">
            
            <div th:text="${result}">div>
            <div th:text="${count}">div>
        div>
    body>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    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());
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72

    抓包结果

    0

    1

    五、常用的 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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    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());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    展示结果

    浏览器展示结果

    1

    抓包结果

    HTTP 响应的正文 body,是经过模板渲染后的结果:

    2

    分析代码

    这个代码的意思很简单,我们创建一个 Person 类,之后通过其构造函数,new 一些对象,并放入顺序表中,最后,以 html 的格式,展现在浏览器上。

    1

    当我们没有通过网络编程,只是经过 Java 单机代码写一个程序的时候,我们可以通过 for each 这样的增强循环,来遍历整个顺序表。

    然而,当我们使用网络编程的时候,需要考虑到前后端交互,那么,服务器端的代码,不仅要考虑逻辑,也要考虑到客户端如何接收到一个明显的响应。

    七、关于 Thymeleaf

    需要明确:Java 中的模板引擎有很多种,Thymeleaf 只是其中一个模板引擎,而由于这是 Spring 官方推荐的,所以我们先使用 Thymeleaf,对之后学习 Spring 框架,也能够做到很好的连接了。

    使用模板 Thymeleaf 这样的模板引擎,主要就是模板渲染这一步骤,它就像是为图片上色一样,这就很像美术课上,我们先得将一个物体的轮廓勾勒出来,再为其添上颜色。

    如果没上过美术课的小伙伴,也没有关系,答题卡大家一定都用过,考试时,老师给大家发的空白答题卡就相当于一个模板文件,当我们涂空白答题卡的时候,就是在渲染模板。

    准备工作

    1. 先从 maven 仓库,引入依赖

    1

    2. 创建好工作目录

    webapp / WEB-INF / template / 【模板文件…】

    2

    Thymeleaf 的使用流程

    一、编写 html 模板文件,放到指定目录中

    二、编写Servlet 代码

    1. 初始化一个 TemplateEngine 实例
    2. 创建一个 ServletContextTemplateResolver 实例,此外,指定需要加载的模板文件的路径、格式、字符集
    3. 创建一个 WebContext 实例,把模板中的变量和 Java 中的变量关联起来,也就是把被替换的变量和新变量关联起来。
    4. 使用 TemplateEngine 类 提供的 process 方法完成最终的模板渲染

    总结

    相对于 Java 其他的模板引擎,Thymeleaf 较为复杂。然而,我们还是得熟知它的用法。虽然刚开始学的时候,很多类不熟悉,但是多敲几遍代码,就可以很顺利地做出来。因为 Thymeleaf 和 JDBC 编程 一样,是一个固定的流程。

    虽然,在上面的几个案例中,未将 html 分离的写法 和 使用模板引擎的写法,这两者看起来代码长度差不了多少,但如果是上百行 html 代码呢?难道我们都需要使用转义字符拼接吗?所以,模板引擎存在的最大意义,就是让 【 后端的 Java 代码 】 和 【 前端的 html, css, JS 代码 】做出分离。

    此外,说来说去,其实并不是使用 Thymeleaf 相应的 API 麻烦。而是,当我们进行 Web 开发的时候,这本身就是一件麻烦的事情。

    因为,当我们写一个 Java 的 “单机代码” 的时候,只需要程序员自己看着,就明白了逻辑。但是 Web 开发,就需要涉及到客户端与服务器之间的交互,最终的结果,我们需要将数据转化成一个较为简单的主观内容,展现给那些不懂代码的普通用户。那么,如何进行转化,如何进行处理请求并做出什么样的响应,这些问题,本身就是一个较为麻烦的事情。

    这就好像:如果我们一个人在家里吃饭的时候,可以大口大口、毫无顾虑地吃饭,但在公共场合的时候,需要考虑到别人的感受,所以我们自己就要注意形象了。

  • 相关阅读:
    【无标题】
    6.1、Flink数据写入到文件
    java计算机毕业设计在线考试系统源程序+mysql+系统+lw文档+远程调试
    2023最新团购社群小程序源码 社区商城小程序源码开源版 带完整后台+部署教程
    如何正确维护实验室超声波清洗器?
    高可用系统架构——关于语雀宕机的思考
    Field ‘id‘ doesn‘t have a default value错误解决办法
    idea如何设置代理实现管理突破呢
    Find My手机保护壳|苹果Find My与手机保护壳结合,智能防丢,全球定位
    GalaxyBase分布式集群关闭后启动
  • 原文地址:https://blog.csdn.net/lfm1010123/article/details/126532531