1)ServletContext(上下文)的介绍
1)之前就是说每一个webapp都对应着一个ServletContext对象,一个webapp都对应着多个Servlet对象,我们的每一个Servlet对象都创建了一个TemplateEngine对象ServletContextTemplateResolver对象
2)而咱们所期望的效果是:下面就可以进行保证ServletContext是webapp级别的单例模式
2)ServletContext的具体用法
2.1)ServletContext是Tomact的内部代码new出来的,咱们是看不到的;
2.2)ServletContext所存在的意义是可以让同一个webapp中的多个Servlet之间可以共享数据,所以ServletContext提供了一些get/set接口,所以程序员想在一个Tomact中的一个webapp的多个Servlet之间共享数据,也是可以通过自定义键值对的方式来实现的
1)void setAttribute(String name,Object obj)向我们的ServletContext中设置键值对;
2)Object getAttribute(String name)根据属性名来获取到属性的值
3)void removeAttribute(),删除对应的属性
1)咱们之前用的HttpSession可以让程序员自己去定义一些键值对来进行存储,就像是一个时间胶囊一样,我们在登陆成功的时候会存放一些数据进去,后续再进行登录进行访问的时候再通过getAttribute()方法进行取出来使用;
2)程序员想要在这多个Servlet之间共享哪些数据,也是通过自定义键值对的方式来进行实现的;那么我们就可以想WebContext对象中来设置一些自定义的键值对,然后再别的Servlet中按照这个Key来进行访问也就可以了
3)也就是说只要是在Java200这个webapp中创建的Servlet,咱们都是可以通过getServletContext得到同一个上下文对象
案例:实现同一个WebApp中的多个Servlet之间实现数据共享:
1)使用WebContext实现多个Servlet中共享数据,相当于是多个Servlet之间进行通信
2)我们创建两个Servlet对象,一个是WriteServlet,向我们的webapp里面的ServletContext设置属性,另一个是ReadServlet,向我们的webapp里面的ServletContext来读取刚才设置的属性,先进行访问write再去访问read;
一:创建WriterServlet类
1)创建一个GET请求;
http://127.0.0.1:8080/Java200/write?message=aaa;
2)我们在WriteServlet中调用req.getparamter()方法,从请求参数中得到一个字符串message,通过req.getServletContext(),或者this.getServletContext(),就可以获取到当前webapp中的ServletContext对象;
3)通过ServletContext.setAttribute()把URL中的message的值设置进去;
二:我们使用ReadServlet来从ServletContext来进行读取数据,就把刚才的WriteServlet存放的数据给取出来;
1)当前我们写的两个Servlet对象都是用的一个webapp,所以我们才可以进行获取到相同的ServletContext对象,因为如果他们进行打包,都打到同一个war包里面了;
2)只要是在这个webapp中创建出来的Servlet对象,在两个Servlet都是可以通过req.getServletContext得到得到同一个上下文对象WebContext;
@WebServlet("/write")
//这个类负责向ServletContext中写数据
//浏览器通过一个形如/write?message=aaa访问到WriteServlet
//就把这个属性message=aaa这个键值对储存到ServletContext
public class WriteServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=utf-8");
//1从请求中获取到message参数
String flag=req.getParameter("message");
//2获取到当前的ServletContext对象
(ServletContext这个对象是Tomact在加载webapp的时候自动创建的)
我们就可以直接拿过来进行使用的
ServletContext servletContext= req.getServletContext();
//3 向ServletContext中写入键值对
servletContext.setAttribute("flag",flag);
//4 返回响应
resp.getWriter().write("<h3>储存message成功</h3>");
}
}
//用这个类来向我们的ServletContext中读取数据,这样就可以把刚才WriteServlet中储存的值读取出来 @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 servletContext= req.getServletContext(); //2从这里面来获取到刚才存得值 String result=(String)servletContext.getAttribute("flag"); resp.getWriter().write("flag"+result); } }
因此我们就可以通过listener来监听我们ServletContext的初始化完毕的动作ServletContext一旦创建好,就触发了监听器,执行我们自己所写的代码事件
@WebListener
public class Mylistener implements ServletContextListener {
//当ServletContext初始化之后,会立即执行这个方法
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
System.out.println("ServletContext已经进行了初始化,马上执行这个方法");
//1 获取到ServletContext这个对象
ServletContext servletContext=servletContextEvent.getServletContext();
servletContext.setAttribute("message","我爱我的家");
}
@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
}
}
1)创建一个新的类实现ServletContextListener接口,重写其中的contextInitialized方法,这个方法是在初始化完毕后被自动调用的;
2)给新的类加上一个@webListener注解,加上这个注解之后才可以被tomact识别出来并进行加载;
3)在方法里面获取ServletContext是通过ServletContextEvent对象来拿到的,后续就可以调用setAttribute()方法和getAttribute()方法来进行使用即可;
4)在这里面我们就可以把TemplateEngin的初始化操作就行了,还可以进行初始化一些其他的操作和内容,并存入到ServletContext这个键值对里面;
所以说我们就可以把Thymeleaf的初始化,放到contextInitialized方法里面,这样就可以保证TemplateEngine是一个webapp级别的单例
public class Mylistener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
//1 初始化TemplateEngine并创建实例,并获取到ServletContext对象(通过方法中的参数获取到)
ServletContext servletContext=servletContextEvent.getServletContext();
TemplateEngine templateEngine=new TemplateEngine();
//2 创建ServletContextTemplateResolver实例(模板解析器),并将两个类进行关联
ServletContextTemplateResolver servletContextTemplateResolver=new ServletContextTemplateResolver(servletContext);
servletContextTemplateResolver.setPrefix("/WEB-INF/template/");
servletContextTemplateResolver.setSuffix(".html");
servletContextTemplateResolver.setCharacterEncoding("utf-8");
templateEngine.setTemplateResolver(servletContextTemplateResolver);
//3把创建好的实例创建键值对放到ServletContext中,方便后面的Servlet进行获取,就不用在每一个Servlet里面进行初始化化操作了
servletContext.setAttribute("TemplateEngine",templateEngine);
System.out.println("当前webapp的TemplateEngine已经初始化完毕");
//4后面的Servlet如果想要获取到templateEngine这个对象,就可以用
//TemplateEngine templateEnginexx=(TemplateEngine) servletContext.getAttribute("TemplateEngine");
//后续就可以调用templateEngine.process方法,HTML文件,webContext;
}
//ServletContext被销毁之前,会自动地执行这个方法
@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
}
}
电话号码显示:
<!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>Document</title>
</head>
<body>
<ul>
<li th:each="person:${persons}">
<span th:text="${person.username}"></span>
<span th:text="${person.password}"></span>
<span th:text="${person.phoneNum}"></span>
<a th:href="${person.url}" th:text="${person.message}"></a>
</li>
</ul>
</body>
</html>
@WebServlet("/GetPhone")
public class PhoneServlet extends HttpServlet {
//1.创建模板引擎对象
TemplateEngine templateEngine=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");
templateEngine.setTemplateResolver(resolver);
}
static class Person{
public String username;
public String password;
public String phoneNum;
public String url;
public String message;
public Person(String username, String password, String phoneNum, String url, String message) {
this.username = username;
this.password = password;
this.phoneNum = phoneNum;
this.url = url;
this.message = message;
}
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
WebContext webContext=new WebContext(req,resp,this.getServletContext());
List<Person> list=new ArrayList<>();
Person person1=new Person("李佳伟","12503487","12345","http://www.baidu.com","百度官网");
Person person2=new Person("李佳鑫","34567","8989","http://www.sougou.com","搜狗官网");
Person person3=new Person("周云刚","9090","8788","http://www.baodu.com","百度官网");
list.add(person2);
list.add(person1);
list.add(person3);
resp.setContentType("text/html;charset=utf-8");
webContext.setVariable("persons",list);
String html=templateEngine.process("Phone",webContext);
resp.getWriter().write(html);
}
}
当我们进行访问GetPone这个路径的时候,我们就可以直接显示每一个人的电话号码了,因为我们后端给我们返回的是一个完整的HTML页面;
1)表白墙:
约定前后端交互的接口:
在前面我们已经写了纯前端的页面版本,在后来我们又写了一个web版本(前后端进行交互的版本,前后端通过ajax的方式来发送json数据;现在我们基于一个模板引擎来进行实现;
我们要引入模板文件,我们要在webapp中创建WEB-INF目录,在这个目录里面创建一个目录叫做template目录,把我们表白墙的前端页面添加到这个目录里面;
1)在最外面我们要加一个form标签(里面包裹input标签),里面设置action属性,表示要跳转到那个界面,我们也是主要通过form表单来提交请求,里面有三个name属性,我们直接可以通过表单提交
2)之前我们写前后端分离的表白墙项目的时候,三个标签里面的值在<script>里面提取出来,并将内容打包成AJAX的方式发送给服务器,但是在现在我们通过form表单来进行提交数据指定name属性,并把数据放到queryString里面,方便后端服务器来进行获取;
3)把JS中构造页面的片段代码去掉,换成模板引擎Thymeleaf的代码,也就是说不用再新创建元素添加到dom树上面了;
4)把原来的button按钮改成submit才可以进行提交,才可以进行发送请求;
5)实现服务器端的代码,内存(List)中保存消息的版本,文件中保存消息的版本,数据库中保存消息的版本;
5)使用监听器来进行初始化模板引擎
6)实现一个HelloWorldServlet这个类,来处理一个/HelloWorld路径这样的一个请求,分别用两个方法来实现,doGET请求用于获取消息列表,doPOST请求用于新增一个消息;
7)当用户点击提交按钮的时候,相当于给服务器发送了一个POST请求,当我们在浏览器上面输入一个URL的时候,就相当于给服务器发送了一个GET请求,就进行加载表白墙的页面;
8)我们在这里面写的doGET方法就是向List中获取里面的所有表白数据,doPOST方法就是客户端点击提交按钮之后,发送了一条表白数据,服务器收到数据之后,创建对应的对象,添加到list里面;
<div class="father">
<h1>表白墙</h1>
<p>输入点击后提交,结果将显示在墙上</p>
<form action="message" method="post">
<div class="line">
<span>谁</span><input type="text" name="from">
</div>
<div class="line">
<span>对谁说</span><input type="text" name="to">
</div>
<div class="line">
<span>说什么</span>
<input type="text" name="message">
</div>
<div class="but">
<input type="submit" value="提交" class="submit">
</div>
</form>
<!-- 添加模板这里的变量,每一个line都是表白墙上面的消息 -->
<div class="line" th:each="message:${messages}">
<span th:text="${message.from}"></span>对
<span th:text="${message.to}"></span>说
<span th:text="${message.message}"></span>
</div>
后端代码
1)处理请求的时候,也需要给请求的对象设置字符集,因为POST请求报文body格式是application/x-www......,所以说服务器在获取到请求中的内容也是URL-Encode的结果(不知道原来是啥样的),Servlet也是不知道这个encode的结果是按照UTF-8还是其他的字符集来进行编码的
2)也就是说服务器在拿到数据的时候是按照utf-8的格式来进行解析的,发送的时候也要设置成utf-8的形式
//先进行初始化TemplateEngine
@WebListener
public class Start implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
//1创建templateEngin对象,用于最终的渲染操作,并且根据这个方法的参数获取到ServletContext对象
TemplateEngine engine=new TemplateEngine();
ServletContext context=servletContextEvent.getServletContext();
//2创建模板解析器对象,进行设置前缀和后缀以及字符编码方式
ServletContextTemplateResolver resolver=new ServletContextTemplateResolver(context);
resolver.setPrefix("/WEB-INF/template/");
resolver.setSuffix(".html");
resolver.setCharacterEncoding("utf-8");
//3将汽车和加油器给进行关联到一起,将resolver对象和engine给关联到一起;
engine.setTemplateResolver(resolver);
//4向ServletContext中设置字符串,方便后面的Servlet进行使用的时候可以访问到TemplateEngine
context.setAttribute("engine",engine);
}
@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
}
}
//正式处理两个请求
class JsonData{
public String from;
public String to;
public String message;
}
@WebServlet("/message")
public class HelloServlet extends HttpServlet {
List<JsonData> list=new ArrayList<>();
//先进行初始化数据
@Override
public void init() throws ServletException {
JsonData jsonData=new JsonData();
jsonData.from="黑猫";
jsonData.to="白猫";
jsonData.message="喵";
list.add(jsonData);
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1先进行设置返回的字符集类型
resp.setContentType("text/html;charset=utf-8");
//2获取到一个web-app特有的ServletContext对象
ServletContext context=req.getServletContext();
//3取出之前进行初始化过的TemplateEngine对象
TemplateEngine engine=(TemplateEngine)context.getAttribute("engine");
//4创建WebContext对象,里面主要设置的是键值对
WebContext webContext=new WebContext(req,resp,context);
webContext.setVariable("messages",list);//相当于是返回一个数组
//5最终进行渲染操作
String html= engine.process("hello",webContext);
resp.getWriter().write(html);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//处理请求的内容,将读到的内容进行解析,得到to,from,message,并创建出JsonData对象,并将它插入到List里面
resp.setContentType("text/html;charset=utf-8");
req.setCharacterEncoding("utf-8");
//1先进行获取POST请求中的参数
String from=req.getParameter("from");
String to=req.getParameter("to");
String message=req.getParameter("message");
//2向list中添加数据
JsonData json=new JsonData();
json.from=from;
json.to=to;
json.message=message;
list.add(json);
//3进行模板渲染,进行返回
ServletContext context=req.getServletContext();
TemplateEngine engine=(TemplateEngine)context.getAttribute("engine");
WebContext webContext=new WebContext(req,resp,context);
webContext.setVariable("messages",list);
String html=engine.process("hello",webContext);
resp.getWriter().write(html);
}
}
1)当我们触发GET请求的时候,返回的响应时一个进行渲染后的HTML界面,返回的报文Content-Type:text/html,charset=utf-8;
2)这是当我们写了一条表白数据并提交之后,客户端给服务器发送的POST请求,显然他发送的报文格式是一个个的键值对,所以请求类型就是Content-Type:application/x-www-form-urlencoded;
响应报文是这样的
HTTP/1.1 200
Content-Type: text/html;charset=utf-8
Content-Length: 2566
Date: Fri, 01 Jul 2022 13:03:57 GMT
Keep-Alive: timeout=20
Connection: keep-alive
<div class="father">
<h1>表白墙</h1>
<p>输入点击后提交,结果将显示在墙上</p>
<form action="message" method="post">
<div class="line">
<span>谁</span><input type="text" name="from">
</div>
<div class="line">
<span>对谁说</span><input type="text" name="to">
</div>
<div class="line">
<span>说什么</span>
<input type="text" name="message">
</div>
<div class="but">
<input type="submit" value="提交" class="submit">
</div>
</form>
<!-- 添加模板这里的变量,每一个line都是表白墙上面的消息 -->
<div class="line">
<span>黑猫</span>对
<span>白猫</span>说
<span>喵</span>
</div>
<div class="line">
<span>黑狗</span>对
<span>母狗</span>说
<span>汪汪</span>
</div>
<div class="line">
<span>黑猫</span>对
<span>白猫</span>说
<span>呵呵</span>
</div>
<div class="line">
<span>李嘉欣</span>对
<span>李佳伟</span>说
<span>大哥</span>
</div>
</body>
2)在线相册项目:
1)我们在这里面要注意目录结构:webapp里面存放image目录,里面包含着若干张图片webapp/WEB-INF里面含有这需要进行渲染的原HTML代码文件,以及有它同级的CSS文件
2)我们在这里面只需写出后端代码即可(CSS样式不需要进行关心,和我们的后端代码没有任何关系),我们只需要写出服务器的代码即可,我们此处指需要进行关心的是我们要给前端页面通过模板引擎传入一个images数组,images数组里面的每一个元素是一个image对象,里面包含name,src,href三个属性;
3)这里面的关键要点:我们使用th:each来分别进行构造多个figure标签,最后把URL和name放到合适的位置上
4)初始化模板引擎,我们也是基于listener来进行实现的,在ServletContext中创建出一个TemplateEngine实例,以备后用;
5)写一个Servlet后端代码,写一个ImageServlet来进行展示图片界面,在这个后端代码里面,就要生成一个Image对象的数组,传给网页模板,并进行返回;
6)生成images数组的方法,给定一个指定目录,我们就需要从这个目录来进行扫描,看看都有哪些图片,根据目录中的图片信息来进行构建出一个image对象的数组
我们在这里面给的指定目录就是/WEB-INF/image目录,看看有多少个图片构成List<Image>
1)我们需要扫描文件路径,我们再进行扫描的时候,如何根据webapp中的image目录得到磁盘目录呢;
我们就需要进行调用String path=context.getRealPath("/image"),这是把web-app中的路径,转换成在磁盘上面的文件路径;
2)根据这个路径再扫描的过程中,看看有哪些是图片文件;
3)上传图片页面:支持我们用户自己上传图片,我们先自己写一个upload.html,再写一个UploadServlet来进行处理上传请求;
1)upload.html只是一个普通的静态页面,不用放到/WEB-INF/template里面,纯纯的静态页面应该放到webapp里面下的目录
2)如果说直接访问模板引擎的页面直接进行访问127.0.0.1:8080/Java200/Java后端代码@webServlet修饰的注解
后端代码:
@WebServlet("/GetImage")
public class ImageServlet extends HttpServlet {
static class Image{
public String name;
public String url;
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1.我们需要扫描指定的目录 /webapp/image路径,我们想看看有多少图片,构成了List<Image>
List<Image> list=LoadImage();
//2.把list构造到模板页面里面
//2.1先进行获取TemplateEngine对象
TemplateEngine templateEngine=(TemplateEngine) this.getServletContext().getAttribute("TemplateEngine");
//2.2进行模板替换
WebContext webContext=new WebContext(req,resp,this.getServletContext());
webContext.setVariable("images",list);
//3.最终进行模板渲染操作
String html= templateEngine.process("Image",webContext);
resp.setContentType("text/html;charset=utf-8");
resp.getWriter().write(html);
}
private List<Image> LoadImage() {
//1.我们需要进行扫描/webapp/Image目录来进行扫描,看看有多少张图片,构成了List<Image>
List<Image> list=new ArrayList<>();
//2.我们需要进行获取到/webapp/Image这个Idea中的目录在磁盘上面的路径
ServletContext context=this.getServletContext();
String path=context.getRealPath("/Image");
System.out.println(path);
//3.这个操作就是把咱们的一个webapp上的路径转化成磁盘上面的路径
//4.我们根据这个路径来进行查看一下有哪些图片文件
File file=new File(path);
File[] files=file.listFiles();
System.out.println(files);
for(File f:files)
{
Image image=new Image();
System.out.println(f.getName());
image.name=f.getName();
image.url="Image/"+f.getName();//相对路径
System.out.println(image.url);
list.add(image);
}
return list;
}
}
前端代码:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>相册</title>
<link rel="stylesheet" href="./style.css">
</head>
<body>
<!--我们主要是通过th:each的方式来进行循环生成多个figure标签,每一个标签就对应着一个图片-->
<figure class="sample" th:each="image:${images}">
<img th:src="${image.url}" alt="sample1" />
<figcaption>
<div>
<h2 th:text="${image.name}"></h2>
</div>
</figcaption>
<a th:href="${image.url}" th:text="${image.name}"></a>
</figure>
</body>
</html>