当前位置: 首页 > news >正文

详解模板引擎二

详解模板引擎二

  • 引入问题
  • 一、什么是 ServletContext
    • 1. 理解 ServletContext
    • 2. 提出问题
    • 3. ServletContext 对象的重要方法
  • 二、代码示例:多个 Servlet 共享数据
    • 1. WriterServlet 类
    • 2. ReaderServlet 类
    • 展示结果
      • 展示结果1
      • 展示结果2
      • 展示结果3
    • 分析代码
  • 三、提出问题
  • 四、什么是监听器
  • 五、代码示例:创建一个监听器
    • 展示结果
  • 六、结合 ServletContext 和 Listener
    • 定好思路
    • 1. ThymeleafConfig 类 (关键类)
    • 2. Servlet 代码
    • 3. html 模板文件
    • 展示结果
  • 总结 ServletContext 和 Listener
  • Thymeleaf 新的总结流程

引入问题

在 Thymeleaf 的使用流程中,每一次我们需要进行模板渲染的时候,就需要初始化一个 TemplateEngine 实例,同时也需要创建一个 ServletContextTemplateResolver 实例…

然而,在实际开发中,可能一个 Servlet 类就对应着一个 HTTP 响应,那么,难道每次创建一个类,就需要实例化初始化一个 TemplateEngine 实例吗?

答:其实并不需要。因为一个完整的项目中,只需要创建一个 TemplateEngine 实例即可,即只用初始化一次即可。而为了完成这样的目的,就需要使用 Servlet 中的 ServletContext 和 “监听器”。

一、什么是 ServletContext

ServletContext 是一个 Servlet 程序中全局的储存信息的空间,服务器开始就存在,服务器关闭就销毁。

  • Tomcat 在启动时,它会为每个 webapp 都创建一个对应的 ServletContext
  • 一个 WEB 应用中的所有的 Servlet 共享同一个 ServletContext 对象
  • 可以通过 HttpServlet.getServletContext() 或
    HttpServletRequest.getServletContext() 获取到当前 webapp 的 ServletContext 对象.
  • Context 英文原义为 “环境” / “上下文”。此处的 ServletContext 对象就相当于一个 webapp 的 “上下文”。这就和语文一样,结合上下文,就能够分析出语境。

1. 理解 ServletContext

3

2. 提出问题

那么,既然 TemplateEngine 这个引擎对象,只需要一个实例, 要不要直接创建单例模式呢?

答:这里不能创建成单例模式,**所谓单例模式,指的是整个进程里面,只有一个实例。**那么,整个 Tomcat 服务器 实际上就是一个进程。
所以,此处的 TemplateEngine 并不是进程级别的单例,而是 webapp 级别的单例。

3. ServletContext 对象的重要方法

方法描述
void setAttribute(String name, Object obj)设置属性(键值对)
Object getAttribute(String name)根据属性名获取属性值, 如果 name 不存在, 返回 null
void removeAttribute(String name)删除对应的属性

二、代码示例:多个 Servlet 共享数据

1. WriterServlet 类

// 负责往 ServletContext 对象中写数据
// 浏览器通过一个形如 /writer?message=abc 访问 WriterServlet,
// 再 message=abc 这个键值对存到 ServletContext 对象中
@WebServlet("/writer")
public class WriterServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html; charset=UTF-8");

        // 1. 先从请求中获取到 message 参数
        String message = req.getParameter("message");
        // 2. 取出 ServletContext 对象 ( 这个对象是 Tomcat 在加载 webapp 的时候自动创建的)
        ServletContext context = this.getServletContext();
        // 3. 往对象写入键值对
        context.setAttribute("message", message);
        // 4. 返回响应
        resp.getWriter().write("<h3> 存储 message 成功 </h3>");
    }
}

2. ReaderServlet 类

