Java EE初阶---模板引擎

发布于:2023-01-13 ⋅ 阅读:(1241) ⋅ 点赞:(0)

1、动态页面的渲染方式

动态页面需要通过服务器根据客户端传来的参数 , 动态计算得到一些结果 , 并且把这些结果显示到页面上。 所谓的 "渲染" (render) 就是把数据和页面结合起来.

1.1 服务器渲染

数据和页面结合的工作 , 通过服务器完成 .

1.2 客户端渲染

服务器把数据返回给浏览器 , 由浏览器把数据和页面结合起来 .
浏览器和服务器之间的数据往往交互通过 ajax 进行 , 数据的格式往往使用 JSON.

之前咱们写的带服务器的表白墙, 就是通过客户端渲染实现的.  

接下来我们先介绍服务器渲染, 再介绍客户端渲染.

2、字符串拼接 HTML

之前我们的代码中直接通过 resp.getWritter().write() 的方式 , 直接在参数中拼接一个 HTML 格式
的字符串 .
@WebServlet("/html")
public class HtmlServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
throws ServletException, IOException {
        String name = (String) req.getParameter("name");
        resp.setContentType("text/html; charset=utf-8");
        resp.getWriter().write(String.format("<h3>name: %s</h3>", name));
   }
}

如果是返回一个简单的页面 , 可以按照上述方式拼接字符串完成 .
但是如果是一个复杂的页面呢 ? 我们通过一个简单的程序先初步感受一下 .

2.1 代码示例: 服务器版猜数字

之前我们实现的猜数字游戏 , 是一个纯粹的网页版本的程序 . 现在我们实现一个服务器版本的程序 .
创建 GuessNumServlet
  • 其中的成员 toGuess 表示要猜的数字, count 表示已经猜了的次数.
  • doGet 方法用于处理游戏的初始化. doPost 用于实现每一次 "" 的过程.
  • 服务器直接通过字符串拼接的方式构造 HTML, 返回给浏览器.
@WebServlet("/guessNum")
public class GuessNumServlet extends HttpServlet {
    // 要猜的数字
    private int toGuess = 0;
    // 已经猜了几次
    private int count = 0;
    private Random random = new Random();
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
throws ServletException, IOException {
        resp.setContentType("text/html; charset=utf-8");
        // get 方法返回一个初始页面, 并且在服务器中存一个随机数
        // 1. 构造随机数, 并清空 count
        toGuess = random.nextInt(100) + 1;
        System.out.println("toGuess = " + toGuess);
        count = 0;
        // 2. 返回页面内容
        String html = "<form action=\"guessNum\" method=\"GET\">\n" +
                "   <input type=\"submit\" value=\"重新开始游戏\">\n" +
                "</form>\n" +
                "\n" +
                "<form action=\"guessNum\" method=\"POST\">\n" +
                "   <input type=\"text\" name=\"num\">\n" +
                "   <input type=\"submit\" value=\"猜\">\n" +
                "</form>";
        resp.getWriter().write(html);
   }
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) 
throws ServletException, IOException {
        // post 方法处理猜数字的动作.
        resp.setContentType("text/html; charset=utf-8");
        // 1. 读取请求中用户猜的数字
        int num = Integer.parseInt(req.getParameter("num"));
        // 2. 比较大小
        String result = "";
        if (num < toGuess) {
            result = "猜低了";
       } else if (num > toGuess) {
            result = "猜高了";
       } else {
            result = "猜对了";
       }
        count++;
        // 3. 构造返回的页面内容
        String html = String.format("<form action=\"guessNum\" 
method=\"GET\">\n" +
                "   <input type=\"submit\" value=\"重新开始游戏\">\n" +
                "</form>\n" +
                "\n" +
                "<form action=\"guessNum\" method=\"POST\">\n" +
                "   <input type=\"text\" name=\"num\">\n" +
                "   <input type=\"submit\" value=\"猜\">\n" +
                "</form>\n" +
                "\n" +
                "<div>已经猜了: %d 次</div>\n" +
                "<div>结果: %s </div>", count, result);
        resp.getWriter().write(html);
   }
}
在 字符串 中直接编写 HTML , 这个是非常麻烦的 ( 尤其是有很多转义字符 ). 可以在其他编辑器直接把HTML 写好 , 然后复制粘贴进去 .
IDEA 会自动加入转义字符 .
<form action="guessNum" method="GET">
    <input type="submit" value="重新开始游戏">
</form>
<form action="guessNum" method="POST">
    <input type="text" name="num">
    <input type="submit" value="猜">
</form>
<div>已经猜了: 次</div>
<div>结果: </div>
部署程序 , 通过浏览器访问 http://127.0.0.1:8080/ServletHelloWorld/guessNum

2.2 小结:

在这个代码中我们可以看到 , 当前页面其实并不复杂 , 但是直接拼装字符串的方式却让代码非常臃肿。 尤其是 HTML Java 代码混在一起 , 非常不利于开发维护。

3、使用模板引擎

3.1 什么是模板引擎

模板引擎就是为了解决上面遇到的 , HTML Java 混在一起的问题 .
我们可以把 HTML 的内容提取出来 , 放到单独的文件中 , 称为 模板 .
对于一些 " 动态 " 的部分 , 比如猜数字的结果 , 猜数字的次数 , 这些可以内容在 模板 中使用 占位符 ( 一些特殊的符号) 占位 , 当服务器把这些 " 动态 " 的部分计算好了之后 , 就可以把 模板 中的 占位符 替换成动态计算的结果, 然后把这个组装好的 HTML 格式的字符串再返回给浏览器 .
模板就类似于考试中的 "填空题" 一样. 试卷上把一句话的一些核心信息挖掉. 然后由考生填写其中缺少的部分. 不同的考生, 就可能填出完全不同的内容.

这些表情包其实都是通过 "一个模板" 来生成出来的.  

Java 中的模板引擎有很多。 JSP, FreeMarker, Velocity, XMLTemplate 等。 使用风格大同小异。 我们使用的是 Thymeleaf。 这是当前最流行的一种模板引擎, 也是未来学习的 Spring Boot 官方推荐的模板引擎。

3.2 Thymeleaf 使用流程

(1)通过 maven 引入依赖
maven 中央仓库搜索 Thymeleaf

选择一个合适的版本, 比如 3.0.12 

<!-- https://mvnrepository.com/artifact/org.thymeleaf/thymeleaf -->
<dependency>
    <groupId>org.thymeleaf</groupId>
    <artifactId>thymeleaf</artifactId>
    <version>3.0.12.RELEASE</version>
</dependency>
(2)创建 HTML 模板文件
创建 helloThymeleaf.html , 放到 webapps/WEB - INF/templates 目录中
<h3 th:text="${message}"></h3>
  • th:text 是 Thymeleaf 的语法. 浏览器不能直接识别 th:text 属性.
(3)编写 Servlet 代码
创建 HelloThymeLeafServlet
  • HelloThymeleafServlet 包含一个成员 TemplateEngine , 这个类是 Thymeleaf 的核心类, 用于完成最终的页面渲染工作.
  • Servlet init 方法中对 TemplateEngine 进行初始化工作. 主要是创建一个解析器对象(resolver), 给解析器对象设置一些配置. 并把解析器对象关联到 TemplateEngine 对象上.
  • resolver 对象在创建的时候需要指定 ServletContext 对象, 这个对象是一个 Servlet 程序中全局唯一的对象. resolver 通过这个对象进一步获取到模板文件所在的路径. resolver 再通过setPrefix setSuffix 来指定哪些文件被视为模板文件, 需要被加载.
  • doGet 方法内部通过 engine.process 来进行具体的渲染工作. 这个操作会把具体的模板文, 和要渲染的数据关联起来, 并把最终结果写入到 resp 对象.
  • WebContext 对象用来组织要渲染的数据.
注意: resolver.setPrefix("/WEB-INF/templates/") 中的 /WEB-INF/templates/ 末尾一定
要有 /
如果缺少了 / , 则 resolver 加载模板文件时就会把路径当成 /WEB-
INF/templateshelloThymeleaf.html , 从而无法加载模板
@WebServlet("/helloThymeleaf")
public class HelloThymeleafServlet extends HttpServlet {
    // TemplateEngine 是核心对象. 切实的负责模板渲染的工作. 
    private TemplateEngine engine = new TemplateEngine();
    @Override
    public void init() throws ServletException {
        // 创建解析器对象. 功能是加载 HTML 模板, 并解析. 
        ServletContextTemplateResolver resolver = new
ServletContextTemplateResolver(this.getServletContext());
        // 设置解析器的字符集
        resolver.setCharacterEncoding("utf-8");
        // 设置符合哪些要求的文件要被解析器加载起来. setPrefix 表示文件前缀, setSuffix 表
示文件后缀. 
        resolver.setPrefix("/WEB-INF/templates/");
        resolver.setSuffix(".html");
        // 把解析器对象和 TemplateEngine 对象关联起来. 后面 TemplateEngine 进行具体渲染
工作时, 就需要先获取到刚刚解析好的模板. 
        engine.setTemplateResolver(resolver);
   }
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
throws ServletException, IOException {
        // 从参数中获取 message
        String message = req.getParameter("message");
        // 创建一个 WebContext 对象. 这里包含要渲染的变量有哪些, 以及变量的值是啥. 
        WebContext webContext = new WebContext(req, resp, getServletContext());
        // 指定针对模板中的 "message" 变量, 渲染成 message 的值. 
        webContext.setVariable("message", message);
        // 进行渲染工作, 把模板文件, 要渲染的数据组合起来, 并把结果直接写到
resp.getWriter() 中. 
        engine.process("helloThymeleaf", webContext, resp.getWriter());
   }
}
(4)部署程序
部署程序 , 通过 URL http://127.0.0.1:8080/ServletHelloWorld/helloThymeleaf?
message=hello 访问服务器

调整 query string 的内容, 就可以看到不同的页面显示结果.
小结
  • 注意体会上述代码中的关联关系。resovler setPrefix setSuffix 指定了从哪个目录下筛选哪些文件.
  • engine.process 方法的第一个参数指定了要加载哪个模板文件.
  • WebContext 中指定了模板变量名和变量值的对应关系(类似于一个哈希表结构). setVariable 的第一个参数, 要和模板文件中写的 ${message} 匹配.
  • engine.process 方法会把刚才的 WebContext 里的值替换到模板中, 并把最终结果写入到 resp 对象里.

上述代码中 , 我们遇到了三个新的关键的类 :
  • TemplateEngine , 核心功能是通过 process() 方法完成渲染工作
  • ServletContextTemplateResolver , 核心功能是加载模板文件, 为后面的渲染做准备.
  • WebContext , 核心功能是组织模板变量要替换成啥样的值.

3.3 代码示例: 改进猜数字

使用 Thymeleaf 就可以改进刚才的猜数字游戏 , Java 代码和 HTML 能够分离开 .
(1)创建 HTML 模板文件
创建 guessNum.html, 放到 webapp/WEB - INF/templates 目录中
<form action="guessNum" method="GET">
    <input type="submit" value="重新开始游戏">
</form>
<form action="guessNum" method="POST">
    <input type="text" name="num">
    <input type="submit" value="猜">
</form>
<div th:if="${!newGame}">
    <div>已经猜了: <span th:text="${count}"></span> 次</div>
    <div>结果: <span th:text="${result}"></span> </div>
</div>
  • 模板文件中涉及到三个变量, newGame 用来判定当前是不是一局新游戏. count 表示已经猜的次. result 表示 "猜大了" "猜小了" 这样的结果.
  • th:if 为一个条件语句. 如果后面的条件成立, 则会显示 <div th:if="${!newGame}"> 标签的内容
(2)编写 Servlet 代码
创建 GuessNumWithThymeleafServlet 类
这个代码的核心就是把之前实现的猜数字逻辑和 Thymeleaf 结合起来.
@WebServlet("/guessNum")
public class GuessNumWithThymeleafServlet extends HttpServlet {
    private TemplateEngine engine = new TemplateEngine();
    // 要猜的数字
    private int toGuess = 0;
    // 已经猜了几次
    private int count = 0;
    private Random random = new Random();
    @Override
    public void init() throws ServletException {
        ServletContextTemplateResolver resolver = new
ServletContextTemplateResolver(getServletContext());
        resolver.setPrefix("/WEB-INF/templates/");
        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. 构造随机数, 并清空 count
        toGuess = random.nextInt(100) + 1;
        System.out.println("toGuess = " + toGuess);
        count = 0;
        // 2. 构造数据
        WebContext webContext = new WebContext(req, resp, getServletContext());
        webContext.setVariable("newGame", true);
        // 3. 返回页面内容
        engine.process("guessNum", webContext, resp.getWriter());
   }
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) 
throws ServletException, IOException {
        resp.setContentType("text/html; charset=utf-8");
        // 1. 获取到用户猜的数字
        int num = Integer.parseInt(req.getParameter("num"));
        // 2. 比较大小
        String result = "";
        if (num < toGuess) {
            result = "猜低了";
       } else if (num > toGuess) {
            result = "猜高了";
       } else {
            result = "猜对了";
       }
        count++;
        // 3. 构造要返回的数据
        WebContext webContext = new WebContext(req, resp, getServletContext());
        webContext.setVariable("newGame", false);
        webContext.setVariable("count", count);
        webContext.setVariable("result", result);
        // 4. 返回数据
        engine.process("guessNum", webContext, resp.getWriter());
   }
}

通过上面的代码就可以明显的感受到, HTML 和 Java 已经彻底分离开. 不需要在字符串中手动拼装 HTML 了.

(3)部署程序
在浏览器通过 URL http://127.0.0.1:8080/ServletHelloWorld/guessNum 访问 , 即可看到程序效果

3.4 Thymeleaf 模板语法 

Thymeleaf 中的语法还有很多. 此处暂时只介绍最常用的几个.
(1)设置标签文本
th:text 的功能就是能设置标签的文本内容
(2)设置标签属性
一些常用的需要设置的属性 :
  • href
  • src
  • class
  • style
代码示例 : 设置 a 标签的 href 属性 .
1) 创建 thymeleafAttr.html webapp/WEB - INF/templates/ .
<a th:href="${url1}">百度</a>
<a th:href="${url2}">搜狗</a>

2) 创建 thymeleaf.AttrServlet . 

@WebServlet("/attr")
public class AttrServlet extends HttpServlet {
    private TemplateEngine engine = new TemplateEngine();
    @Override
    public void init() throws ServletException {
        ServletContextTemplateResolver resolver = new
ServletContextTemplateResolver(getServletContext());
        resolver.setPrefix("/WEB-INF/templates/");
        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");
        WebContext webContext = new WebContext(req, resp, getServletContext());
        webContext.setVariable("url1", "http://www.baidu.com");
        webContext.setVariable("url2", "http://www.sogou.com");
        engine.process("thymeleafAttr", webContext, resp.getWriter());
   }
}

3) 部署程序, 通过 URL http://127.0.0.1:8080/ServletHelloWorld/attr 访问

点击 a 标签 , 就会跳转到对应的页面 .
(3)条件判断
th:if 的功能是根据条件决定该标签是否显示 .
<div th:if="${!newGame}">
    <div>已经猜了: <span th:text="${count}"></span> 次</div>
    <div>结果: <span th:text="${result}"></span> </div>
</div>
(4)循环
th:each 的功能是可以循环的构造出多个元素 .
语法格式为 :
th:each="自定义的元素变量名称 : ${集合变量名称}"
代码示例 : 根据 List 生成列表
1) 创建 thymeleafEach.html, 放到 webapp/WEB - INF/templates/ .
<ul>
    <li th:each="person : ${persons}">
        <span th:text="${person.name}"></span>
        <span th:text="${person.phone}"></span>
    </li>
</ul>
  • persons 是一个 Java 中的 Collections 类型.
  • 其中 person 相当于就是访问到 persons 中的每个元素.
2) 创建 Person
class Person {
    private String name;
    private String phone;
    public String getName() {
        return name;
   }
    public String getPhone() {
        return phone;
   }
    public Person(String name, String phone) {
        this.name = name;
        this.phone = phone;
   }
}
  • name phone 两个属性的名字要和模板中的 名字 (形如: ${person.name} ) 匹配.
  • name phone 两个属性需要设置成 public, 或者提供 public getter 方法.
3) 创建 thymeleaf.EachServlet
@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.setCharacterEncoding("utf-8");
        resolver.setPrefix("/WEB-INF/templates/");
        resolver.setSuffix(".html");
        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("张三", "110"));
        persons.add(new Person("李四", "111"));
        persons.add(new Person("王五", "112"));
        WebContext webContext = new WebContext(req, resp, getServletContext());
        webContext.setVariable("persons", persons);
        engine.process("thymeleafEach", webContext, resp.getWriter());
   }
}

4) 部署程序, 使用 URL http://127.0.0.1:8080/ServletHelloWorld/each 访问服务器.

使用 each 操作还可以获取到当前元素的下标编号 .
代码示例 : 生成带序号的列表
修改 thymeleafEach.html
<ul>
    <li th:each="person, status : ${persons}">
        <span th:text="${status.index}"></span>
        <span th:text="${person.name}"></span>
        <span th:text="${person.phone}"></span>
    </li>
</ul>
status Thymeleaf 自动创建的 " 状态变量 ". 通过状态变量的 index 属性获取到当前元素的下标.
(5)查看模板语法的报错信息
通过形如下面的代码来渲染模板 , 如果模板渲染出错 , 能看到服务器返回了 500 状态码 , 但是看不到异常调用栈.
engine.process("thymeleafEach", webContext, resp.getWriter());
原因是抛出的异常被 process 内部处理掉了 .
可以使用以下代码代替 , 即可看到异常调用栈 .
String html = engine.process("thymeleafEach", webContext);
resp.getWriter().write(html);

