目录
ServletContextTemplateResolver
Web开发的历程
web早期的页面和页面里面的逻辑是混合在一起的,在以前主要使用JSP、PHP来写。但是这种web开发只适合做简单的页面,复杂的页面无论是从开发角度还是从维护角度都比较麻烦。到了后面出现了模板引擎,它的作用就是使页面和逻辑分离。在java用Servlet来写这样的web,它在开发和维护性方面改善了很多,但是前后端的接口开发不方便分工。到了现在前端页面通过ajax和后端通信,后端只返回数据(通常是JSON格式)即可,后端也不用再去拼接页面,都是由前端拿到数据之后,自己负责拼接。前后端分离是现在主流的开发方式,但是模板引擎现在有些地方还在用,所以还是得掌握。
模板引擎的概念
模板引擎是动态页面的渲染方式,是一种将前后端分离开来的一种方式,但是这种分开不是绝对的分开,它会将前端和后端连接的接口关联起来,然后通过一套机制实现前后端交互。模板引擎就像一套试卷里面的开放性作文题,每个人写的都不一样,但是模板是一套的,那就是这张试卷。
模板引擎中的模板指的是将前端页面HTML文件中的一些内容提取出来,放到单独的文件中。对于一些“动态”的部分,这部分内容在模板中用占位符占位。当服务器将这些“动态”的部分计算好了之后,就把模板中的占位符替换成动态计算的结果,然后把这个组装好的HTML格式字符串再返回给浏览器。
java中的模板引擎有很多,现在主要掌握Thymeleaf。
Thymeleaf中的核心三个类
TemplateEngine
TemplateEngine负责渲染工作,渲染指的就是把动态的数据替换到html模板中的指定位置。
WebContext
WebContext负责把HTML模板中的变量和java代码中的变量给关联起来(可以简单的理解为键值对结构,HTML文件中的变量是key,而java文件中的变量是value),专门用来描述映射关系。
ServletContextTemplateResolver
ServletContextTemplateResolver称为模板解析器,它负责将之前写好的HTML模板加载过来交给TemplateEngine对象进行渲染
Thymeleaf的使用流程
0.将Thymeleaf所需要的环境依赖下载到java中的pom.xml文件中;
1.先编写html模板文件,放到指定目录中;
2.创建Servlet代码:
1)先创建一个TemplateEngine实例;
2)创建一个ServletContextTemplateResolver实例,并且指定需要加载模板文件的路径和字符集,并且把Resolver对象和TemplateEngine关联起来。这一步在init里面处理;
下面这两步在doGet里面处理:
3)把要和模板中关联起来的变量,使用WebContext来进行表示
4)进行最终的渲染,TemplateEngine有一个process方法专门来做这个事情
下面演示一个使用Thymeleaf的实例:
首先去第三方库里面下来Thymeleaf的依赖,将依赖复制到pom.xml文件中:
然后在WEB-INF目录下面创建一个template目录,再然后在该目录下面创建hello.html文件:
解析html文件中th:text="${message}"
th就是thymeleaf的缩写,意思是这个属性是thymeleaf所提供的的。text表示这里的类型是字符串,而${message}表示一个具体的变量。
刚开始创建这个标签的时候,message下面可能会有一条红线,这是因为还没有变量来关联它,所以这里可以不用担心。
第二步创建helloThymeleaf.java文件:
然后按照里面的步骤写上相应的业务代码:
@WebServlet("/helloThymeleaf")
public class HelloThymeleaf extends HttpServlet {
//首先先创建TemplateEngine实例
TemplateEngine engine = new TemplateEngine();
//完成Thymeleaf的初始化
@Override
public void init() throws ServletException {
//创建一个ServletContextTemplateResolver实例,并且指定需要加载模板文件的路径和字符集,并且把Resolver对象和TemplateEngine关联起来
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. 先从参数中读取出用户要传过来的 message 的值. (从 query string 中读取)
String value = req.getParameter("key");
//2.把要和模板中关联起来的变量,使用WebContext来进行表示
WebContext webContext = new WebContext(req,resp,this.getServletContext());
webContext.setVariable("message",value);
//3,进行最终的渲染
String hello = engine.process("hello", webContext);
System.out.println(hello);
resp.getWriter().write(hello);
}
}
然后启动服务器,在浏览中输入URL:
在通过一个猜数字的游戏来演示Thymeleaf的使用:
首先在template目录下面创建一个GuessNum.html文件,约定前后端交互的方式,然后写上相应的逻辑:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>猜数字游戏</title>
</head>
<body>
<form action="GuessNum" method="post">
<input type="text" name="num">
<input type="submit" value="提交">
</form>
<!--这个th:if是一个条件显示的逻辑.if后面的表达式为真就显示里面的标签.如果表达式为假就不显示-->
<div th:if="${!newGame}">
<!--这两个代码, 得是猜了之后才会显示的, 开始一局新游戏的时候不显示.-->
<div th:text="${result}"></div>
<div th:text="${count}"></div>
</div>
</body>
</html>
创建一个GuessNumer的java文件,然后写上相应的业务代码:
@WebServlet("/GuessNum")
public class GuessNumber extends HttpServlet {
//首先创建一个TemplateEngine实例
TemplateEngine engine = new TemplateEngine();
// toGuess 表示要猜的数字
private int toGuess = 0;
// count 表示要猜的次数
private int count = 0;
//完成Thymeleaf的初始化
@Override
public void init() throws ServletException {
//创建一个ServletContextTemplateResolver实例,并且指定需要加载模板的路径和字符集,并且把Resolver对象和TemplateEngine关联起来
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();
toGuess = random.nextInt(100)+1;
count = 0;
//2.返回一个页面
WebContext webContext = new WebContext(req,resp,this.getServletContext());
webContext.setVariable("newGame",true);
String guessNum = engine.process("GuessNum", webContext);
resp.getWriter().write(guessNum);
}
//处理一次猜的过程
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=utf-8");
// 1. 从请求中, 读取出用户提交的数字的内容
String num = req.getParameter("num");
int i = Integer.parseInt(num);
String result = "";
//2.和toGuess比较
if(i<toGuess){
result = "猜低了";
}
if(i>toGuess){
result = "猜大了";
}
if(i==toGuess){
result = "猜对了";
}
//3.自增猜的次数
count++;
//4.构造一个响应页面
WebContext webContext = new WebContext(req,resp,this.getServletContext());
webContext.setVariable("newGame",false);
webContext.setVariable("result",result);
webContext.setVariable("count",count);
String ret = engine.process("GuessNum", webContext);
resp.getWriter().write(ret);
}
}
在浏览器中输入URL然后猜数字:
Thymeleaf模板语法
命令 功能
th:text 把关联变量的值换到HTML标签中,主要是用来替换字符串
th:[HTML标签属性] 用来设置便签的属性(这些属性有href、src、calss、style......)
th:if 当表达式结果为真是就渲染,为假时就不渲染
th:each 循环渲染
上面th:text、th:if已经演示过了,下面演示其他两个:
设置标签属性
在template目录下面创建thymeleafAttr.html文件:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>设置标签的属性</title>
</head>
<body>
<a href="${url1}">百度</a>
<a href="${url2}">搜狗</a>
</body>
</html>
然后创建一个Setting_Lable的java文件,在里面写相应的业务逻辑:
@WebServlet("/Setting")
public class Setting_Lable extends HttpServlet {
//创建一个TemplateEngine实例
TemplateEngine engine = new TemplateEngine();
//完成Thymeleaf的初始化
@Override
public void init() throws ServletException {
//创建一个ServletContextTemplateResolver实例,并且指定需要加载的模板文件的路径和指定字符集,最后将resolver和TemplateEngine关联起来
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");
WebContext webContext = new WebContext(req,resp,this.getServletContext());
webContext.setVariable("url1","https://www.baidu.com");
webContext.setVariable("url2","https://www.sogou.com");
String attr = engine.process("thymeleafAttr", webContext);
resp.getWriter().write(attr);
}
}
打开浏览器输入URL:
循环渲染
th:each的功能是循环构造出多个元素,在屏幕上就相当于循环渲染出多个元素
语法格式:
th:each="自定义的元素变量名称:${集合变量名称}"
创建Each.html文件放到template目录下面:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<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>
创建ThymeleafEach的java文件,并写上相应的业务逻辑:
class Person{
public String name;
public String phone;
public Person(String name, String phone) {
this.name = name;
this.phone = phone;
}
public String getName() {
return name;
}
public String getPhone() {
return phone;
}
}
@WebServlet("/each")
public class ThymeleafEach extends HttpServlet {
//首先创建一个TemplateEngine对象实例
TemplateEngine engine = new TemplateEngine();
//完成Thymeleaf的初始化
@Override
public void init() throws ServletException {
//创建ServletContextTemplateResolver实例,并且指定模板文件的路径和字符集,最终将resolver和Thymeleaf关联起来
ServletContextTemplateResolver resolver = new ServletContextTemplateResolver(this.getServletContext());
resolver.setPrefix("/WEB-FIN/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> person = new ArrayList<>();
person.add(new Person("张三","12345"));
person.add(new Person("李四","54321"));
WebContext webContext = new WebContext(req,resp,this.getServletContext());
webContext.setVariable("persons",person);
String each = engine.process("Each", webContext);
resp.getWriter().write(each);
}
}
启动服务器,在浏览器中输入URL:
上面html代码中persons相当于java中的一个集合/数组,然后person就相当于访问到persons中的每一个元素。
我们发现上面的利用Thymeleaf的方式来让html文件和java文件分开的操作,每操作一次就要分别创建TemlateEngine实例、ServletContextTemplateResolver解析器对象一次,很麻烦。为了解决这一问题,前辈们就使用ServletContext对象中的方法,让多个Servlet之间,能够共享一个模板引擎。
ServletContext
ServletContext的概念
ServletContext叫做Servlet上下文,它是服务器为每一个工程创建的一个对象,是一个工程中Servlet程序全局存储信息的空间,它可以让同一个webapp目录下面的Servlet之间共享变量。服务器一启动就存在,服务器一关闭就销毁。
利用ServletContext的特性就可以实现让多个Servlet之间共享一个TemplateEngine,为了做到这一点,就需要把TemplateEngine的实例在ServletContext中进行创建。
在只创建一个引擎之间先来了解一下ServletContext中的方法:
ServletContext对象中的方法
void setAttribute(String name,Object obj) 创建键值对
Object getAttribute(String name) 根据属性名获取属性值,如果name不存在,返回null
void removeAttribute(String name) 删除对应的属性
getServletContext()或者this.getServletContext() 获取当前ServletContext对象
下面通过一个代码演示案例来实现多个Servlet之间通过SelvletContext来通信:
单独创建WriteServlet.java和ReadServlet.java两个文件,WriteServlet文件负责往ServletContext
里面写数据,而ReadServlet负责从ServletContext里面读数据:
// 负责往 ServletContext 里面写数据
// 浏览器通过一个形如 /writer?message=aaa 访问到 WriterServlet, 就把 message=aaa 这个键值对存到 ServletContext
@WebServlet("/write")
public class WriteServlet 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("key",message);
resp.getWriter().write("<h3>向ServletContext中写入数据成功</h3>");
}
}
// 使用这个 Servlet 从 ServletContext 中读取数据
// 就把刚才的 WriterServlet 存的数据给取出来~
@WebServlet("/read")
public class ReadServlet 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 key = (String) context.getAttribute("key");
// 3. 把取出的数据显示出来
resp.getWriter().write("key: "+key);
}
}
在浏览器输入框中输入URL:
监听器(Listener)
JavaEE中的监听器是JavaWeb中的三大组件(Servlet、Filter、Listener)之一,在Servlet运行过程中,会有一些特殊的“时机”,可以供我们来执行一些自定义的逻辑,我们用一种特殊的手段来获取这种时机,这就是监听器。监听器就是一个实现了特定接口的Java类,主要用来监听某个对象的状态变化。
Servlet中的监听器有很多,针对不同的Application、Session、Request对象,有不同的监听器,
可以监听HttpServletRequest的创建和销毁,还有属性变化、监听HttpSession的创建和销毁还有属性变化、监听ServletContext的创建和销毁还有属性变化。
监听生命周期:ServletRequestListener、HttpSessionListener、ServletContextListener
监听属性值:ServletRequestAttributeListener、HttpSessionAttributeListener 、ServletContextAttributeListener
我们在这里只需要监听ServletContext的创建即可。
监听器(Listener)的构成
- 事件源:被监听的对象
- 监听器:监听的对象,事件源的变化会触发监听器的响应行为
- 响应行为:监听器监听到事件源的状态变化时所执行的动作
监听ServletContext的创建
- 首先创建一个类,实现ServletContextListener接口,并实现它的两个方法contextInitialized和contextDestroyed
- 这个类需要使用@WebListener注解修饰,才能正确被Tomcat识别
- contextInitialized的参数是ServletContextEvent对象,这个对象表示一个事件。可以通过ServletContextEvent.getServletContext()来获取到ServletContext
下面演示监听器和模板引擎一起 工作:
首先创建一个ThymeleafConfig的java文件,让它实现ServletContextListener,并实现它的两个方法,里面用来实现TemplateEngine对象和创建解析器对象,并为模板文件设置路径和字符集,将模板文件和和模板引擎联系起来:
@WebListener
public class ThymeleafConfig implements ServletContextListener {
//这个会在context被创建的时候被调用,里面的ServletContextEvent表示一个事件,在这里就表示要执行的Servlet
//通过sce的getServletContext方法获取到刚刚被创建好的ServletContext对象
@Override
public void contextInitialized(ServletContextEvent sce) {
//1.首先先获得webapp的上下文context
ServletContext context = sce.getServletContext();
//2.初始化 TemplateEngine
TemplateEngine engine = new TemplateEngine();
//3.创建解析器对象,然后设置模板文件的路径和字符集,将模板文件和模板引擎关联起来
ServletContextTemplateResolver resolver = new ServletContextTemplateResolver(context);
resolver.setPrefix("/WEB-INF/template/");
resolver.setSuffix(".html");
resolver.setCharacterEncoding("utf-8");
engine.setTemplateResolver(resolver);
//4.把创建好的engine实例给放到ServletContext中.
context.setAttribute("engine",engine);
//打印日志,方便观察
System.out.println("TemplateEngine 初始化完毕!");
}
//这个会在context被销毁的时候调用
@Override
public void contextDestroyed(ServletContextEvent sce) {
}
}
然后就可以修改之前的带有模板引擎的文件了,修改之前HelloThymeleaf里面的代码,让他直接从ServletContext中获取到engine实例即可:
@WebServlet("/helloThymeleaf")
public class HelloThymeleaf extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=utf-8");
// 0. 获取到engine
ServletContext context = getServletContext();
TemplateEngine engine = (TemplateEngine) context.getAttribute("engine");
// 1. 先从参数中读取出用户要传过来的 message 的值. (从 query string 中读取)
String value = req.getParameter("key");
//2.把要和模板中关联起来的变量,使用WebContext来进行表示
WebContext webContext = new WebContext(req,resp,context);
webContext.setVariable("message",value);
//3,进行最终的渲染
String hello = engine.process("hello", webContext);
System.out.println(hello);
resp.getWriter().write(hello);
}
}
启动服务器,在浏览器中输入URL: