这部分源码:https://github.com/yupengtao1024/JavaWeb2025.git

本篇提前使用maven进行依赖管理

1. 我们为什么需要Servlet?

Web服务器中Servlet作用举例

  • 举例一:插入数据

  • 举例二:查询数据

2. 什么是Servlet?

如果把Web应用比作一个餐厅,Servlet就是餐厅中的服务员——负责接待顾客、上菜、结账。

1681544428055
  • 从广义上来讲,Servlet规范是Sun公司制定的一套技术标准,包含与Web应用相关的一系列接口,而具体的Servlet容器负责提供标准的实现。
  • 从狭义上来讲,Servlet指的是javax.servlet.Servlet接口及其子接口,也可以指实现了Servlet接口的实现类。
  • Servlet(Server Applet)作为服务器端的一个组件,它的本意是“服务器端的小程序”。
    • Servlet的实例对象由Servlet容器负责创建;
    • Servlet的方法由容器在特定情况下调用;
    • Servlet容器会在Web应用卸载时销毁Servlet对象的实例。

3. 如何使用Servlet?

1. 案例— 使用servlet获取请求参数

  • 使用Servlet接口的方式:
    ① 搭建Web开发环境
    ② 创建动态Web工程

image-20250305112235501

③ 依赖pom.xml

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
<?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>groupId</groupId>
<artifactId>HelloServlet</artifactId>
<version>1.0-SNAPSHOT</version>

<properties>
<maven.compiler.source>23</maven.compiler.source>
<maven.compiler.target>23</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
<!-- https://mvnrepository.com/artifact/jakarta.servlet/jakarta.servlet-api -->
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>6.1.0</version>
<scope>provided</scope>
</dependency>
</dependencies>

</project>

④页面index.html
使用servlet的页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="fruit" method="post">
<table>
名称:<input type="text" name="fname"/><br/>
价格:<input type="text" name="price"/><br/>
库存:<input type="text" name="fcount"/><br/>
备注:<input type="text" name="remark"/><br/>
<input type="submit" value="添加"/><br/>
</table>
</form>
</body>
</html>

⑤ 在web.xml配置文件中注册FruitServlet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
version="6.0">
<servlet>
<servlet-name>HelloServlet</servlet-name>
<servlet-class>com.this0.servlet.FruitServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>HelloServlet</servlet-name>
<url-pattern>/fruit</url-pattern>
</servlet-mapping>
</web-app>

说明:

  • :这个url-pattern可以配置多个,这时表示的就是访问这些url都会触发这个Servlet进行响应,运行浏览器,访问刚才配置的url路径,Servlet的service方法就会被调用。
  • 中的文本内容必须以 / 或 *. 开始书写路径。相当于将资源映射到项目根目录下形成虚拟的资源文件。
  • 中的可以声明多个,可以通过任意一个都可以访问。但是开发中一般只会配置一个。

找到注册的servlet

⑥FruitServlet
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
package com.this0.servlet;

import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;

public class FruitServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {


String fname = req.getParameter("fname");
Integer price = Integer.valueOf(req.getParameter("price"));
Integer fcount = Integer.valueOf(req.getParameter("fcount"));
String remark = req.getParameter("remark");

System.out.println("fname=" + fname);
System.out.println("price=" + price);
System.out.println("fcount=" + fcount);
System.out.println("remark=" + remark);
}
}

2. 运行分析(执行原理)

  • index.html :通过页面找web.xml里的配置参数
  • web.xml:通过配置里的url-pattern找到使用的类,然后调用该类
  • 如果配置文件一旦修改,需要重启服务器来重新部署web项目。

3. Servlet作用总结

  • 接收请求 【解析请求报文中的数据:请求参数】
  • 处理请求 【DAO和数据库交互】
  • 完成响应 【设置响应报文】

4. 注解方式配置servlet

4. servlet的生命周期

1. Servlet生命周期概述

简单的叙述生命周期,就是对象在容器中从开始创建到销毁的过程。

2. Servlet生命周期的主要过程

生命周期 对应方法 执行时机 执行次数
构造对象 构造器 第一次请求或者容器启动 1
初始化 init() 构造完毕后 1
处理服务 service(HttpServletRequest req,HttpServletResponse res) 每次请求 多次
销毁 destory() 容器关闭 1
① Servlet对象的创建:构造器
  • 默认情况下,Servlet容器第一次收到HTTP请求时创建对应Servlet对象。
  • 容器之所以能做到这一点是由于我们在注册Servlet时提供了全类名,容器使用反射技术创建了Servlet的对象。
② Servlet对象初始化:init()
  • Servlet容器创建Servlet对象之后,会调用init(ServletConfig config)方法。
  • 作用:在Servlet对象创建后,执行一些初始化操作。例如,读取一些资源文件、配置文件,或建立某种连接(比如:数据库连接)
  • init()方法只在创建对象时执行一次,以后再接到请求时,就不执行了
③ 处理请求:service()
  • service(ServletRequest req, ServletResponse res)方法处理HTTP请求。
  • 在每次接到请求后都会执行。
  • Servlet的作用,主要在此方法中体现。
  • 同时要求容器将ServletRequest对象和ServletResponse对象传入。
④ Servlet对象销毁:destroy()
  • 服务器重启、服务器停止执行或Web应用卸载时会销毁Servlet对象,会调用public void destroy()方法。
  • 此方法用于销毁之前执行一些诸如释放缓存、关闭连接、保存内存数据持久化等操作。

3. Servlet请求过程

  • 第一次请求执行:
    • 调用构造器,创建对象
    • 执行init()方法
    • 执行service()方法
  • 后面请求执行:
    • 执行service()方法
  • 对象销毁前
    • 执行destroy()方法

4. 代码示例

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
package com.this0.servlet;

import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;

import java.io.IOException;

@WebServlet(value = "/servletLifeCycle", loadOnStartup = 1)
public class ServletLifeCycle extends HttpServlet {

public ServletLifeCycle() {
System.out.println("构造器");
}

@Override
public void init() throws ServletException {
System.out.println("初始化");
}

@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
System.out.println("服务");
}

@Override
public void destroy() {
System.out.println("销毁");
}
}

5. 生命周期总结

  1. 通过生命周期测试我们发现Servlet对象在容器中是单例的,多个线程可能会使用相同的Servlet对象,所以在Servlet中,我们不要轻易定义一些容易经常发生修改的成员变量
  2. load-on-startup中定义的正整数表示实例化顺序,如果数字重复了,容器会自行解决实例化顺序问题,但是应该避免重复
  3. Tomcat容器中,已经定义了一些随系统启动实例化的servlet,我们自定义的servlet的load-on-startup尽量不要占用数字1-5

5. servlet继承结构

idea按 ctrl + H 查看继承关系

image-20250307014731137