// 使用这个 Servlet 从 ServletContext 对象中读取数据
// 就把刚才的 WriterServlet 存储的数据取出来
@WebServlet("/reader")
public class ReaderServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html; charset=UTF-8");

        // 1. 获取到一个 ServletContext 对象
        ServletContext context = this.getServletContext();
        // 2. 从 Context 中获取到刚刚存储的值
        String message = (String)context.getAttribute("message");
        // 3. 把取的数据显示在页面上
        resp.getWriter().write("message: " + message);
    }
}

展示结果

展示结果1

如果我们直接从 reader 路径,发送 HTTP请求,可以看到,message 对应字符串的值为空,这很好理解,我们并没有先存值,取出来的当然为空了。

1

展示结果2

如果我们先往 ServletContext 对象中存入值【 hello world 】,之后,取 message 值的时候,就可以拿到【 hello world 】了。很显然,这也是一种键值对的结构。

2

展示结果3

如果我们重启服务器,如果直接从 reader 路径,发送 HTTP 请求的时候,那么对应的 message 依然为空。这是为什么呢?

3

因为,ServletContext 是一个 Servlet 程序中全局的储存信息的空间,服务器开始就存在,服务器关闭就销毁。当重启服务器,自然是会清空之前的 ServletContext 中的内容。

然而,我们当前使用的是 IDEA 内置的 Smart Tomcat 插件,我们通过这个插件来开启服务器,它并没有保存到本地 Tomcat 目录下的 webapps 目录,所以重启服务器,就会将之前的内容清空。

但是,如果我们将程序打包成 jar 包,在本地的目录进行部署,再通过 【 startup.bat 】这种手动方式开启 Tomcat 服务器,可能又是不同的情况。

分析代码

1

2

三、提出问题

基于上面的 ServletContext 机制,我们知道了,它就像一个冰箱,可以往里面放东西,也可以从里面取东西。所以,如果我们像之前说的,一个 webapp 目录下,所有的 Servlet 共用一个 TemplateEngine 对象,这实现起来就不麻烦了。我们只需要把TemplateEngine 初始化好,同时放到 ServletContext 对象里,后面的其他 Servlet 就不必再初始化了,直接取出刚才的 engine 对象即可。

然而,上面的代码让我们发现,取数据的时候并不是第一时间就能够取的,( 需要先往里面存入数据,等存好了数据,又要约定一些代码来告诉调用者,已经存好了数据。)所以,这样未免也太过于麻烦,总是需要时间差。

那么,有什么办法可以实现其他的 Servlet 都能够第一时间地就获取到一个初始化好的TemplateEngine 实例呢?

答:Servlet 为我们提供了 “监听器” 机制,可以解决上面的问题。

请继续往下看。

四、什么是监听器

在 Servlet 运行过程中,会有一些特殊的 “时机”,可以供我们来执行一些我们自定义的逻辑,监听器的作用就是让开发人员可以在这些 特殊时机 “插入代码”。

Servlet 中的监听器种类有很多,例如:

  • 监听 HttpSession 的创建 / 销毁,属性变化
  • 监听 HttpServletRequest 的创建 / 销毁,属性变化
  • 监听 ServletContext 的创建 / 销毁,属性变化

使用监听器之前,我们需要自定义一个类实现接口 ServletContextListener,并重写 contextInitialized 和 contextDestroyed 这两个方法

  • contextInitialized 这个方法表示:ServletContext 初始化完后,会调用这个方法;
  • contextDestroyed 这个方法表示:ServletContext 销毁之前,会调用这个方法。

五、代码示例:创建一个监听器

// 光创建这个类还不够,还需要让 Tomcat 能够识别这个类,通过 @WebListener 注解来进行描述
@WebListener
public class MyListener implements ServletContextListener {

    /**
     * ServletContext 初始化完后,会调用这个方法
     */
    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        System.out.println("ServletContext 初始化!");
        // 获取 ServletContext 对象,通过方法的参数获取
        ServletContext context = servletContextEvent.getServletContext();
        context.setAttribute("message", "hello world");
    }

    /**
     * ServletContext 销毁之前,会调用这个方法
     * 显然,当前的逻辑,我们并不会用到下面的方法
     */
    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {

    }
}