4、只创建一个引擎实例

上面的代码中, 每个需要渲染页面的 Servlet 类都需要创建一个TemplateEngine实例并进行初始化。其实是完全没有必要的.

一个完整的项目中, 只需要创建一个 TemplateEngine, 并且只初始化一次即可.

类似的, ServletContextTemplateResolver 也是只需要创建一个实例, 并初始化一次.
为了完成这样的目的, 就需要使用 Servlet 中的 ServletContext 和 "监听器".

4.1 什么是 ServletContext

ServletContext是一个 Servlet 程序中全局的储存信息的空间, 服务器开始就存在, 服务器关闭才销毁.
  • Tomcat 在启动时,它会为每个Web app都创建一个对应的 ServletContext.
  • 一个WEB应用中的所有 Servlet 共享同一个 ServletContext 对象.
  • 可以通过 HttpServlet.getServletContext() 或者HttpServletRequest.getServletContext() 获取到当前 webapp 的 ServletContext 对象.
context 英文原义为 "环境" / "上下文". 此处的 ServletContext 对象就相当于一个 webapp 的 "上下文".

(1)ServletContext 对象的重要方法

可以看到 ServletContext HttpSession 类很类似 , 也是在内部组织了若干个键值对结构 , 相当于一个哈希表。此时同一个 webapp 的多个 Servlet 之间就可以通过 ServletContext 来共享数据。
(2)代码示例 : 多个 Servlet 共享数据 .
1) 创建 WriterServlet
  • 从请求参数中读取一个字符串 message
  • 通过 req.getServletContext() 或者 this.getServletContext() 即可获取到当前 webapp ServletContext
  • 通过 ServletContext.setAttribute() message 的值设置进去.
@WebServlet("/writer")
public class WriterServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
throws ServletException, IOException {
        // 1. 从请求参数中读取 message 字段的值
        String message = (String) req.getParameter("message");
        // 2. 把这个值设置到 ServletContext 中
        ServletContext context = req.getServletContext();
        context.setAttribute("message", message);
        // 3. 返回结果
        resp.setContentType("text/plain; charset=utf-8");
        resp.getWriter().write("设置 message 成功!");
   }
}

2) 创建 ReaderServlet

  • 通过 req.getServletContext() 或者 this.getServletContext() 即可获取到当前 webapp ServletContext
  • 通过 ServletContext.getAttribute() message 的值获取出来.
@WebServlet("/reader")
public class ReaderServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
throws ServletException, IOException {
        // 1. 从 ServletContext 中读取 message
        ServletContext context = req.getServletContext();
        String message = (String) context.getAttribute("message");
        // 2. 把结果返回给浏览器
        resp.setContentType("text/plain; charset=utf-8");
        resp.getWriter().write("message: " + message);
   }
}
3) 部署程序 , 通过 URL http://127.0.0.1:8080/ServletHelloWorld/writer?message=hello 访
, 可以看到 message 设置成功

再通过 URL http://127.0.0.1:8080/ServletHelloWorld/reader 访问 , 可以看到 message 已经获
取成功.

如果我们不访问 /writer, 直接访问 /reader, 那么此时得到的 message null.  

4.2 什么是监听器 (Listener)

在 Servlet 运行过程中, 会有一些特殊的 "时机", 可以供我们来执行一些我们自定义的逻辑.
监听器就是让程序猿可以在这些 特殊时机 "插入代码".
Servlet 中的监听器种类有很多, 例如:
  • 监听 HttpSession 的创建销毁, 属性变化
  • 监听 HttpServletRequest 的创建销毁, 属性变化
  • 监听 ServletContext 的创建销毁, 属性变化
此处我们只需要使用监听器监听 ServletContext 的创建即可.
涉及到的接口: ServletContextListener
我们实现这个接口, 并重写其中的 servletContextInitialized 方法. 当 ServletContext 创建的时候
就会自动执行到 servletContextInitialized 方法.
(1)代码示例: 监听 ServletContext 的创建
1) 创建 MyListener 类
  • 实现 ServletContextListener 接口, 并实现两个方法 contextInitialized 和contextDestroyed
  • MyListener 类需要使用 @WebListener 注解修饰, 才可以正确被 Tomcat 识别.
  • contextInitialized 的参数是 ServletContextEvent 对象. 这个对象表示一个 "事件". 此处我们暂不深究, 我们只需要知道通过 ServletContextEvent.getServletContext() 能获取ServletContext 即可.
@WebListener
public class MyListener implements ServletContextListener {
    public void contextInitialized(ServletContextEvent sce) {
        // 这个会在 context 被创建的时候调用.
        // 创建的时机在所有 Servlet 实例化之前.
        System.out.println("ServletContext 创建");
        // 通过 sce 的 getServletContext 方法获取到刚刚创建好的 ServletContext 对象
        ServletContext context = sce.getServletContext();
        context.setAttribute("message", "message 的初始值");
   }
    public void contextDestroyed(ServletContextEvent sce) {
        // 这个会在 context 被销毁的时候调用. 此处我们不关注
   }
}

2) 部署程序, 可以看到服务器启动过程中就会打印出 "ServletContext 创建" 这样的日志.

使用 URL http://127.0.0.1:8080/ServletHelloWorld/reader 来访问服务器, 可以看到

4.3 修改 Thymeleaf 引擎初始化代码

结合 ServletContext Listener, 我们就可以对之前的 Thymeleaf 引擎初始化代码做出调整 .
  • 创建监听器, 监听 ServletContext 的创建.
  • ServletContext 创建完毕后, contextInitialized 中创建 TemplateEngine 实例和ServletContextTemplateResolver 实例, 并完成初始化操作.
  • 把创建出来的 TemplateEngine 实例放到 ServletContext .
  • 后续 Servlet 如果需要使用 TemplateEngine , 那么直接从 ServletContext 获取到之前创建好的TemplateEngine 实例即可, 不必重新创建.