1. Servlet 接口相关方法

  • Servlet 规范接口,所有的Servlet必须实现
    • public void init(ServletConfig config) throws ServletException;
      • 初始化方法,容器在构造servlet对象后,自动调用的方法,容器负责实例化一个ServletConfig对象,并在调用该方法时传入
      • ServletConfig对象可以为Servlet 提供初始化参数
    • public ServletConfig getServletConfig();
      • 获取ServletConfig对象的方法,后续可以通过该对象获取Servlet初始化参数
    • public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;
      • 处理请求并做出响应的服务方法,每次请求产生时由容器调用
      • 容器创建一个ServletRequest对象和ServletResponse对象,容器在调用service方法时,传入这两个对象
    • public String getServletInfo();
      • 获取ServletInfo信息的方法
    • public void destroy();
      • Servlet实例在销毁之前调用的方法

2. GenericServlet 抽象类

  • GenericServlet 抽象类是对Servlet接口一些固定功能的粗糙实现,以及对service方法的再次抽象声明,并定义了一些其他相关功能方法
    • private transient ServletConfig config;
      • 初始化配置对象作为属性
    • public GenericServlet() { }
      • 构造器,为了满足继承而准备
    • public void destroy() { }
      • 销毁方法的平庸实现
    • public String getInitParameter(String name)
      • 获取初始参数的快捷方法
    • public Enumeration getInitParameterNames()
      • 返回所有初始化参数名的方法
    • public ServletConfig getServletConfig()
      • 获取初始Servlet初始配置对象ServletConfig的方法
    • public ServletContext getServletContext()
      • 获取上下文对象ServletContext的方法
    • public String getServletInfo()
      • 获取Servlet信息的平庸实现
    • public void init(ServletConfig config) throws ServletException()
      • 初始化方法的实现,并在此调用了init的重载方法
    • public void init() throws ServletException
      • 重载init方法,为了让我们自己定义初始化功能的方法
    • public void log(String msg)
    • public void log(String message, Throwable t)
      • 打印日志的方法及重载
    • public abstract void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;
      • 服务方法再次声明
    • public String getServletName()
      • 获取ServletName的方法

3. HttpServlet 抽象类

abstract class HttpServlet extends GenericServlet。

HttpServlet抽象类,除了基本的实现以外,增加了更多的基础功能

  • private static final String METHOD_DELETE = “DELETE”;

  • private static final String METHOD_HEAD = “HEAD”;

    ······

    • 上述属性用于定义常见请求方式名常量值
  • public HttpServlet() {}

    • 构造器,用于处理继承
  • public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException

    • 对服务方法的实现
    • 在该方法中,将请求和响应对象转换成对应HTTP协议的HttpServletRequest HttpServletResponse对象
    • 调用重载的service方法
  • public void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException

    • 重载的service方法,被重写的service方法所调用
    • 在该方法中,通过请求方式判断,调用具体的do***方法完成请求的处理
  • protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException

    ······

  • protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException

  • protected void doTrace(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException

    • 对应不同请求方式的处理方法
    • 除了doOptions和doTrace方法,其他的do*** 方法都在故意响应错误信息

4. 自定义Servlet

继承关系图解

1682299663047

  • 自定义Servlet中,必须要对处理请求的方法进行重写
    • 要么重写service方法
    • 要么重写doGet/doPost方法

5. 小结

  1. 继承关系: HttpServlet -> GenericServlet -> Servlet
  2. Servlet中的核心方法: init() , service() , destroy()
  3. 服务方法: 当有请求过来时,service方法会自动响应(其实是tomcat容器调用的)
    在HttpServlet中我们会去分析请求的方式:到底是get、post、head还是delete等等
    然后再决定调用的是哪个do开头的方法
    那么在HttpServlet中这些do方法默认都是405的实现风格-要我们子类去实现对应的方法,否则默认会报405错误
  4. 因此,我们在新建Servlet时,我们才会去考虑请求方法,从而决定重写哪个do方法

6. ServletConfig和ServletContext

1 ServletConfig的使用

ServletConfig是什么

  • 为Servlet提供初始配置参数的一种对象,每个Servlet都有自己独立唯一的ServletConfig对象
  • 容器会为每个Servlet实例化一个ServletConfig对象,并通过Servlet生命周期的init方法传入给Servlet作为属性
1682302307081

ServletConfig是一个接口,定义了如下API

方法名 作用
getServletName() 获取<servlet-name>HelloServlet</servlet-name>定义的Servlet名称
getServletContext() 获取ServletContext对象
getInitParameter() 获取配置Servlet时设置的『初始化参数』,根据名字获取值
getInitParameterNames() 获取所有初始化参数名组成的Enumeration对象

2 ServletContext的使用

ServletContext是什么

  • ServletContext对象又称呼为上下文对象,或者叫应用域对象
  • 容器会为每个app创建一个独立的唯一的ServletContext对象
  • ServletContext对象为所有的Servlet所共享
  • ServletContext可以为所有的Servlet提供初始配置参数

1682303205351

3 ServletContext其他重要API

获取资源的真实路径

1
String realPath = servletContext.getRealPath("资源在web目录中的路径");
  • 例如我们的目标是需要获取项目中某个静态资源的路径,不是工程目录中的路径,而是部署目录中的路径;只要使用了servletContext动态获取资源的真实路径,那么无论项目的部署路径发生什么变化,都会动态获取项目运行时候的实际路径

获取项目的上下文路径

1
String contextPath = servletContext.getContextPath();
  • 项目的部署名称,也叫项目的上下文路径,在部署进入tomcat时所使用 的路径,该路径是可能发生变化的,通过该API动态获取项目真实的上下文路径,可以帮助我们解决一些后端页面渲染技术或者请求转发和响应重定向中的路径问题

域对象的相关API

  • 域对象: 一些用于存储数据和传递数据的对象,传递数据不同的范围,我们称之为不同的域
  • ServletContext代表应用,所以ServletContext域也叫作应用域,是webapp中最大的域,可以在本应用内实现数据的共享和传递
  • webapp中的三大域对象,分别是应用域,会话域,请求域
  • 三大域对象都具有的API如下
API 功能解释
void setAttribute(String key,Object value); 向域中存储/修改数据
Object getAttribute(String key); 获得域中的数据
void removeAttribute(String key); 移除域中的数据

7. HttpServletRequest

1. HttpServletRequest简介

HttpServletRequest是什么

  • HttpServletRequest是一个接口,其父接口是ServletRequest
  • HttpServletRequest是Tomcat将请求报文转换封装而来的对象,在Tomcat调用service方法时传入
  • HttpServletRequest代表客户端发来的请求,所有请求中的信息都可以通过该对象获得

1681699577344

2. HttpServletRequest常见API

HttpServletRequest怎么用

  • 获取请求行信息相关(方式,请求的url,协议及版本)
API 功能解释
StringBuffer getRequestURL(); 获取客户端请求的url
String getRequestURI(); 获取客户端请求项目中的具体资源
int getServerPort(); 获取客户端发送请求时的端口
int getLocalPort(); 获取本应用在所在容器的端口
int getRemotePort(); 获取客户端程序的端口
String getScheme(); 获取请求协议
String getProtocol(); 获取请求协议及版本号
String getMethod(); 获取请求方式
  • 获得请求头信息相关
API 功能解释
String getHeader(String headerName); 根据头名称获取请求头
Enumeration getHeaderNames(); 获取所有的请求头名字
String getContentType(); 获取content-type请求头
  • 获得请求参数相关
API 功能解释
String getParameter(String parameterName); 根据请求参数名获取请求单个参数值
String[] getParameterValues(String parameterName); 根据请求参数名获取请求多个参数值数组
Enumeration getParameterNames(); 获取所有请求参数名
Map<String, String[]> getParameterMap(); 获取所有请求参数的键值对集合
BufferedReader getReader() throws IOException; 获取读取请求体的字符输入流
ServletInputStream getInputStream() throws IOException; 获取读取请求体的字节输入流
int getContentLength(); 获得请求体长度的字节数
  • 其他API
API 功能解释
String getServletPath(); 获取请求的Servlet的映射路径
ServletContext getServletContext(); 获取ServletContext对象
Cookie[] getCookies(); 获取请求中的所有cookie
HttpSession getSession(); 获取Session对象
void setCharacterEncoding(String encoding) ; 设置请求体字符集

8. HttpServletResponse

1 HttpServletResponse简介

HttpServletResponse是什么

  • HttpServletResponse是一个接口,其父接口是ServletResponse
  • HttpServletResponse是Tomcat预先创建的,在Tomcat调用service方法时传入
  • HttpServletResponse代表对客户端的响应,该对象会被转换成响应的报文发送给客户端,通过该对象我们可以设置响应信息

1681699577344

2 HttpServletResponse的常见API

HttpServletRequest怎么用

  • 设置响应行相关
API 功能解释
void setStatus(int code); 设置响应状态码
  • 设置响应头相关
API 功能解释
void setHeader(String headerName, String headerValue); 设置/修改响应头键值对
void setContentType(String contentType); 设置content-type响应头及响应字符集(设置MIME类型)
  • 设置响应体相关
API 功能解释
PrintWriter getWriter() throws IOException; 获得向响应体放入信息的字符输出流
ServletOutputStream getOutputStream() throws IOException; 获得向响应体放入信息的字节输出流
void setContentLength(int length); 设置响应体的字节长度,其实就是在设置content-length响应头
  • 其他API
API 功能解释
void sendError(int code, String message) throws IOException; 向客户端响应错误信息的方法,需要指定响应码和响应信息
void addCookie(Cookie cookie); 向响应体中增加cookie
void setCharacterEncoding(String encoding); 设置响应体字符集

MIME类型

  • MIME类型,可以理解为文档类型,用户表示传递的数据是属于什么类型的文档
  • 浏览器可以根据MIME类型决定该用什么样的方式解析接收到的响应体数据
  • 可以这样理解: 前后端交互数据时,告诉对方发给对方的是 html/css/js/图片/声音/视频/… …
  • tomcat/conf/web.xml中配置了常见文件的拓展名和MIMIE类型的对应关系
  • 常见的MIME类型举例如下
文件拓展名 MIME类型
.html text/html
.css text/css
.js application/javascript
.png /.jpeg/.jpg/… … image/jpeg
.mp3/.mpe/.mpeg/ … … audio/mpeg
.mp4 video/mp4
.m1v/.m1v/.m2v/.mpe/… … video/mpeg

9. 请求转发和响应重定向

1 概述

什么是请求转发和响应重定向

  • 请求转发和响应重定向是web应用中间接访问项目资源的两种手段,也是Servlet控制页面跳转的两种手段

  • 请求转发通过HttpServletRequest实现,响应重定向通过HttpServletResponse实现

2 请求转发

请求转发运行逻辑图

1682321228643

请求转发特点

  • 请求转发通过HttpServletRequest对象获取请求转发器实现
  • 请求转发是服务器内部的行为,对客户端是屏蔽的
  • 客户端只发送了一次请求,客户端地址栏不变
  • 服务端只产生了一对请求和响应对象,这一对请求和响应对象会继续传递给下一个资源
  • 因为全程只有一个HttpServletRequset对象,所以请求参数可以传递,请求域中的数据也可以传递
  • 请求转发可以转发给其他Servlet动态资源,也可以转发给一些静态资源以实现页面跳转
  • 请求转发可以转发给WEB-INF下受保护的资源
  • 请求转发不能转发到本项目以外的外部资源

请求转发测试代码

image-20250311235317897

  • TestForward
1
2
3
4
5
6
7
8
9
10
11
12
@WebServlet(name = "TestForward", value = "/testForward")
public class TestForward extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

System.out.println("内部转发......");
System.out.println("转发到servletLifeCycle......");

req.getRequestDispatcher("/servletLifeCycle").forward(req, resp);
}
}

3 响应重定向

响应重定向运行逻辑图

1682322460011

响应重定向特点

  • 响应重定向通过HttpServletResponse对象的sendRedirect方法实现
  • 响应重定向是服务端通过302响应码和路径,告诉客户端自己去找其他资源,是在服务端提示下的,客户端的行为
  • 客户端至少发送了两次请求,客户端地址栏是要变化的
  • 服务端产生了多对请求和响应对象,且请求和响应对象不会传递给下一个资源
  • 因为全程产生了多个HttpServletRequset对象,所以请求参数不可以传递,请求域中的数据也不可以传递
  • 重定向可以是其他Servlet动态资源,也可以是一些静态资源以实现页面跳转
  • 重定向不可以到给WEB-INF下受保护的资源
  • 重定向可以到本项目以外的外部资源

响应重定向测试代码

  • TestSendRedirect
1
2
3
4
5
6
7
8
9
@WebServlet(name = "TestSendRedirect", value = "/testSendRedirect")
public class TestSendRedirect extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

System.out.println("重定向到servletLifeCycle......");
resp.sendRedirect("/hello/servletLifeCycle");
}
}

10. 控制台乱码问题

乱码问题产生的根本原因是什么

  1. 数据的写入和读取使用的不是同一个字符集
  2. 使用了不支持某个语言文字的字符集
  3. 如果中文乱码中,出现了带有中文痕迹的乱码,那说明都使用了支持中文的不同字符集
  4. 如果中文乱码中,出现了没有中文痕迹的乱码,那说明可能是使用了不支持中文字符集

各个字符集的兼容性

1682326867396
  • 由上图得知,上述字符集都兼容了ASCII
  • ASCII中有什么? 英文字母和一些通常使用的符号,所以这些东西无论使用什么字符集都不会乱码

Tomcat控制台乱码

在tomcat10.1.7这个版本中,修改 tomcat/conf/logging.properties中,所有的UTF-8为GBK即可

  • 修改前
1681443202115
  • 修改后
1681443273573
  • 重启测试

11. 会话跟踪技术

1 为什么需要会话管理

