上篇博客中,我们了解了服务器渲染,并用Thymeleaf模板引擎写了一个简单的猜数字小游戏。

下面我们简单了解一下Thymeleaf的语法。

Thymeleaf常见命令

命令 功能
th:text 在标签体中展示表达式求值结果的文本内容
th:[HTML标签属性] 设置任意的 HTML 标签属性的值
th:if 当表达式的结果为真时则显示内容,否则不显示
th:each 循环访问元素

Thymeleaf语法有很多,我们这里先简单介绍这四个。th:text和th:if在猜数字小游戏案例已经使用过,这里不再介绍。

th:[属性]
  • HTML模板代码:
1
2
3
4
<body>
<a th:href="${url1}">胖虎同学</a>
<a th:href="${url2}">胖虎同学1</a>
</body>
  • Servlet代码:
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
@WebServlet("/thymeleafUrl.html")
public class ThymeleafUrlServlet extends HttpServlet {
//1.创建核心类,用于完成页面渲染
public TemplateEngine engine = new TemplateEngine();
//2.初始化模板引擎,创建解析器,加载文件,关联
public void init(){
ServletContext context = getServletContext();
ServletContextTemplateResolver resolver = new ServletContextTemplateResolver(context);
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 {
ServletContext context = getServletContext();
resp.setContentType("text/html;charset=utf-8");
//3.实例化WebContext,关联变量
WebContext webContext = new WebContext(req,resp,context);
webContext.setVariable("url1","https://youngsay.cn");
webContext.setVariable("url2","http://blog.youngsay.cn");
//4.渲染
engine.process("thymeleafUrl",webContext,resp.getWriter());
}
}

th:each

列举游戏战绩:昵称、击杀、死亡、得分

  • HTML模板代码:
1
2
3
4
5
6
7
8
9
10
<body>
<ul>
<li th:each="person : ${persons}">
<span th:text="${person.name}"></span>
<span th:text="${person.kill}"></span>
<span th:text="${person.dead}"></span>
<span th:text="${person.score}"></span>
</li>
</ul>
</body>
  • Servlet代码:
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
class Person {
public String name;
public int kill;
public int dead;
public int score;

public Person(String name, int kill, int dead, int score) {
this.name = name;
this.kill = kill;
this.dead = dead;
this.score = score;
}

public String getName() {
return name;
}

public int getKill() {
return kill;
}

public int getDead() {
return dead;
}

public int getScore() {
return score;
}
}

@WebServlet("/thymeleafEach")
public class ThymeleafEach extends HttpServlet {
//创建TemplateEngine对象
public TemplateEngine engine = new TemplateEngine();
//初始化模板引擎
public void init() {
ServletContext context = getServletContext();
ServletContextTemplateResolver resolver = new ServletContextTemplateResolver(context);
resolver.setPrefix("/WEB-INF/templates/");
resolver.setSuffix(".html");
resolver.setCharacterEncoding("utf-8");
engine.setTemplateResolver(resolver);
System.out.println("初始化成功");
}

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
List<Person> persons = new ArrayList<Person>();
persons.add(new Person("損友比过狗友i",12,2,80));
persons.add(new Person("马棚管理员",21,8,130));
persons.add(new Person("吃枣药丸",16,5,110));
ServletContext context = getServletContext();
WebContext webContext = new WebContext(req,resp,context);
webContext.setVariable("persons",persons);

resp.setContentType("text/html;charset=utf-8");
engine.process("thymeleafEach",webContext, resp.getWriter());
}
}

ServletContext

观察上篇博客猜数字游戏代码和上面的两段代码,我们发现,每个Servlet都创建了一个TemplateEngine对象并进行初始化,其实这完全没必要,一个webapp中,只创建一个TemplateEngine对象即可。

这就需要用到ServletContext,它是Servlet程序全局存储信息的空间,每个webapp中只有一个ServlectContext,多个Servlet之间共享一个ServlectContext。通过 HttpServlet.getServletContext() 方法获取到当前webapp 的ServletContext对象。

下面是ServletContext常见方法:

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

我们用代码来证明多个一个webapp中多个Servlet类共用一个ServletContext。

  • 创建一个WriterServlet类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@WebServlet("/writer")
public class WriterServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1.获取message参数
String message = req.getParameter("message");
ServletContext context = getServletContext();
//2.将message设置到ServletContext
context.setAttribute("message",message);
resp.setContentType("text/html;charset=utf-8");
//3.返回结果
resp.getWriter().write("设置成功");
}
}
  • 创建一个ReaderServlet类
1
2
3
4
5
6
7
8
9
10
11
12
@WebServlet("/reader")
public class ReaderServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1.读取ServletContext中的message值
ServletContext context = getServletContext();
String message = (String) context.getAttribute("message");
resp.setContentType("text/html;charset=utf-8");
//2.返回读到的值
resp.getWriter().write(message);
}
}

展示结果

监听器

要想做到一个webapp中只创建一个TemplateEngine对象,除了认识ServletContext,还需要了解监听器。

使用监听器监听ServletContext的创建,创建好后在ServletContext完成初始化操作,后续的 Servlet 直接从 ServletContext 中获取到engine实例即可,不必每创建一个Servlet都初始化一次。

首先创建一个监听器类Mylistener,实现ServletContextListener接口,重写contextInitialized方法。在contextInitialized方法内完成初始化等一系列操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@WebListener
public class MyListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
//1.通过方法的参数可以获取到ServletContext
ServletContext context = sce.getServletContext();
//2.创建TemplateEngine类对象engine
TemplateEngine engine = new TemplateEngine();
//3.创建解析器
ServletContextTemplateResolver resolver = new ServletContextTemplateResolver(context);
//4.加载文件
resolver.setPrefix("/WEB-INF/templates/");
resolver.setSuffix(".html");
resolver.setCharacterEncoding("utf-8");
//5.关联engine和解析器
engine.setTemplateResolver(resolver);
//6.将engine放到ServletContext中,后续直接调用getAttribute获取engine
context.setAttribute("engine",engine);
}
}

有了MyListener类,之前代码中engine对象的创建和init方法内的初始化操作都可以删掉了。