1) 创建 ThymeleafConfig , 实现 ServletContextListener 接口
@WebListener
public class ThymeleafConfig implements ServletContextListener {
    public void contextInitialized(ServletContextEvent sce) {
        // 1. 先获取到 context 对象
        ServletContext context = sce.getServletContext();
        // 2. 创建 TemplateEngine 实例
        TemplateEngine engine = new TemplateEngine();
        // 3. 创建 ServletContextTemplateResolver 实例
        ServletContextTemplateResolver resolver = new
ServletContextTemplateResolver(context);
        // 4. 给 resolve 设置一些属性
        resolver.setCharacterEncoding("utf-8");
        resolver.setPrefix("/WEB-INF/templates/");
        resolver.setSuffix(".html");
        // 5. 关联 resolver 和 engine
        engine.setTemplateResolver(resolver);
        // 6. 把 engine 放到 ServletContext 中, 以备后面的其他 Servlet 类使用
        context.setAttribute("engine", engine);
   }
    public void contextDestroyed(ServletContextEvent sce) {
   }
}
2) 后续的 Servlet 直接从 ServletContext 中获取到 engine 实例即可 .
创建新的类 HelloThymeleaf , 模板文件复用之前的 helloThymeleaf.html
@WebServlet("/helloThymeleafTest")
public class HelloThymeleaf extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
throws ServletException, IOException {
        String message = req.getParameter("message");
        ServletContext context = getServletContext();
        TemplateEngine engine = (TemplateEngine) context.getAttribute("engine");
        WebContext webContext = new WebContext(req, resp, context);
        webContext.setVariable("message", message);
        engine.process("helloThymeleaf", webContext, resp.getWriter());
   }
}

3) 部署程序, 通过 URL http://127.0.0.1:8080/ServletHelloWorld/helloThymeleafTest?message=hello 访问

4.4 小结

在 Thymeleaf 中, 主要涉及到三个核心类
  • TemplateEngine : 只应该有一个实例, 被初始化一次.
  • ServletContextTemplateResolver : 只应该有一个实例, 被初始化一次.
  • WebContext : 可以有多个实例, 每次请求都需要创建一个新的 WebContext
思考: 使用 "单例模式" 控制 TemplateEngine 能否完成同样的效果嘛?
注意: 创建 TemplateEngine 需要依赖 ServletContextTemplateResolver , 而
ServletContextTemplateResolver 的创建又依赖 ServletContext.
如果使用脱离了 Servlet 环境的单例模式, 那么就难以获取到 ServletContext 实例。如果要是在 ServletContextListener 中使用 单例模式, 反而就不如直接把得到的TemplateEngine 放到 ServletContext 中更方便。

5、综合案例

5.1 表白墙

通过 Thymeleaf 重新实现表白墙程序 .
1. 准备工作
1) 使用 IDEA 创建 maven 项目 . 细节不再赘述 .
2) 创建 webapp 目录和 WEB-INF/web.xml WEB-INF/templates

3) 修改 pom.xml, 引入依赖.  

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.example</groupId>
    <artifactId>表白墙服务器版(2)</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
        <!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api 
-->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.thymeleaf</groupId>
            <artifactId>thymeleaf</artifactId>
            <version>3.0.12.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.45</version>
        </dependency>
    </dependencies>
    <packaging>war</packaging>
    <build>
        <finalName>MessageWall</finalName>
    </build>
</project>
2. 创建模板文件
创建 message.html, 放到 WEB-INF/templates .
这个代码从最初的静态页面版本的表白墙拷贝过来, 稍加修改.
修改的要点 :
  • 样式和页面结构不变.
  • 给这几个 input 输入框外面套上一层 form 标签. <form action="message" method="POST">
  • "", "对谁", "说什么" 这三个 input 标签加上 name 属性. 这是后续提交表单的必备信息.
  • 把提交按钮的 input type 改成 "submit"
  • 在最下方显示消息列表的部分, 做出如下调整
<div class="row" th:each="msg : ${messages}">
    <span th:text="${msg.from}">小猫</span> 对 <span th:text="${msg.to}">小狗
</span> 说: <span th:text="${msg.message}">喵</span>
</div>
  • 把 CSS 中的样式进行了微调. 原来是针对所有的 span 设置宽度和行高, 现在改成名为 label class, 并把 "", "对谁", "说什么" 三个 span 标签引用 label.
完整代码如下 :
<!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>
    <style>
       * {
            margin: 0;
            padding: 0;
       }
        .container {
            width: 400px;
            margin: 0 auto;
       }
        h1 {
            text-align: center;
            padding: 20px 0;
       }
        p {
            color: #666;
            text-align: center;
            font-size: 14px;
            padding: 10px 0;
       }
        .row {
            height: 40px;
            display: flex;
            justify-content: center;
            align-items: center;
       }
        /* 要修改这里的样式 */
        .label {
            width: 100px;
            line-height: 40px;
       }
        .edit {
            width: 200px;
            height: 30px;
       }
        .submit {
            width: 304px;
            height: 40px;
            color: white;
            background-color: orange;
            border: none;
       }
        .submit:active {
            background-color: #666;
       }
    </style>
</head>
<body>
<div class="container">
    <h1>表白墙</h1>
    <p>输入后点击提交, 会将信息显示在表格中</p>
    <form action="message" method="POST">
        <div class="row">
            <span class="label">谁: </span>
            <input class="edit" type="text" name="from">
        </div>
        <div class="row">
            <span class="label">对谁: </span>
            <input class="edit" type="text" name="to">
        </div>
        <div class="row">
            <span class="label">说什么: </span>
            <input class="edit" type="text" name="message">
        </div>
        <div class="row">
            <input type="submit" value="提交" class="submit">
        </div>
    </form>
    <div class="row" th:each="msg : ${messages}">
        <span th:text="${msg.from}">小猫</span> 对 <span th:text="${msg.to}">小狗
</span> 说: <span th:text="${msg.message}">喵</span>
    </div>