HTTP是无状态协议

  • 无状态就是不保存状态,即无状态协议(stateless),HTTP协议自身不对请求和响应之间的通信状态进行保存,也就是说,在HTTP协议这个级别,协议对于发送过的请求或者响应都不做持久化处理

2 会话管理实现的手段

Cookie和Session配合解决

  • cookie是在客户端保留少量数据的技术,主要通过响应头向客户端响应一些客户端要保留的信息
  • session是在服务端保留更多数据的技术,主要通过HttpSession对象保存一些和客户端相关的信息
  • cookie和session配合记录请求状态
1 Cookie概述

cookie是一种客户端会话技术,cookie由服务端产生,它是服务器存放在浏览器的一小份数据,浏览器以后每次访问该服务器的时候都会将这小份数据携带到服务器去。

  • 服务端创建cookie,将cookie放入响应对象中,Tomcat容器将cookie转化为set-cookie响应头,响应给客户端
  • 客户端在收到cookie的响应头时,在下次请求该服务的资源时,会以cookie请求头的形式携带之前收到的Cookie
  • cookie是一种键值对格式的数据,从tomcat8.5开始可以保存中文,但是不推荐
  • 由于cookie是存储于客户端的数据,比较容易暴露,一般不存储一些敏感或者影响安全的数据

原理图

1682411089082

应用场景举例

  1. 记录用户名

    当我们在用户名的输入框中输入完用户名后,浏览器记录用户名,下一次再访问登录页面时,用户名自动填充到用户名的输入框.

  2. 保存电影播放进度

    在网页上播放电影的时候,如果中途退出浏览器了,下载再打开浏览器播放同一部电影的时候,会自动跳转到上次退出时候的进度,因为在播放的时候会将播放进度保存到cookie中

2 Cookie的使用

向响应中增加Cookie

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@WebServlet("/createCookie")
public class createCookie extends HttpServlet {

@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

//创建cookie
Cookie cookie1 = new Cookie("c1", "c1_value");
Cookie cookie2 = new Cookie("c2", "c2_value");

//将cookie放入响应对象
resp.addCookie(cookie1);
resp.addCookie(cookie2);
}
}

image-20250312225016517

从请求中读取Cookie

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@WebServlet("/getCookie")
public class getCookie extends HttpServlet {

@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

//获取请求中的cookie
Cookie[] cookies = req.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
System.out.println(cookie.getName() + "=" + cookie.getValue());
}
}
}
}

image-20250312231243726

3 Cookie的时效性

默认情况下Cookie的有效期是一次会话范围内,我们可以通过cookie的setMaxAge()方法让Cookie持久化保存到浏览器上

  • 会话级Cookie
    • 服务器端并没有明确指定Cookie的存在时间
    • 在浏览器端,Cookie数据存在于内存中
    • 只要浏览器还开着,Cookie数据就一直都在
    • 浏览器关闭,内存中的Cookie数据就会被释放
  • 持久化Cookie
    • 服务器端明确设置了Cookie的存在时间
    • 在浏览器端,Cookie数据会被保存到硬盘上
    • Cookie在硬盘上存在的时间根据服务器端限定的时间来管控,不受浏览器关闭的影响
    • 持久化Cookie到达了预设的时间会被释放

cookie.setMaxAge(int expiry)参数单位是秒,表示cookie的持久化时间,如果设置参数为0,表示将浏览器中保存的该cookie删除

  • 设置一个Cookie为持久化cookie
1
2
3
4
5
6
7
8
9
10
11
12
@WebServlet("/saveCookie")
public class saveCookie extends HttpServlet {

@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

Cookie cookie = new Cookie("c2", "new_c2_value");
cookie.setMaxAge(60);

resp.addCookie(cookie);
}
}

image-20250313172224736

4 Cookie的提交路径

访问互联网资源时不能每次都需要把所有Cookie带上。访问不同的资源时,可以携带不同的cookie,我们可以通过cookie的setPath(String path) 对cookie的路径进行设置

  • 设置带路径的cookie
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@WebServlet("/cookiePath")
public class cookiePath extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

// cookie1设置提交路径
Cookie cookie1 = new Cookie("c1", "c1_message");
cookie1.setPath("/path/cookiePath");

//cookie2不设置路径
Cookie cookie2 = new Cookie("c2", "c2_message");

resp.addCookie(cookie1);
resp.addCookie(cookie2);
}
}

image-20250313174718313

  • 向其他资源请求时就不携带c1了

image-20250313175025404

4 Session

1 HttpSession概述

HttpSession是一种保留更多信息在服务端的一种技术,服务器会为每一个客户端开辟一块内存空间,即session对象. 客户端在发送请求时,都可以使用自己的session. 这样服务端就可以通过session来记录某个客户端的状态了

  • 服务端在为客户端创建session时,会同时将session对象的id,即JSESSIONID以cookie的形式放入响应对象
  • 后端创建完session后,客户端会收到一个特殊的cookie,叫做JSESSIONID
  • 客户端下一次请求时携带JSESSIONID,后端收到后,根据JSESSIONID找到对应的session对象
  • 通过该机制,服务端通过session就可以存储一些专门针对某个客户端的信息了
  • session也是域对象

原理图如下

1682413051408

应用场景

  1. 记录用户的登录状态

    用户登录后,将用户的账号等敏感信息存入session

  2. 记录用户操作的历史

    例如记录用户的访问痕迹,用户的购物车信息等临时性的信息

2 HttpSession的使用

用户提交form表单到loginServlet,携带用户名和密码,loginServlet获取session 将用户名和密码存到Session,用户再请求其他任意Servlet,能获取之间存储的用户名和密码

  • 定义表单页,提交用户名,提交后
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="login" method="post">
用户:
<input type="text" name="username">
密码:
<input type="password" name="password">
<input type="submit" value="提交">
</form>
</body>
</html>
  • 定义loginServlet,将用户名存入session
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("/login")
public class loginServlet extends HttpServlet {

@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

String username = req.getParameter("username");
String password = req.getParameter("password");

//获取session对象
HttpSession session = req.getSession();

//获取Session的ID
String jSessionId = session.getId();
System.out.println(jSessionId);

//判断session是不是新创建的session
boolean isNew = session.isNew();
System.out.println(isNew);

if (username != null && password != null && username.equals("admin") && password.equals("admin")) {
session.setAttribute("username", username);
session.setAttribute("password", password);
System.out.println("登录成功,数据存到了session中");
}
}
}
  • 响应中收到了一个JSESSIONID的cookie

image-20250313234346721

  • 定义其他servlet,从session中读取用户名
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
@WebServlet("/getSession")
public class getSession extends HttpServlet {

@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

HttpSession session = req.getSession();

//获取jSessionId
String jSessionId = session.getId();
System.out.println(jSessionId);

//是不是新session
boolean isNew = session.isNew();
System.out.println(isNew);

//从session中取出数据
Object username = session.getAttribute("username");
Object password = session.getAttribute("password");

System.out.println(username);
System.out.println(password);
}
}

  • 请求中携带了一个JSESSIONID的cookie