由于当前监听器的机制,能够让 ServletContext 初始化完后,直接调用contextInitialized 方法,所以,我们在这个方法中,就可以往 ServletContext 对象中存数据。关于代码的细节,我在上面的代码中,已经给出注解。

展示结果

当我们再次从 reader 路径,发送 HTTP 请求的时候,那么 message 对应的值就是我们在监听器中直接设置的值了。这样一来,就少了另外创建 WriterServlet 类的步骤。

0

六、结合 ServletContext 和 Listener

定好思路

  • 创建一个 ThymeleafConfig 类,在这个类中,我们实现一个监听器,此外,在代码中,创建一个 TemplateEngine 实例,并往 ServletContext 中存放。这样一来,在同一个 webapp 目录下,所有 Servlet 程序都能够第一时间内,从 ServletContext 取出 engine 实例。

  • 对当前博客,之前写的 【 th: each 】案例进行修改,去除掉 init 初始化方法。

1. ThymeleafConfig 类 (关键类)

@WebListener
public class ThymeleafConfig implements ServletContextListener {

    /**
     * 初始化 TemplateEngine
     */
    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        ServletContext context = servletContextEvent.getServletContext();

        // 1. 创建 engine 实例
        TemplateEngine engine = new TemplateEngine();
        // 2. 创建 resolver 实例
        ServletContextTemplateResolver resolver = new ServletContextTemplateResolver(context);
        resolver.setPrefix("WEB-INF/template/");
        resolver.setSuffix(".html");
        resolver.setCharacterEncoding("UTF-8");
        engine.setTemplateResolver(resolver);
        // 3. 把创建好的 engine 对象存放到 ServletContext 中
        context.setAttribute("engine", engine);
        System.out.println("TemplateEngine 初始化完毕!");
    }

    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {

    }
}

2. Servlet 代码

class Person2 {
    public String name;
    public String phone;

    public Person2(String name, String phone) {
        this.name = name;
        this.phone = phone;
    }
}

@WebServlet("/each2")
public class EachServlet2 extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html; charset = UTF-8");
        List<Person2> persons = new ArrayList<>();
        persons.add(new Person2("Jack", "123"));
        persons.add(new Person2("Rose", "456"));
        persons.add(new Person2("Ron", "789"));
        persons.add(new Person2("Bruce", "321"));
        persons.add(new Person2("Lisa", "654"));

        WebContext webContext = new WebContext(req, resp, this.getServletContext());
        webContext.setVariable("persons", persons);

        // 从 ServletContext 对象中取出 engine 实例
        ServletContext context = this.getServletContext();
        TemplateEngine engine = (TemplateEngine)context.getAttribute("engine");

		// 下面的 each 表示 html 模板文件
        engine.process("each", webContext, resp.getWriter());
    }
}

3. 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>

展示结果

2

总结 ServletContext 和 Listener

ServletContext 和 Listener 是 Servlet 提供的两个机制,在 web 开发中,它们常搭配 Thymeleaf 一起使用。

我们可以做一个比喻:

ServletContext 相当于一个冰箱,既可以往里面放东西,也可以从里面取东西,这些 “东西”,可以是普通的变量,也可以是对象。
Listener 监听器相当于一个摄像头,当有人往冰箱中放数据 / 取数据 的时候,它就能够监测到。在我们当前举的例子中,监听器是用来监测 TemplateEngine 类 所创建出来的实例,然而,在实际开发中,它也能够监听一些其他的数据。总之,监听器也是一个较为重要的机制,不仅局限于当前的逻辑。

Thymeleaf 新的总结流程

  1. 构造一个 html 模板.
    这里涉及到一些 Thymeleaf 对应的一些特殊属性,例如:【th:text, th:if, th:each, th:href …】

  2. 初始化模板引擎
    (1) 创建 TemplateEngine 实例
    (2) 创建 ServletContextTemplateResolver 实例,并规定好目录的路径,前后缀…
    (3) 把 engine 和 resolver 关联起来
    (4) 把 engine 对象存放到 ServletContext 里面

  3. 在业务代码中,使用引擎渲染
    (1) 从 ServletContext 中获取到 engine
    (2) 构建好 WebContext
    (3) 最终,通过 process 方法进行模板渲染(变量替换)

相关文章:

  • Java Spring整合Redis工具类
  • 深入理解 Compose Navigation 实现原理
  • springboot小型教育网站的开发与建设毕业设计源码100853
  • js类型检测
  • 微服务网关Gateway实践总结
  • python学生成绩管理系统 毕业设计-附源码061011
  • springboot财务管理系统毕业设计-附源码061533
  • STM32与DS18B20数字温度传感器寄生供电方式的新方案与1-wire总线程序设计
  • python+nodejs+vue大学生心理健康测评管理系统
  • springboot呼伦贝尔旅游网站的设计与实现毕业设计源码091833
  • 基于Springboot超市管理系统毕业设计-附源码231443
  • SSM汽车订票系统毕业设计-附源码061801
  • springboot农村饮用水海量数据存储平台毕业设计-附源码061205
  • 【数学分析笔记03】上确界和下确界
  • HarmonyOS鸿蒙学习笔记(9)Navigator组件实现页面路由跳转
  • Create React App 使用
  • JavaScript设计模式与开发实践系列之策略模式
  • PHP CLI应用的调试原理
  • SegmentFault 技术周刊 Vol.27 - Git 学习宝典:程序员走江湖必备
  • Spark RDD学习: aggregate函数
  • vue数据传递--我有特殊的实现技巧
  • 可能是历史上最全的CC0版权可以免费商用的图片网站
  • 普通函数和构造函数的区别
  • 前端面试之闭包
  • 使用 QuickBI 搭建酷炫可视化分析
  • 微服务入门【系列视频课程】
  • elasticsearch-head插件安装
  • mysql 慢查询分析工具:pt-query-digest 在mac 上的安装使用 ...
  • 从如何停掉 Promise 链说起
  • $.ajax()方法详解
  • (1/2)敏捷实践指南 Agile Practice Guide ([美] Project Management institute 著)
  • (11)MSP430F5529 定时器B
  • (3)llvm ir转换过程
  • (32位汇编 五)mov/add/sub/and/or/xor/not
  • (C++20) consteval立即函数
  • (iPhone/iPad开发)在UIWebView中自定义菜单栏
  • (附源码)node.js知识分享网站 毕业设计 202038
  • (附源码)spring boot公选课在线选课系统 毕业设计 142011
  • (蓝桥杯每日一题)平方末尾及补充(常用的字符串函数功能)
  • (三)centos7案例实战—vmware虚拟机硬盘挂载与卸载
  • (原創) 人會胖會瘦,都是自我要求的結果 (日記)
  • (转)eclipse内存溢出设置 -Xms212m -Xmx804m -XX:PermSize=250M -XX:MaxPermSize=356m
  • (转)Windows2003安全设置/维护
  • *p=a是把a的值赋给p,p=a是把a的地址赋给p。
  • .NET delegate 委托 、 Event 事件
  • .Net多线程总结
  • .net和jar包windows服务部署
  • .NET连接MongoDB数据库实例教程
  • .NET企业级应用架构设计系列之技术选型
  • @Import注解详解
  • @transaction 提交事务_【读源码】剖析TCCTransaction事务提交实现细节
  • @Valid和@NotNull字段校验使用
  • [1525]字符统计2 (哈希)SDUT
  • [2013AAA]On a fractional nonlinear hyperbolic equation arising from relative theory
  • [c]扫雷