</div>
</body>
</html>
3. 拷贝之前的代码
拷贝之前版本实现的 DBUtil, Message, MessageServlet 类到项目中 .
DBUtil
// 负责和数据库建立连接
public class DBUtil {
    private static final String URL = "jdbc:mysql://127.0.0.1:3306/MessageWall?
characterEncoding=utf8&useSSL=false";
    private static final String USERNAME = "root";
    private static final String PASSWORD = "";
    private static DataSource dataSource = null;
    private static DataSource getDataSource() {
        if (dataSource == null) {
            synchronized (DBUtil.class) {
                if (dataSource == null) {
                    dataSource = new MysqlDataSource();
                   ((MysqlDataSource)dataSource).setUrl(URL);
                   ((MysqlDataSource)dataSource).setUser(USERNAME);
                   ((MysqlDataSource)dataSource).setPassword(PASSWORD);
               }
           }
       }
        return dataSource;
   }
    public static Connection getConnection() {
        try {
            return getDataSource().getConnection();
       } catch (SQLException e) {
            e.printStackTrace();
       }
        return null;
   }
    public static void close(Connection connection,
                             PreparedStatement statement,
                             ResultSet resultSet) {
        try {
            if (resultSet != null) {
                resultSet.close();
           }
            if (statement != null) {
                statement.close();
           }
            if (connection != null) {
                connection.close();
           }
       } catch (SQLException e) {
            e.printStackTrace();
       }
   }
}

 Message

public class Message {
    public String from;
    public String to;
    public String message; }

MessageServlet

  • 删除 objectMapper 成员
  • 清空其中的 doGet doPost , 一会重新实现.
  • load save 方法保持原状
@WebServlet("/message")
public class MessageServlet extends HttpServlet {
    private List<Message> load() {
        List<Message> messages = new ArrayList<Message>();
        // 1. 和数据库建立连接
        Connection connection = DBUtil.getConnection();
        PreparedStatement statement = null;
        ResultSet resultSet = null;
        try {
            // 2. 拼装 SQL
            String sql = "select * from messages";
            statement= connection.prepareStatement(sql);
            // 3. 执行 SQL
            resultSet = statement.executeQuery();
            // 4. 遍历结果集合
            while (resultSet.next()) {
                Message message = new Message();
                message.from = resultSet.getString("from");
                message.to = resultSet.getString("to");
                message.message = resultSet.getString("message");
                messages.add(message);
           }
       } catch (SQLException e) {
            e.printStackTrace();
       } finally {
            // 5. 释放必要的资源
            DBUtil.close(connection, statement, resultSet);
       }
        return messages;
   }
    private void save(Message message) {
        // 1. 和数据库建立连接
        Connection connection = DBUtil.getConnection();
        PreparedStatement statement = null;
        try {
            // 2. 拼装 SQL
            String sql = "insert into messages values(?, ?, ?)";
            statement = connection.prepareStatement(sql);
            statement.setString(1, message.from);
            statement.setString(2, message.to);
            statement.setString(3, message.message);
            // 3. 执行 SQL
            statement.executeUpdate();
       } catch (SQLException e) {
            e.printStackTrace();
       } finally {
            // 4. 释放必要的资源
            DBUtil.close(connection, statement, null);
       }
   }
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
throws ServletException, IOException {
        resp.setContentType("text/html;charset=utf-8");
        // GET 方法获取页面内容
   }
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) 
throws ServletException, IOException {
        // POST 方法发布新留言
   }
}
4. 初始化模板引擎
创建 ThymeleafConfig , 并实现 ServletContextListener 接口
  • contextInitialized 中初始化 TemplateEngine
@WebListener
public class ThymeleafConfig implements ServletContextListener {
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("初始化 engine");
        ServletContext context = sce.getServletContext();
        TemplateEngine engine = new TemplateEngine();
        ServletContextTemplateResolver resolver = new
ServletContextTemplateResolver(context);
        resolver.setCharacterEncoding("utf-8");
        resolver.setPrefix("/WEB-INF/templates/");
        resolver.setSuffix(".html");
        engine.setTemplateResolver(resolver);
        context.setAttribute("engine", engine);
   }
    public void contextDestroyed(ServletContextEvent sce) {
   }
}

5. 编写 doGet doPost

编写 MessageServlet doGet 方法和 doPost 方法 .
使用 doGet 方法来查看消息 , 使用 doPost 方法来添加消息 .
1) doGet
页面模板中的变量名为 messages , 此处就需要在 webContext 中设置一个 messages 变量 .
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws
ServletException, IOException {
    resp.setContentType("text/html;charset=utf-8");
    // GET 方法获取页面内容
    // 1. 从数据库读取数据
    List<Message> messages = load();
    // 2. 再进行页面渲染
    ServletContext context = getServletContext();
    TemplateEngine engine = (TemplateEngine) context.getAttribute("engine");
    WebContext webContext = new WebContext(req, resp, context);
    webContext.setVariable("messages", messages);
    engine.process("message", webContext, resp.getWriter());
}

2) doPost

注意 , 请求也要设置字符编码为 utf - 8 , 否则读取到的参数可能是乱码 .
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws
ServletException, IOException {
    // POST 方法发布新留言
    // 1. 从参数中获取到消息数据
    //   [注意] 要给 req 也设置字符集, 否则读取表单中的中文可能就会乱码
    req.setCharacterEncoding("utf-8");
    Message message = new Message();
    message.from = req.getParameter("from");
    message.to = req.getParameter("to");
    message.message = req.getParameter("message");
    System.out.println(message.from + " 对 " + message.to + " 说: " +
message.message);
    // 2. 把数据插入到数据库中
    save(message);
    // 3. 重定向到 GET 方法的页面
    resp.sendRedirect("message");
}
6. 部署程序
部署程序 , 使用 URL http://127.0.0.1:8080/MessageWall/message

5.2 在线相册

这是一个直接从网上找到的相册程序 . 我们基于这个程序的前端页面 , 把这个程序改造成一个带服务器版本的相册程序.
初始代码
目录结构形如

1) index.html

  • 每个图片是一个 figure 标签.
  • figure 标签中使用 img 表示缩略图, 使用 figcaption 标签来放图片的标题.