image-20250313235324199

getSession方法的处理逻辑

1682477914654

常用的API:
request.getSession() -> 获取当前的会话,没有则创建一个新的会话
request.getSession(true) -> 效果和不带参数相同
`request.getSession(false) -> 获取当前会话,没有则返回null,不会创建新的

3 HttpSession时效性

为什么要设置session的时效

  • 用户量很大之后,Session对象相应的也要创建很多。如果一味创建不释放,那么服务器端的内存迟早要被耗尽。
  • 客户端关闭行为无法被服务端直接侦测,或者客户端较长时间不操作也经常出现,类似这些的情况,就需要对session的时限进行设置了

默认的session最大闲置时间(两次使用同一个session中的间隔时间) 在tomcat/conf/web.xml配置为30分钟

1682478412527

我们可以自己在当前项目的web.xml对最大闲置时间进行重新设定

1682478633650

也可以通过HttpSession的API 对最大闲置时间进行设定

1
2
// 设置最大闲置时间
session.setMaxInactiveInterval(60);

也可以直接让session失效

1
2
// 直接让session失效
session.invalidate();

如果禁用了 Cookies,会话仍然可以存储在服务器端,但服务器无法直接将 Session ID 发送给客户端。
在禁用 Cookies 的情况下,可以使用其他方式来传递 Session ID。以下是一些可能的替代方法:

  1. URL 重写:服务器可以通过在每个链接和表单中添加 Session ID 的查询参数或路径参数来传递 Session ID。这样,服务器可以通过解析请求中的 URL 来识别用户的会话。
  2. 隐藏表单字段:在包含表单的页面中,可以将 Session ID 添加为一个隐藏的表单字段,并在每次提交表单时将其发送回服务器。
  3. HTTP 头:服务器可以通过自定义的 HTTP 头将 Session ID 发送回客户端。例如,可以在响应中包含一个名为 “X-Session-ID” 的头部,并在后续请求中将该头部包含在请求中

12. 三大域对象

1. 域对象概述

域对象: 一些用于存储数据和传递数据的对象,不同的域对象代表不同的域,共享数据的范围也不同

  • web项目中,我们一定要熟练使用的域对象分别是 请求域,会话域,应用域
  • 请求域对象是HttpServletRequest ,传递数据的范围是一次请求之内及请求转发
  • 会话域对象是HttpSession,传递数据的范围是一次会话之内,可以跨多个请求
  • 应用域对象是ServletContext,传递数据的范围是本应用之内,可以跨多个会话

三大域对象的数据作用范围图解

  • 请求域
1682480592506
  • 会话域
1682480716152
  • 应用域
1682480913847
  • 所有域在一起
1682488186891

2. 域对象的使用

域对象的API

API 功能
void setAttribute(String name,String value) 向域对象中添加/修改数据
Object getAttribute(String name); 从域对象中获取数据
removeAttribute(String name); 移除域对象中的数据

API测试

  • 测试代码见仓库
    image-20250314045001295

  • 请求转发时,请求域可以传递数据请求域内一般放本次业务有关的数据,如:查询到的所有的部门信息

  • 同一个会话内,不用请求转发,会话域可以传递数据会话域内一般放本次会话的客户端有关的数据,如:当前客户端登录的用户

  • 同一个APP内,不同的客户端,应用域可以传递数据应用域内一般放本程序应用有关的数据 如:Spring框架的IOC容器

13. 过滤器

1 过滤器概述

Filter,即过滤器,是对目标资源的请求进行过滤的一套技术规范,是Java Web项目中最为实用的技术之一

  • Filter的工作位置是项目中所有目标资源之前,容器在创建HttpServletRequest和HttpServletResponse对象后,会先调用Filter的doFilter方法
  • Filter的doFilter方法可以控制请求是否继续,如果放行,则请求继续,如果拒绝,则请求到此为止,由过滤器本身做出响应
  • Filter不仅可以对请求做出过滤,也可以在目标资源做出响应前,对响应再次进行处理
  • Filter的常用应用包括但不限于: 登录权限检查,解决网站乱码,过滤敏感字符,日志记录,性能分析… …

生活举例: 公司前台

  • 公司前台对来访人员进行审核,如果是游客则拒绝进入公司,如果是客户则放行 . 客户离开时提醒客户不要遗忘物品

过滤器开发中应用的场景

  • 日志的记录
  • 性能的分析
  • 乱码的处理
  • 事务的控制
  • … …

过滤器工作位置图解

1682494494396

Filter接口API

  • 源码
1
2
3
4
5
6
7
8
9
10
11
12
package jakarta.servlet;
import java.io.IOException;

public interface Filter {
default public void init(FilterConfig filterConfig) throws ServletException {
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException;
default public void destroy() {
}
}

  • API目标
API 目标
default public void init(FilterConfig filterConfig) 初始化方法,由容器调用并传入初始配置信息filterConfig对象
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 过滤方法,核心方法,过滤请求,决定是否放行,响应之前的其他处理等都在该方法中
default public void destroy() 销毁方法,容器在回收过滤器对象之前调用的方法

2 过滤器使用

目标:开发一个日志记录过滤器

  • 用户请求到达目标资源之前,记录用户的请求资源路径
  • 响应之前记录本次请求目标资源运算的耗时
  • 可以选择将日志记录进入文件,为了方便测试,这里将日志直接在控制台打印

定义一个过滤器类,编写功能代码

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
@WebFilter(
filterName = "logginFilter",
urlPatterns = {"/servletA", "*.html"},
servletNames = {"servletBName"}
)
public class LoggingFilter implements Filter {

private SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

// 参数父转子
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;

// 拼接日志文本
String requestURI = request.getRequestURI();
String time = dateFormat.format(new Date());
String beforeLogging = requestURI + "在" + time + "被请求了";

// 打印日志
System.out.println(beforeLogging);

// 获取系统时间
long t1 = System.currentTimeMillis();

// 放行请求
filterChain.doFilter(request, response);

// 获取系统时间
long t2 = System.currentTimeMillis();

// 拼接日志文本
String afterLogging = requestURI + "在" + time + "的请求耗时:" + (t2 - t1) + "毫秒";

// 打印日志
System.out.println(afterLogging);

}
}

  • 说明
    • doFilter方法中的请求和响应对象是以父接口的形式声明的,实际传入的实参就是HttpServletRequest和HttpServletResponse子接口级别的,可以安全强转
    • filterChain.doFilter(request,response); 这行代码的功能是放行请求,如果没有这一行代码,则请求到此为止
    • filterChain.doFilter(request,response);在放行时需要传入request和response,意味着请求和响应对象要继续传递给后续的资源,这里没有产生新的request和response对象

定义两个Servlet作为目标资源

  • ServletA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@WebServlet(urlPatterns = "/servletA",name = "servletAName")
public class ServletA extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 处理器请求
System.out.println("servletA处理请求的方法,耗时10毫秒");
// 模拟处理请求耗时
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}

}
}
  • ServletB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@WebServlet(urlPatterns = "/servletB", name = "servletBName")
public class ServletB extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 处理器请求
System.out.println("servletB处理请求的方法,耗时15毫秒");
// 模拟处理请求耗时
try {
Thread.sleep(15);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}

测试

image-20250317235027399

过滤过程图解

1682496991032

3 过滤器生命周期

过滤器作为web项目的组件之一,和Servlet的生命周期类似,略有不同,没有servlet的load-on-startup的配置,默认就是系统启动立刻构造

阶段 对应方法 执行时机 执行次数
创建对象 构造器 web应用启动时 1
初始化方法 void init(FilterConfig filterConfig) 构造完毕 1
过滤请求 void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) 每次请求 多次
销毁 default void destroy() web应用关闭时 1次

测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@WebFilter("/*")
public class LifeCycleFilter implements Filter {

public LifeCycleFilter() {
System.out.println("过滤器构造方法调用");
}

@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("过滤器初始化方法调用");
}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

System.out.println("过滤器链方法调用");
chain.doFilter(request, response);
}

@Override
public void destroy() {
System.out.println("过滤器销毁");
}
}

4 过滤器链的使用

一个web项目中,可以同时定义多个过滤器,多个过滤器对同一个资源进行过滤时,工作位置有先后,整体形成一个工作链,称之为过滤器链

  • 过滤器链中的过滤器的顺序由filter-mapping顺序决定
  • 每个过滤器过滤的范围不同,针对同一个资源来说,过滤器链中的过滤器个数可能是不同的
  • 如果某个Filter是使用ServletName进行匹配规则的配置,那么这个Filter执行的优先级要更低

图解过滤器链

1682556566084

过滤器链功能测试

  • 定义三个过滤器,对目标资源Servlet的请求进行过滤

  • 目标Servlet资源代码

1
2
3
4
5
6
7
@WebServlet("/servletC")
public class ServletC extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("servletC service method invoked");
}
}
  • 三个过滤器代码
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
@WebFilter(urlPatterns = "/servletC")
public class Filter1 implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("filter1 before chain.doFilter code invoked");

filterChain.doFilter(servletRequest, servletResponse);

System.out.println("filter1 after chain.doFilter code invoked");

}
}

@WebFilter(urlPatterns = "/servletC")
public class Filter2 implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("filter2 before chain.doFilter code invoked");

filterChain.doFilter(servletRequest,servletResponse);

System.out.println("filter2 after chain.doFilter code invoked");

}
}



@WebFilter(urlPatterns = "/servletC")
public class Filter3 implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("filter3 before chain.doFilter code invoked");

filterChain.doFilter(servletRequest, servletResponse);

System.out.println("filter3 after chain.doFilter code invoked");

}
}

  • 测试结果

image-20250318012441333

工作流程图解

1682497251883

5 注解方式配置过滤器

@WebFilter注解的使用

  • 源码
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
package jakarta.servlet.annotation;

import jakarta.servlet.DispatcherType;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface WebFilter {
String description() default "";

String displayName() default "";

WebInitParam[] initParams() default {};

String filterName() default "";

String smallIcon() default "";

String largeIcon() default "";

String[] servletNames() default {};

String[] value() default {};

String[] urlPatterns() default {};

DispatcherType[] dispatcherTypes() default {DispatcherType.REQUEST};

boolean asyncSupported() default false;
}

  • 一个比较完整的Filter的XML配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!--配置filter,并为filter起别名-->
<filter>
<filter-name>loggingFilter</filter-name>
<filter-class>com.atguigu.filters.LoggingFilter</filter-class>
<!--配置filter的初始参数-->
<init-param>
<param-name>dateTimePattern</param-name>
<param-value>yyyy-MM-dd HH:mm:ss</param-value>
</init-param>
</filter>
<!--为别名对应的filter配置要过滤的目标资源-->
<filter-mapping>
<filter-name>loggingFilter</filter-name>
<!--通过映射路径确定过滤资源-->
<url-pattern>/servletA</url-pattern>
<!--通过后缀名确定过滤资源-->
<url-pattern>*.html</url-pattern>
<!--通过servlet别名确定过滤资源-->
<servlet-name>servletBName</servlet-name>
</filter-mapping>
  • 将xml配置转换成注解方式实现
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
@WebFilter(
filterName = "loggingFilter",
initParams = {@WebInitParam(name="dateTimePattern",value="yyyy-MM-dd HH:mm:ss")},
urlPatterns = {"/servletA","*.html"},
servletNames = {"servletBName"}
)
public class LoggingFilter implements Filter {
private SimpleDateFormat dateFormat ;

/*init初始化方法,通过filterConfig获取初始化参数
* init方法中,可以用于定义一些其他初始化功能代码
* */
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 获取初始参数
String dateTimePattern = filterConfig.getInitParameter("dateTimePattern");
// 初始化成员变量
dateFormat=new SimpleDateFormat(dateTimePattern);
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
// 参数父转子
HttpServletRequest request =(HttpServletRequest) servletRequest;
HttpServletResponse response =(HttpServletResponse) servletResponse;
// 拼接日志文本
String requestURI = request.getRequestURI();
String time = dateFormat.format(new Date());
String beforeLogging =requestURI+"在"+time+"被请求了";
// 打印日志
System.out.println(beforeLogging);
// 获取系统时间
long t1 = System.currentTimeMillis();
// 放行请求
filterChain.doFilter(request,response);
// 获取系统时间
long t2 = System.currentTimeMillis();
String afterLogging =requestURI+"在"+time+"的请求耗时:"+(t2-t1)+"毫秒";
// 打印日志
System.out.println(afterLogging);

}
}

14. 监听器

1 监听器概述

监听器:专门用于对域对象身上发生的事件或状态改变进行监听和相应处理的对象

  • 监听器并不监听web项目中的所有组件,仅仅是对三大域对象做相关的事件监听

监听器的分类

  • web中定义八个监听器接口作为监听器的规范

  • 按监听的对象划分

    • application域监听器 ServletContextListener ServletContextAttributeListener
    • session域监听器 HttpSessionListener HttpSessionAttributeListener HttpSessionBindingListener HttpSessionActivationListener
    • request域监听器 ServletRequestListener ServletRequestAttributeListener
  • 按监听的事件分

    • 域对象的创建和销毁监听器 ServletContextListener HttpSessionListener ServletRequestListener
    • 域对象数据增删改事件监听器 ServletContextAttributeListener HttpSessionAttributeListener ServletRequestAttributeListener
    • 其他监听器 HttpSessionBindingListener HttpSessionActivationListener

2 监听器的六个主要接口

1 application域监听器

ServletContextListener 监听ServletContext对象的创建与销毁

方法名 作用
contextInitialized(ServletContextEvent sce) ServletContext创建时调用
contextDestroyed(ServletContextEvent sce) ServletContext销毁时调用
  • ServletContextEvent对象代表从ServletContext对象身上捕获到的事件,通过这个事件对象我们可以获取到ServletContext对象。

ServletContextAttributeListener 监听ServletContext中属性的添加、移除和修改

方法名 作用
attributeAdded(ServletContextAttributeEvent scab) 向ServletContext中添加属性时调用
attributeRemoved(ServletContextAttributeEvent scab) 从ServletContext中移除属性时调用
attributeReplaced(ServletContextAttributeEvent scab) 当ServletContext中的属性被修改时调用
  • ServletContextAttributeEvent对象代表属性变化事件,它包含的方法如下:
方法名 作用
getName() 获取修改或添加的属性名
getValue() 获取被修改或添加的属性值
getServletContext() 获取ServletContext对象

测试代码

  • 定义监听器
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
@WebListener
public class ApplicationListener implements ServletContextListener , ServletContextAttributeListener {
// 监听初始化
@Override
public void contextInitialized(ServletContextEvent sce) {
ServletContext application = sce.getServletContext();
System.out.println("application"+application.hashCode()+" initialized");
}
// 监听销毁
@Override
public void contextDestroyed(ServletContextEvent sce) {
ServletContext application = sce.getServletContext();
System.out.println("application"+application.hashCode()+" destroyed");
}

// 监听数据增加
@Override
public void attributeAdded(ServletContextAttributeEvent scae) {
String name = scae.getName();
Object value = scae.getValue();
ServletContext application = scae.getServletContext();
System.out.println("application"+application.hashCode()+" add:"+name+"="+value);
}

// 监听数据移除
@Override
public void attributeRemoved(ServletContextAttributeEvent scae) {
String name = scae.getName();
Object value = scae.getValue();
ServletContext application = scae.getServletContext();
System.out.println("application"+application.hashCode()+" remove:"+name+"="+value);
}
// 监听数据修改
@Override
public void attributeReplaced(ServletContextAttributeEvent scae) {
String name = scae.getName();
Object value = scae.getValue();
ServletContext application = scae.getServletContext();
Object newValue = application.getAttribute(name);
System.out.println("application"+application.hashCode()+" change:"+name+"="+value+" to "+newValue);
}


}

  • 定义触发监听器的代码
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
// ServletA用于向application域中放入数据
@WebServlet(urlPatterns = "/servletA",name = "servletAName")
public class ServletA extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 向application域中放入数据
ServletContext application = this.getServletContext();
application.setAttribute("k1","v1");
application.setAttribute("k2","v2");
}
}


// ServletB用于向application域中修改和移除数据
@WebServlet(urlPatterns = "/servletB", name = "servletBName")
public class ServletB extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletContext appliation = getServletContext();
// 修改application域中的数据
appliation.setAttribute("k1","value1");
// 删除application域中的数据
appliation.removeAttribute("k2");
}
}
2 session域监听器

HttpSessionListener 监听HttpSession对象的创建与销毁

方法名 作用
sessionCreated(HttpSessionEvent hse) HttpSession对象创建时调用
sessionDestroyed(HttpSessionEvent hse) HttpSession对象销毁时调用
  • HttpSessionEvent对象代表从HttpSession对象身上捕获到的事件,通过这个事件对象我们可以获取到触发事件的HttpSession对象。

HttpSessionAttributeListener 监听HttpSession中属性的添加、移除和修改

方法名 作用
attributeAdded(HttpSessionBindingEvent se) 向HttpSession中添加属性时调用
attributeRemoved(HttpSessionBindingEvent se) 从HttpSession中移除属性时调用
attributeReplaced(HttpSessionBindingEvent se) 当HttpSession中的属性被修改时调用
  • HttpSessionBindingEvent对象代表属性变化事件,它包含的方法如下:
方法名 作用
getName() 获取修改或添加的属性名
getValue() 获取被修改或添加的属性值
getSession() 获取触发事件的HttpSession对象

测试代码

  • 定义监听器
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
@WebListener
public class SessionListener implements HttpSessionListener, HttpSessionAttributeListener {
// 监听session创建
@Override
public void sessionCreated(HttpSessionEvent se) {
HttpSession session = se.getSession();
System.out.println("session"+session.hashCode()+" created");
}

// 监听session销毁
@Override
public void sessionDestroyed(HttpSessionEvent se) {
HttpSession session = se.getSession();
System.out.println("session"+session.hashCode()+" destroyed");
}
// 监听数据增加
@Override
public void attributeAdded(HttpSessionBindingEvent se) {
String name = se.getName();
Object value = se.getValue();
HttpSession session = se.getSession();
System.out.println("session"+session.hashCode()+" add:"+name+"="+value);
}
// 监听数据移除
@Override
public void attributeRemoved(HttpSessionBindingEvent se) {
String name = se.getName();
Object value = se.getValue();
HttpSession session = se.getSession();
System.out.println("session"+session.hashCode()+" remove:"+name+"="+value);
}
// 监听数据修改
@Override
public void attributeReplaced(HttpSessionBindingEvent se) {
String name = se.getName();
Object value = se.getValue();
HttpSession session = se.getSession();
Object newValue = session.getAttribute(name);
System.out.println("session"+session.hashCode()+" change:"+name+"="+value+" to "+newValue);
}

}
  • 定义触发监听器的代码
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
// servletA用于创建session并向session中放数据
@WebServlet(urlPatterns = "/servletA",name = "servletAName")
public class ServletA extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 创建session,并向session中放入数据
HttpSession session = req.getSession();

session.setAttribute("k1","v1");
session.setAttribute("k2","v2");
}
}


// servletB用于修改删除session中的数据并手动让session不可用
@WebServlet(urlPatterns = "/servletB", name = "servletBName")
public class ServletB extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
HttpSession session = req.getSession();
// 修改session域中的数据
session.setAttribute("k1","value1");
// 删除session域中的数据
session.removeAttribute("k2");
// 手动让session不可用
session.invalidate();
}
}
3 request域监听器

ServletRequestListener 监听ServletRequest对象的创建与销毁

方法名 作用
requestInitialized(ServletRequestEvent sre) ServletRequest对象创建时调用
requestDestroyed(ServletRequestEvent sre) ServletRequest对象销毁时调用
  • ServletRequestEvent对象代表从HttpServletRequest对象身上捕获到的事件,通过这个事件对象我们可以获取到触发事件的HttpServletRequest对象。另外还有一个方法可以获取到当前Web应用的ServletContext对象。

ServletRequestAttributeListener 监听ServletRequest中属性的添加、移除和修改

方法名 作用
attributeAdded(ServletRequestAttributeEvent srae) 向ServletRequest中添加属性时调用
attributeRemoved(ServletRequestAttributeEvent srae) 从ServletRequest中移除属性时调用
attributeReplaced(ServletRequestAttributeEvent srae) 当ServletRequest中的属性被修改时调用
  • ServletRequestAttributeEvent对象代表属性变化事件,它包含的方法如下:
方法名 作用
getName() 获取修改或添加的属性名
getValue() 获取被修改或添加的属性值
getServletRequest () 获取触发事件的ServletRequest对象
  • 定义监听器
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
@WebListener
public class RequestListener implements ServletRequestListener , ServletRequestAttributeListener {
// 监听初始化
@Override
public void requestInitialized(ServletRequestEvent sre) {
ServletRequest request = sre.getServletRequest();
System.out.println("request"+request.hashCode()+" initialized");
}

// 监听销毁
@Override
public void requestDestroyed(ServletRequestEvent sre) {
ServletRequest request = sre.getServletRequest();
System.out.println("request"+request.hashCode()+" destoryed");
}


// 监听数据增加
@Override
public void attributeAdded(ServletRequestAttributeEvent srae) {
String name = srae.getName();
Object value = srae.getValue();
ServletRequest request = srae.getServletRequest();
System.out.println("request"+request.hashCode()+" add:"+name+"="+value);
}

// 监听数据移除
@Override
public void attributeRemoved(ServletRequestAttributeEvent srae) {
String name = srae.getName();
Object value = srae.getValue();
ServletRequest request = srae.getServletRequest();
System.out.println("request"+request.hashCode()+" remove:"+name+"="+value);
}
// 监听数据修改
@Override
public void attributeReplaced(ServletRequestAttributeEvent srae) {
String name = srae.getName();
Object value = srae.getValue();
ServletRequest request = srae.getServletRequest();
Object newValue = request.getAttribute(name);
System.out.println("request"+request.hashCode()+" change:"+name+"="+value+" to "+newValue);
}
}
  • 定义触发监听器的代码
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
//  servletA向请求域中放数据
@WebServlet(urlPatterns = "/servletA",name = "servletAName")
public class ServletA extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 向request中增加数据
req.setAttribute("k1","v1");
req.setAttribute("k2","v2");
// 请求转发
req.getRequestDispatcher("servletB").forward(req,resp);
}
}

// servletB修改删除域中的数据
@WebServlet(urlPatterns = "/servletB", name = "servletBName")
public class ServletB extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 修改request域中的数据
req.setAttribute("k1","value1");
// 删除session域中的数据
req.removeAttribute("k2");

}
}

3 session域的两个特殊监听器

1 session绑定监听器

HttpSessionBindingListener 监听当前监听器对象在Session域中的增加与移除

方法名 作用
valueBound(HttpSessionBindingEvent event) 该类的实例被放到Session域中时调用
valueUnbound(HttpSessionBindingEvent event) 该类的实例从Session中移除时调用
  • HttpSessionBindingEvent对象代表属性变化事件,它包含的方法如下:
方法名 作用
getName() 获取当前事件涉及的属性名
getValue() 获取当前事件涉及的属性值
getSession() 获取触发事件的HttpSession对象

测试代码

  • 定义监听器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class MySessionBindingListener  implements HttpSessionBindingListener {
// 监听绑定
@Override
public void valueBound(HttpSessionBindingEvent event) {
HttpSession session = event.getSession();
String name = event.getName();
System.out.println("MySessionBindingListener"+this.hashCode()+" binding into session"+session.hashCode()+" with name "+name);
}

// 监听解除绑定
@Override
public void valueUnbound(HttpSessionBindingEvent event) {
HttpSession session = event.getSession();
String name = event.getName();
System.out.println("MySessionBindingListener"+this.hashCode()+" unbond outof session"+session.hashCode()+" with name "+name);
}
}
  • 定义触发监听器的代码
1
2
3
4
5
6
7
8
9
10
11
@WebServlet(urlPatterns = "/servletA",name = "servletAName")
public class ServletA extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
HttpSession session = req.getSession();
// 绑定监听器
session.setAttribute("bindingListener",new MySessionBindingListener());
// 解除绑定监听器
session.removeAttribute("bindingListener");
}
}
2 钝化活化监听器

HttpSessionActivationListener 监听某个对象在Session中的序列化与反序列化。

方法名 作用
sessionWillPassivate(HttpSessionEvent se) 该类实例和Session一起钝化到硬盘时调用
sessionDidActivate(HttpSessionEvent se) 该类实例和Session一起活化到内存时调用
  • HttpSessionEvent对象代表事件对象,通过getSession()方法获取事件涉及的HttpSession对象。

什么是钝化活化

  • session对象在服务端是以对象的形式存储于内存的,session过多,服务器的内存也是吃不消的
  • 而且一旦服务器发生重启,所有的session对象都将被清除,也就意味着session中存储的不同客户端的登录状态丢失
  • 为了分摊内存压力并且为了保证session重启不丢失,我们可以设置将session进行钝化处理
  • 在关闭服务器前或者到达了设定时间时,对session进行序列化到磁盘,这种情况叫做session的钝化
  • 在服务器启动后或者再次获取某个session时,将磁盘上的session进行反序列化到内存,这种情况叫做session的活化

如何配置钝化活化

  • 在web目录下,添加 META-INF下创建Context.xml

1682565824241

  • 文件中配置钝化
1
2
3
4
5
6
<?xml version="1.0" encoding="UTF-8"?>
<Context>
<Manager className="org.apache.catalina.session.PersistentManager" maxIdleSwap="1">
<Store className="org.apache.catalina.session.FileStore" directory="d:\mysession"></Store>
</Manager>
</Context>
  • 请求servletA,获得session,并存入数据,然后重启服务器
1
2
3
4
5
6
7
8
9
@WebServlet(urlPatterns = "/servletA",name = "servletAName")
public class ServletA extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
HttpSession session = req.getSession();
// 添加数据
session.setAttribute("k1","v1");
}
}
  • 请求servletB获取session,获取重启前存入的数据
1
2
3
4
5
6
7
8
9
10
@WebServlet(urlPatterns = "/servletB", name = "servletBName")
public class ServletB extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
HttpSession session = req.getSession();
Object v1 = session.getAttribute("k1");
System.out.println(v1);

}
}

如何监听钝化活化

  • 定义监听器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class ActivationListener  implements HttpSessionActivationListener, Serializable {
// 监听钝化
@Override
public void sessionWillPassivate(HttpSessionEvent se) {
HttpSession session = se.getSession();
System.out.println("session with JSESSIONID "+ session.getId()+" will passivate");
}

// 监听活化
@Override
public void sessionDidActivate(HttpSessionEvent se) {
HttpSession session = se.getSession();
System.out.println("session with JSESSIONID "+ session.getId()+" did activate");
}
}

  • 定义触发监听器的代码
1
2
3
4
5
6
7
8
9
10
11
@WebServlet(urlPatterns = "/servletA",name = "servletAName")
public class ServletA extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
HttpSession session = req.getSession();
// 添加数据
session.setAttribute("k1","v1");
// 添加钝化活化监听器
session.setAttribute("activationListener",new ActivationListener());
}
}