figure 和 figcaption 是 HTML5 引入的一种新的 "语义化标签". 此处我们不展开讨论.
<head>
    <meta charset="UTF-8">
    <title>相册</title>
    <link rel="stylesheet" href="./style.css">
</head>
<body>
<!-- 第一组图片 -->
<figure class="sample">
    <img src="image/1.jpg" alt="sample1" />
    <figcaption>
        <div>
            <h2>Deconovo</h2>
            <h4>Classic Sculptures</h4>
        </div>
    </figcaption>
    <a href="image/1.jpg"></a>
</figure>
<figure class="sample">
    <img src="image/2.jpg" alt="sample2" />
    <figcaption>
        <div>
            <h2>Deconovo</h2>
            <h4>Classic Sculptures</h4>
        </div>
    </figcaption>
    <a href="image/2.jpg"></a>
</figure>
<figure class="sample">
    <img src="image/3.jpg" alt="sample3" />
    <figcaption>
        <div>
            <h2>Deconovo</h2>
            <h4>Classic Sculptures</h4>
        </div>
    </figcaption>
    <a href="image/3.jpg"></a>
</figure>
</body>

2) style.css

/* 引入文字样式库 */
@import url(https://fonts.googleapis.com/css?family=Raleway:400,700);
/* sample 部分的整体样式 */
.sample {
    font-family: 'Raleway', Arial, sans-serif;
    position: relative;
    overflow: hidden;
    margin: 10px;
    min-width: 230px;
    max-width: 315px;
    width: 100%;
    color: #ffffff;
    text-align: center;
    font-size: 16px;
    background-color: #000000; }
.sample *,
.sample *:before,
.sample *:after {
    -webkit-box-sizing: border-box;
    box-sizing: border-box;
    /* 当过了 0.55s 过渡效果 */
    -webkit-transition: all 0.55s ease;
    transition: all 0.55s ease; }
/* 图片部分的样式 */
.sample img {
    max-width: 100%;
    backface-visibility: hidden;
    vertical-align: top; }
/* figcaption 用作文档中插图的图像,带有一个标题 */
.sample figcaption {
    position: absolute;
    bottom: 25px;
    right: 25px;
    padding: 5px 10px 10px; }
/* 绘制线条 */
.sample figcaption:before,
.sample figcaption:after {
    height: 2px;
    width: 400px;
    position: absolute;
    content: '';
    background-color: #ffffff; }
/* 上面一条线 */
.sample figcaption:before {
    top: 0;
    left: 0;
    -webkit-transform: translateX(100%);
    transform: translateX(100%);
}
/* 下面一条线 */
.sample figcaption:after {
    bottom: 0;
    right: 0;
    -webkit-transform: translateX(-100%);
    transform: translateX(-100%);
}
/* 绘制线条 */
.sample figcaption div:before,
.sample figcaption div:after {
    width: 2px;
    height: 300px;
    position: absolute;
    content: '';
    background-color: #ffffff; }
/* 左面一条线 */
.sample figcaption div:before {
    top: 0;
    left: 0;
    -webkit-transform: translateY(100%);
    transform: translateY(100%);
}
/* 右面一条线 */
.sample figcaption div:after {
    bottom: 0;
    right: 0;
    -webkit-transform: translateY(-100%);
    transform: translateY(-100%);
}
/* 文字部分 */
.sample h2,
.sample h4 {
    margin: 0;
    text-transform: uppercase; }
.sample h2 {
    font-weight: 400; }
.sample h4 {
    display: block;
    font-weight: 700;
    background-color: #ffffff;
    padding: 5px 10px;
    color: #000000; }
.sample a {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0; }
/* 当鼠标放到图片时的效果, .hover 仅演示需要,可自行取消 */
.sample:hover img,
.sample.hover img {
    zoom: 1;
    filter: alpha(opacity=50);
    -webkit-opacity: 0.5;
    opacity: 0.5; }
.sample:hover figcaption:before,
.sample.hover figcaption:before,
.sample:hover figcaption:after,
.sample.hover figcaption:after,
.sample:hover figcaption div:before,
.sample.hover figcaption div:before,
.sample:hover figcaption div:after,
.sample.hover figcaption div:after {
    -webkit-transform: translate(0, 0);
    transform: translate(0, 0);
}
.sample:hover figcaption:before,
.sample.hover figcaption:before,
.sample:hover figcaption:after,
.sample.hover figcaption:after {
    /* 过渡延时 0.15s */
    -webkit-transition-delay: 0.15s;
    transition-delay: 0.15s; }
/* 背景仅演示作用 */
html {
    height: 100%; }
body {
    background-color: #212121;
    display: flex;
    justify-content: center;
    align-items: center;
    flex-flow: wrap;
    margin: 0;
    height: 100%; }
3) image/1.jpg image/2.jpg image/3.jpg

1. 准备工作

1) 使用 IDEA 创建 maven 项目 . 细节不再赘述 .
2) 创建 webapp 目录和 WEB-INF/web.xml WEB-INF/templates

3) 修改 pom.xml, 引入依赖.  

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.example</groupId>
    <artifactId>表白墙服务器版(2)</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
        <!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api 
-->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.thymeleaf</groupId>
            <artifactId>thymeleaf</artifactId>
            <version>3.0.12.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.45</version>
        </dependency>
    </dependencies>
    <packaging>war</packaging>
    <build>
        <finalName>MessageWall</finalName>
    </build>
</project>
2. 创建模板
把原版代码拷贝到 webapp 目录中 , 按如下结构组织 .
注意 index.html 和 style.css 都是在 webapp 目录中, 不是在 WEB-INF 中.

然后修改 index.html 的内容

  • 每个图片只保留 <h2> 这个主标题, 不保留 <h4> 这个副标题.
  • 服务器获取到图片, 组织成一个名为 images List.
  • 然后根据 images 中的信息, 填充 img 标签的 src 属性(用于展示) a 标签的 href 属性(用于点击跳).
<body>
    <figure class="sample" th:each="image : ${images}">
        <img src="image/1.jpg" alt="sample1" th:src="${image.src}"/>
        <figcaption>
            <div>
                <h2 th:text="${image.name}">Deconovo</h2>
            </div>
        </figcaption>
        <a href="image/1.jpg" th:href="${image.src}"></a>
    </figure>
</body>

3. 初始化模板引擎

创建 ThymeleafConfig , 实现逻辑和之前的一样
@WebListener
public class ThymeleafConfig implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        ServletContext context = sce.getServletContext();
        TemplateEngine engine = new TemplateEngine();
        ServletContextTemplateResolver resolver = new
ServletContextTemplateResolver(context);
        resolver.setPrefix("/");
        resolver.setSuffix(".html");
        resolver.setCharacterEncoding("utf-8");
        engine.setTemplateResolver(resolver);
        context.setAttribute("engine", engine);
   }
    @Override
    public void contextDestroyed(ServletContextEvent sce) {
   }
}
4. 实现图片列表页面
创建 Image
public class Image {
    public String name;
    public String src; }
创建 ImageServlet
  • 图片都放在 webapp/image 目录中. 可以通过 ServletContext.getRealPath("/image") 获取/image 的磁盘上的绝对路径.
  • 直接使用 File.listFiles() 来罗列出指定目录中的所有文件.
  • 把这些文件取出来, 组织成 List<Image> , 填充到模板中
@WebServlet("/imageShow")
public class ImageServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
throws ServletException, IOException {
        resp.setContentType("text/html; charset=utf-8");
        // 1. 获取当前有哪些图片
        List<Image> images = getImages();
        // 2. 把图片信息返回到页面上
        ServletContext context = getServletContext();
        TemplateEngine engine = (TemplateEngine) context.getAttribute("engine");
        WebContext webContext = new WebContext(req, resp, context);
        webContext.setVariable("images", images);
        String html = engine.process("index", webContext);
        resp.getWriter().write(html);
   }
    
    // 从 webapp/image 目录中读取图片信息
    private List<Image> getImages() {
        List<Image> images = new ArrayList<>();
        // 1. 获取 "/image" 在磁盘上的绝对路径
        ServletContext context = getServletContext();
        String path = context.getRealPath("/image");
        System.out.println("path: " + path);
        // 2. 查看这个路径下都有哪些图片文件
        File imageRoot = new File(path);
        File[] files = imageRoot.listFiles();
        for (File f : files) {
            Image image = new Image();
            image.name = f.getName();
            // 在网页上点击图片, 就能跳转到图片的详细页面. 
            image.src = "image/" + f.getName();
            images.add(image);
       }
        return images;
   }
}
此时部署程序 , 通过 URL http://127.0.0.1:8080/Image/imageShow 访问, 可以看到图片展示出的效果

点击图片, 就可以跳转到图片的具体展示页. (这个是一个静态资源)

5. 实现图片上传功能

创建 upload.html, 放到 webapps 目录中
<form action="imageUpload" method="post" enctype="multipart/form-data">
    <input type="file" name="image">
    <input type="submit" value="上传">
</form>
创建 UploadServlet
  • 通过 HttpServletRequest.getPart("image") 获取到 Part 对象.
  • 通过 Part.write() 方法把请求中的数据写入到磁盘文件中, 放到 webapp/image 目录里.
  • 上传成功后返回一个重定向响应, 返回到图片列表页
@MultipartConfig
@WebServlet("/imageUpload")
public class UploadServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) 
throws ServletException, IOException {
        // 把图片放到 webapp/image 目录中
        // 先获取到 image 所在的磁盘路径
        String path = getServletContext().getRealPath("/image");
        // 从请求中获取到图片的 Part 对象
        Part part = req.getPart("image");
        // 获取到图片名字
        String name = part.getSubmittedFileName();
        // 把图片放到指定目录中
        part.write(path + "/" + name);
        // 返回图片列表页
        resp.sendRedirect("imageShow");
   }
}

部署程序, 通过 URL http://127.0.0.1:8080/Image/upload.html 打开上传图片页面

上传图片后的效果.  

6、附录: 代码片段

6.1 引入依赖

<!-- https://mvnrepository.com/artifact/org.thymeleaf/thymeleaf -->
<dependency>
    <groupId>org.thymeleaf</groupId>
    <artifactId>thymeleaf</artifactId>
    <version>3.0.12.RELEASE</version>
</dependency>

6.2 初始化模板引擎 

@WebListener
public class ThymeleafConfig implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        ServletContext context = sce.getServletContext();
        TemplateEngine engine = new TemplateEngine();
        ServletContextTemplateResolver resolver = new
ServletContextTemplateResolver(context);
        // 指定模板文件所在的路径
        resolver.setPrefix("/WEB-INF/templates/");
        resolver.setSuffix(".html");
        resolver.setCharacterEncoding("utf-8");
        engine.setTemplateResolver(resolver);
        context.setAttribute("engine", engine);
   }
    @Override
    public void contextDestroyed(ServletContextEvent sce) {
   }
}

6.3 模板页面

  • th:text 设置元素的值.
  • th:属性 设置元素属性.
  • th:if 进行条件判定.
  • th:each 循环遍历
<div class="row" th:each="msg : ${messages}">
    <span th:text="${msg.from}">小猫</span> 对 <span th:text="${msg.to}">小狗
</span> 说: <span th:text="${msg.message}">喵</span>
</div>

6.4 渲染页面 

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws
ServletException, IOException {
 // 先获取数据
    // 再进行页面渲染
    ServletContext context = getServletContext();
    // "engine" 是之前在 监听器 里面初始化好的 TemplateEngine 对象
    TemplateEngine engine = (TemplateEngine) context.getAttribute("engine");
    WebContext webContext = new WebContext(req, resp, context);
    // "messages" 是模板页面中的 "变量" 名
    webContext.setVariable("messages", messages);
    // "message" 是模板文件名.
    engine.process("message", webContext, resp.getWriter());
}

本文含有隐藏内容,请 开通VIP 后查看