java-Web基础之Servlet、Filter、Listener

努力有时候也战胜不了天分,但至少能让别人看得起你。

——《七龙珠》

手动创建一个Maven项目

由于要学习Java中的Servlet,Filter,Listener等组件需要下载依赖,所以我们需要在构建工具中进行演示和学习。本文使用Maven项目作为示例。

创建文件夹结构

1
2
mkdir -p myapp/src/main/java	//创建文件结构
cd myapp //进入项目

手动创建一个简单的 pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
<!-- 文件: pom.xml -->
<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>com.example</groupId>
<artifactId>myapp</artifactId>
<version>1.0-SNAPSHOT</version>
</project>

这样我们就创建好了一个最简单maven项目。

Servlet

Servlet 是JavaWeb技术的核心组件,本质上就是运行在服务器端的小程序,用于处理客户端(如浏览器)的请求,生成动态响应。Servlet主要用于处理Http请求,实现动态网页内容生成。

Servlet 生命周期

Servlet的生命周期由Servlet容器(如Tomcat)管理,主要有以下几个阶段:

Servlet容器:Web服务器,用于接收HTTP请求并调用你写的Servlet 代码处理请求。

初始化 init() –只调用一次

  • 在Servlet第一次被访问时,容器会创建一个Servlet实例,然后调用它的 init()方法。
  • 通常用来做资源的初始化操作、比如数据库连接、读取配置文件等。
1
2
3
4
@Override
public void init() throws ServletException {
System.out.println("Servlet 初始化:init()");
}

注:只有一个实例,init()只调用一次

请求处理 service()–每次请求都会调用

  • 每次有客户端请求Servlet,Tomcat就会调用Servlet实例的 service() 方法
  • service() 实例会自动判断请求类型(GET/POST),然后分发给对应的doGet() 或 doPost()
1
2
3
4
5
6
7
8
9
10
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("处理 GET 请求");
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("处理 POST 请求");
}

注:此处我们先暂时了解大概流程,无需关心代码实际实现

销毁 destroy()–只调用一次

  • 当Tomcat关闭、或Servlet被卸载(如热更新时),容器会调用 destroy()方法
  • 通常用来释放资源,如关闭数据库连接,停止线程等。
1
2
3
4
@Override
public void destroy() {
System.out.println("Servlet 被销毁:destroy()");
}

补充说明

  1. 一个Servlet类只被实例化一次(单例),多个请求公用一个实例,线程不安全。
  2. Servlet生命周期由容器自动管理,你无需手动调用这些方法
  3. SpringMVC的低层DispatchServlet也是一个Servlet,生命周期和你写的一样。

部署Tomcat

Apache 基金会开发的免费、开源、轻量级 Java Web 容器 ,支持 Servlet 和 JSP 规范

  • SpringBoot + SpringMVC 中内置Tomcat,无需配置容器,直接Main方法运行。

首先我们在官网下载安装TomCat:Apache Tomcat® - Welcome!

安装后记住安装路径,后面要用。

然后我们在VScode中安装扩展:

1747657005375

这个扩展帮助我们管理和启动Tomcat。

安装完成后,我们在VScode的侧边栏中的SERVICE中找到这个扩展(注意在资源管理器栏的最下面):

1747657081823

我们右键这个服务选择:Creat New Server

现在我们查看VScode的最上面,询问我们是否需要下载server,由于我们之前已经手动下载过Tomcat,所以选择No。

然后我们在弹出的窗口中选择之前安装的Tomcat的文件夹,来到这个页面:

1747657319639

不用更改配置,直接滑倒最下面点击Finish即可。

现在我们看到扩展中已经托管了Tomcat:

1747657387304

现在我们创建项目结构如下:

1
2
3
4
5
6
7
servlet-lifecycle-demo/
├── pom.xml
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/example/LifecycleServlet.java

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
28
29
30
31
32
33
34
<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>com.example</groupId>
<artifactId>servlet-lifecycle-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>

<dependencies>
<!-- Servlet API -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
</dependencies>

<build>
<finalName>servlet-lifecycle-demo</finalName>
<plugins>
<!-- Maven war 插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.3.1</version>
</plugin>
</plugins>
</build>
</project>

LifecycleServlet.java 文件内容如下:

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.example;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
import java.io.IOException;

@WebServlet("/life")
public class LifecycleServlet extends HttpServlet {

public LifecycleServlet() {
System.out.println("🚀 构造方法:Servlet 实例被创建");
}

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

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
System.out.println("📥 doGet():处理 GET 请求");
resp.setContentType("text/html;charset=UTF-8");
resp.getWriter().write("<h2>你好,这是 Servlet 生命周期演示</h2>");
}

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

然后我们在终端中输入:

1
mvn clean package

好,现在我们只需要右键Tomcat选择:add Deployment

1747657850124

然后选择File:

1747657911662

选择我们刚刚编译好的wer文件:

1747658001528

最后选择No:

1747657966359

我们就将刚刚编写好的Servlet加入了Tomcat容器中。

右键Tomcat服务选择Start Server,服务开始运行。此时打印的信息只有Tomcat的启动信息,Servlet还没有被创建。

1747658502463

然后我们浏览器访问:http://localhost:8080/servlet-lifecycle-demo/life

看到servlet实例被创建,然后调用了一次GET请求,

1747658529418

之后再次访问页面,也不会在创建新的Servlet实例,而是一直调用同一个实例的doGet方法

1747658627861

最后我们Stop Server:

1747658665412

我们可以在日志中找到Servlet销毁函数调用是打印的日志。

Servlet Mapping

Servlet的作用是将客户端发送的请求URL路径与特定的Servlet 类关联起来。当接收到符合某个映射规则的请求时,它会将请求交给对应的Servlet来处理。

使用 @WebServlet注解配置映射

在我们上面的代码中,我们使用

1
2
@WebServlet("/life")
public class LifecycleServlet extends HttpServlet

进行注解配置。这里的”/life” 就是一个URL模式(URL pattern), 当用户访问路径 /life 时,Tomcat就会将这个请求交给 LifecycleServlet的实例来处理。

  • 多个URL模式:可以为一个Servlet映射多个URL模式

    1
    @WebServlet({"/life","/mylife"})

    这样当访问多个路径中的任意一个路径时,都会交给 LifecycleServlet实例进行处理

  • 指定 name :可以为Servlet指定一个名称

    1
    2
    3
    4
    @WebServlet(name = "lifecycleServlet", urlPatterns = {"/life"})
    public class LifecycleServlet extends HttpServlet {
    // ... your code ...
    }

    在注解映射时,name属性通常不是必须的,但是在web.xml中,servlet-name属性是必须的,所以这里介绍一下。

使用 web.xml配置映射(传统方式)

在web应用的 WEB-INF目录下,会有一个web.xml文件。我们也可以通过这个文件进行Servlet配置和Servlet映射。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">

<servlet>
<servlet-name>lifecycleServlet</servlet-name>
<servlet-class>com.example.LifecycleServlet</servlet-class>
</servlet>

<servlet-mapping>
<servlet-name>lifecycleServlet</servlet-name>
<url-pattern>/life</url-pattern>
</servlet-mapping>

</web-app>
  • <servlet> 用于声明一个Servlet,通过 <servlet-name>指定名称(必须),<servlet-class>指定Servlet类的完整限定名。
  • <servlet-mapping>元素用于将一个Servlet名称 <servlet-name>映射到一个URL模式 <url-pattern>

URL模式的类型

  • 精确匹配:以 /开头,例如 /life。只用当请求的路径与此路径完全匹配时,Servlet才会被调用。
  • 路径匹配:以 /开头,以 /*结尾,例如 /admin/*。任何以 /admin/ 开头的请求都会被匹配到这个Servlet。
  • 扩展名匹配:以 *. 开头,例如 *.do。任何以 .do结尾的请求都会被匹配到这个Servlet
  • 默认Servlet:只有一个 /作为URL模式,他会处理所有其他Servlet映射都无法匹配的请求。

Servlet特性

Servlet的线程安全性:

  • Servlet实例是单例的,但是对于每个请求,Servlet容器都会创建一个新的线程来执行Servlet的 service()方法(doGet()doPost())
  • Servlet需要考虑线程安全性。避免在Servlet的实例变量中存储共享的可变状态,或者采取同步措施来保护共享资源。

Servlet的配置(ServletConfig):

  • ServletConfig 对象代表Servlet的配置信息,每个servlet都有自己的ServletConfig对象
  • 我们可以在 web.xml (或使用 @WebInitParam 注解)为Servlet配置初始化参数,并通过 ServletConfiggetInitParameter(String name) 方法获取。

一个小DEMO

  • 使用ServletConfig获取初始化参数:
1
2
3
4
5
6
7
8
<servlet>
<servlet-name>helloConfig</servlet-name>
<servlet-class>com.example.HelloConfig</servlet-class>
<init-param>
<param-name>exampleParam</param-name>
<param-value>Hello from config!</param-value>
</init-param>
</servlet>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class HelloConfig extends HttpServlet {

private String exampleParam;

@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
exampleParam = config.getInitParameter("exampleParam");
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("text/html;charset=UTF-8");
resp.getWriter().write("<h2>" + exampleParam + "</h2>");
}

}
  • 使用 @WebInitParam 注解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@WebServlet(urlPatterns = "/hello")
@WebInitParam(name = "exampleParam", value = "Hello from annotation!")
public class HelloAnnotation extends HttpServlet {
private String exampleParam;

@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
exampleParam= config.getInitParameter("exampleParam");
System.out.println("Greeting from annotation: " + exampleParam);
}

// ... doGet method ...
}

无论使用注解还是web.xml在Servlet类中获取初始化参数的方法都一样。只是注册参数的方法不同。

Servlet API详解

Servlet API 是构建Java Web 应用的核心。它定义了Servlet容器和你的Servlet之间的契约。

HttpServletRequest 接口

HttpServletRequest 对象代表了客户端发送给Servlet的HTTP请求。通过这个对象,你可以获取到请求的所有信息。

常用方法:

获取参数时,如果没有设置该参数,则会得到 null

  • getParameter(String name): 获取指定名称的请求参数的值(返回 String)。如果参数不存在或有多个同名参数,行为可能不确定。
  • getParameterValues(String name): 获取指定名称的所有请求参数的值(返回 String[])。
  • getParameterMap(): 获取所有请求参数的 Map<String, String[]>
  • getHeader(String name): 获取指定名称的请求头的值(返回 String)。
  • getHeaders(String name): 获取指定名称的所有请求头的值(返回 Enumeration<String>)。
  • getHeaderNames(): 获取所有请求头的名称(返回 Enumeration<String>)。
  • getMethod(): 获取请求的 HTTP 方法(例如:”GET”, “POST”)。
  • getRequestURI(): 获取请求的 URI。
  • getContextPath(): 获取 Web 应用的上下文路径。
  • getServletPath(): 获取 Servlet 的路径。
  • getQueryString(): 获取查询字符串(URL 中 ? 后面的部分)。
  • getRemoteAddr(): 获取发送请求的客户端的 IP 地址。
  • getRemoteHost(): 获取发送请求的客户端的主机名。
  • getSession(): 返回当前请求关联的 HttpSession,如果不存在则创建一个新的。
  • getSession(boolean create): 返回当前请求关联的 HttpSession,如果 createtrue 且不存在则创建一个新的,否则返回 null
  • setAttribute()HttpServletRequest 的属性只在当前请求的处理过程中有效,当该请求的响应发送回客户端后,这些属性就不再需要了
  • getAttribute():获取设置的属性。

练习1:获取get请求中的参数 name=ulna&age=20

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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
package com.example;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
import java.io.IOException;
import javax.servlet.ServletConfig;

import java.util.Enumeration;
import java.util.Map;


@WebServlet(name = "Test1", urlPatterns = {"/test1"})
public class HttpServletRequestTest1 extends HttpServlet {


@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// 获取请求参数的最佳实践

//直接获取参数
String name = req.getParameter("name");
String age = req.getParameter("age"); //缺少参数获取值null

// 通过Map方法获取
Map <String, String[]> parameterMap = req.getParameterMap();

String nameMap = parameterMap.get("name")[0];
//String ageMap = parameterMap.get("age")[0]; //如果少参数会报错
String ageMap = null; //使用Map在获取值之前需要判断是否存在
if (parameterMap.containsKey("age")) {
ageMap = parameterMap.get("age")[0];
}

// 获取请求头的最佳实践
// 获取所有请求头名称
Enumeration<String> headerNames = req.getHeaderNames(); //获取所有请求头名称


// 只获取想要的请求头
String userAgent = req.getHeader("user-agent"); //获取User-Agent请求头
System.out.println("User-Agent: " + userAgent);

// 设置响应内容类型
resp.setContentType("text/html;charset=UTF-8");

// 输出响应内容
resp.getWriter().println("<html><body>");
resp.getWriter().println("<h1>Request Method: " + req.getMethod() + "</h1>");
resp.getWriter().println("<h1>Received Parameters</h1>");
resp.getWriter().println("<p>Name: " + name + "</p>");
resp.getWriter().println("<p>Age: " + age + "</p>");
resp.getWriter().println("<h2>Using Map Method</h2>");
resp.getWriter().println("<p>Name from Map: " + nameMap + "</p>");
resp.getWriter().println("<p>Age from Map: " + ageMap + "</p>");
resp.getWriter().println("<h2>Request Headers</h2>");
while (headerNames.hasMoreElements()) { //像迭代器一样访问请求头
String headerName = headerNames.nextElement();
//可以在这里对请求头进行处理
String headerValue = req.getHeader(headerName);
resp.getWriter().println("<p>" + headerName + ": " + headerValue + "</p>");
}
// 输出上下文路径
resp.getWriter().println("<h1>Context Path: " + req.getContextPath() + "</h1>"); //当前应用的上下文路径
// 输出Servlet路径
resp.getWriter().println("<h1>Servlet Path: " + req.getServletPath() + "</h1>"); //当前Servlet实例的路径
// 实际访问的路径 = 上下文路径 + Servlet路径

resp.getWriter().println("</body></html>");
}

}

HttpServletResponse 接口

HttpServletResponse 对象代表了Servlet发送给客户端的Http响应。通过这个对象,我们可以设置响应的内容状态码响应头等信息。

常用方法:

  • getWriter(): 返回一个 PrintWriter 对象,你可以使用它向客户端发送文本数据。
  • getOutputStream(): 返回一个 ServletOutputStream 对象,你可以使用它向客户端发送二进制数据(例如:图片、文件)。
  • setContentType(String type): 设置响应的内容类型(例如:”text/html”, “application/json”)。同时也可以设置字符编码,例如 "text/html;charset=UTF-8"
  • setStatus(int sc): 设置响应的状态码(例如:200, 404, 500)。
  • sendError(int sc, String msg): 发送一个错误响应到客户端,并设置状态码和错误消息。
  • sendRedirect(String location): 发送一个临时的重定向响应,告诉客户端浏览器去访问另一个 URL。
  • setHeader(String name, String value): 设置指定的响应头。
  • addHeader(String name, String value): 添加指定的响应头(允许有多个同名 header)。
  • setDateHeader(String name, long date): 设置日期格式的响应头。
  • setIntHeader(String name, int value): 设置整数格式的响应头。
  • setContentLength(int len): 设置响应内容的长度。

练习2:创建一个Servlet,当通过访问特定的URL时如:/redirect-me , 他会使用sendRedirect() 方法将客户端重定向到另一个你指定的URL例如:https://www.google.com

1
2
3
4
5
6
7
8
9
10
@WebServlet(name = "Redirect", urlPatterns = {"/redirect-me"})
public class HttpServletRequestTest1 extends HttpServlet {

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.sendRedirect("https://www.google.com");
}

}

ServletConfig 接口

每个Servlet都有一个对应的 ServletConfig 对象,Servlet 容器在构建Servlet时会创建这个对象,并传递给Servlet的 init(ServletConfig config)方法。ServletConfig 接口允许你访问Servlet的配置信息。

注册初始参数:

只有一个参数时:通常可以这样简化写

1
2
3
@WebServlet(name = "ConfigTest", urlPatterns = {"/configTest"})
@WebInitParam(name = "param1", value = "value1")
public class ConfigTest extends HttpServlet

多个参数时:规范写法

1
2
3
4
5
6
7
@WebServlet(name = "ConfigTest",
urlPatterns = {"/configTest"},
initParams = {
@WebInitParam(name = "param1", value = "value1"),
@WebInitParam(name = "param2", value = "value2"),
})
public class ConfigTest extends HttpServlet

常用方法:

  • getServletName(): 返回 Servlet 的名称,通常是在 web.xml 或使用 @WebServlet 注解时指定的。
  • getInitParameter(String name): 返回 Servlet 初始化参数中指定名称的值(返回 String)。这些参数可以在 web.xml 中通过 <init-param> 标签配置,或者在使用注解时通过 @WebInitParam 配置。
  • getInitParameterNames(): 返回包含所有 Servlet 初始化参数名称的 Enumeration<String>.
  • getServletContext(): 返回当前 Web 应用的 ServletContext 对象。

练习3:创建一个Servlet,通过 @WebInitPara 注解配置至少两个初始化参数。在Servlet的 doGet() 方法中获取并显示这些初始化参数的值以及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
@WebServlet(name = "ConfigTest",
urlPatterns = {"/configTest"},
initParams = {
@WebInitParam(name = "param1", value = "value1"),
@WebInitParam(name = "param2", value = "value2"),
})
public class ConfigTest extends HttpServlet {
private String param1;
private String param2;

@Override
public void init(ServletConfig config) throws ServletException{
super.init(config);
param1 = config.getInitParameter("param1");
param2 = config.getInitParameter("param2");
}

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

resp.setContentType("text/plain");
resp.getWriter().println("Servlet Name: " + getServletConfig().getServletName()); //servlet中任何地方可以使用getServletConfig()获取该Servlet的Config
resp.getWriter().println("Param1: " + param1);
resp.getWriter().println("Param2: " + param2);
}

}

ServletContext 接口

ServletContext 接口代表了当前的Web应用程序。它是整个Web应用共享的上下文,所有的Servlet、Filter、Listener 等都可以访问同一个 ServletContext对象。Servlet 容器在启动Web应用时创建 ServletContext对象。

常用方法:

  • getAttribute(String name): 返回指定名称的应用程序级别的属性值(返回 Object)。
  • setAttribute(String name, Object object): 设置应用程序级别的属性/对象
  • removeAttribute(String name): 移除指定名称的应用程序级别的属性。
  • getInitParameter(String name): 返回 Web 应用的上下文初始化参数中指定名称的值(返回 String)。这些参数在 web.xml<context-param> 标签中配置。
  • getInitParameterNames(): 返回包含所有 Web 应用上下文初始化参数名称的 Enumeration<String>.
  • getResource(String path): 返回指定路径的资源 URL。路径以 / 开头,相对于 Web 应用的根目录。
  • getResourceAsStream(String path): 返回指定路径的资源输入流。
  • getContextPath(): 返回 Web 应用的上下文路径。
  • getServletContextName(): 返回 Web 应用的名称(如果在部署描述符中指定了)。
  • log(String msg): 将消息写入 Servlet 容器的日志。

练习4:

  1. 在你的web应用的web.xml文件种配置至少一个 <context-param>
  2. 创建一个Servlet,该Servlet 获取并显示这个上下文参数的值。
  3. 在该Servlet中,设置一个ServletContext属性,并在同一个Servlet中获取并显示这个属性的值。

web.xml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">

<context-param>
<param-name>appName</param-name>
<param-value>LearnServlet</param-value>
</context-param>
<context-param>
<param-name>blogName</param-name>
<param-value>https://blog.ulna520.top</param-value>
</context-param>
</web-app>
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
@WebServlet(name = "ContextTest",
urlPatterns = {"/contextTest"}
)
public class ContextTest extends HttpServlet {

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

ServletContext context = getServletContext();

String appName = context.getInitParameter("appName");
String blogName = context.getInitParameter("blogName");
String contextPath = context.getContextPath();
String ServletContextName = context.getServletContextName(); //未设置得到null

Map<String,Integer> map = new HashMap<>();
map.put("appName", 1);
map.put("blogName", 2);
map.put("contextPath", 3);
map.put("ServletContextName", 4);
context.setAttribute("ServletContext", map); //可以设置Object对象,以供全局访问一个对象


resp.setContentType("text/html;charset=UTF-8");
resp.getWriter().println("<html><body>");
resp.getWriter().println("<h1>Context Parameters</h1>");
resp.getWriter().println("<p>Application Name: " + appName + "</p>");
resp.getWriter().println("<p>Blog Name: " + blogName + "</p>");
resp.getWriter().println("<p>Context Path: " + contextPath + "</p>");
resp.getWriter().println("<p>Servlet Context Name: " + ServletContextName + "</p>");
resp.getWriter().println("<h2>Context Attributes</h2>");
resp.getWriter().println("<ul>");
Map<String, Integer> contextMap = (Map<String, Integer>) context.getAttribute("ServletContext");
for (Map.Entry<String, Integer> entry : contextMap.entrySet()) {
resp.getWriter().println("<li>" + entry.getKey() + ": " + entry.getValue() + "</li>");
}
resp.getWriter().println("</ul>");
resp.getWriter().println("</body></html>");
}

}

Servlet 请求转发与重定向

请求转发

请求转发是指将当前请求的处理交给web服务器上的另一个资源来完成,这个过程发生在服务器内部,客户端浏览器只发送一次请求(与重定向的一个关键不同)

实现方式:

通过 RequestDispatcher 接口来实现请求转发。我们可以通过 HttpServletRequest 对象的 getRequestDispatcher(String path) 方法获取一个 RequestDispatcher对象。path 可以是相对于当前Servlet上下文路径

RequestDispatcher 接口提供了两个主要方法用于转发:

  • forword(ServletRequest request, ServletResponse response):将请求转发给另一个资源。
    • 注意:在转发后,forword()后的代码有可能会执行。但是在调用之后,当前Servlet不应该再向客户端发送任何响应。最佳实践是 forwrod()后就立即结束当前方法。
  • include(ServletRequest request, ServletResponse response):将另一个资源的内容包含到当前响应中。执行流程会回到当前Servlet继续处理。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@WebServlet("/first")
public class FirstServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setAttribute("message", "Hello from FirstServlet");
RequestDispatcher dispatcher = request.getRequestDispatcher("/second");
System.out.println("FirstServlet: Forwarding request to /second");
dispatcher.forward(request, response);
System.out.println("FirstServlet: After forwarding"); // 这行代码可能不会立即执行,取决于 SecondServlet 的处理
}

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@WebServlet("/second")
public class SecondServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String message = (String) request.getAttribute("message");
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
out.println("<!DOCTYPE html>");
out.println("<html><head><title>Second Servlet</title></head><body>");
out.println("<h1>Second Servlet 处理请求</h1>");
if (message != null) {
out.println("<p>Message from previous servlet: " + message + "</p>");
} else {
out.println("<p>No message passed.</p>");
}
out.println("</body></html>");
System.out.println("SecondServlet: Request processed.");
}

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}

请求重定向

请求重定向是指服务器告诉客户端浏览器发送一个新的HTTP请求到另一个URL。这涉及到两次HTTP请求-响应周期。

实现方式:

通过 HttpServletResponse 对象的 sendRedirect(String location)方法来发送重定向响应。

location 是客户端浏览器需要重新访问的URL。

1
2
3
4
5
6
7
8
9
10
11
12
@WebServlet("/redirect-google")
public class RedirectServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("RedirectServlet: Received request, redirecting to Google.");
response.sendRedirect("https://www.google.com");
System.out.println("RedirectServlet: After sending redirect.");
}

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}

Filter

Filter基础知识

Filter(过滤器)是Servlet规范中一个非常重要的组件,它的核心作用是在请求到达目标Servlet或JSP之前,以及响应离开目标Servlet/JSP后,对请求和响应进行截获和处理。

Fliter的作用

在没有Filter之前,如果我们需要对所有请求进行一些通用操作(比如权限检查、编码设置),我们就在每个Servlet中重复编写这些逻辑。这回导致大量的重复代码,难以维护。Filter的出现就是为了解决这个问题,它允许我们将这些通用的、与具体业务逻辑无关的功能从Servlet中分离出来,集中处理。

应用场景

  • 权限认证:检查用户是否已经登录,是否有权访问某个资源。
  • 日志记录:记录每个请求的详细信息,例如请求路径、用户IP、请求时间、响应耗时等。
  • 字符编码处理:统一设置请求和响应的字符编码,防止中文乱码问题。
  • 数据压缩:对响应内容进行GZIP压缩,减少网络传输量,提高加载速度。
  • 敏感词过滤:对用户提交的数据进行过滤,例如评论中的敏感词。
  • 缓存控制:设置HTTP响应头,控制客户端的缓存行为。
  • 会话管理:检查会话是否存在,或在会话过期时重定向。

Filter的生命周期

Filter也有自己的生命周期,由Servlet容器如(Tomcat)管理:

  1. 加载和实例化:当Web应用程序启动或者容器首次需要使用某个Filter时,容器会加载并创建Filter的实例。

  2. 初始化(init()):容器会调用Filter实例的 init()方法。这个方法只被调用一次,用于执行一些初始化操作,比如读取配置参数、简历数据库连接等。

    方法签名:

    1
    void init(FilterConfig filterConfig) throws ServletException
  3. 请求处理(doFilter()):当客户端请匹配到该Filter拦截的URL模式时,容器就会调用Filter实例的 doFilter()方法。这是Filter执行核心逻辑的地方。
    方法签名:

    1
    void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
  4. 销毁(destroy()):当Web应用程序关闭或者Filter被卸载时,容器会调用Filter实例的 destroy()方法。这个方法也只会调用一次,用于释放资源,比如关闭数据库连接、清理缓存等。

    方法签名:

    1
    void destroy()

Filter三大接口

javax.servlet.Filter接口

javax.servlet.Filter 是所有自定义过滤器的基石。它是一个接口,定义了Filter必须实现的三个核心方法。

  • init()
  • doFilter()
  • destroy()
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
package javax.servlet;

import java.io.IOException;

public interface Filter {
/**
* 由 Web 容器在 Filter 实例化后立即调用,只调用一次。
* 用于执行初始化任务。
* @param filterConfig Filter 的配置对象
* @throws ServletException 如果初始化过程中发生错误
*/
void init(FilterConfig filterConfig) throws ServletException;

/**
* 这是 Filter 的核心方法,每当请求匹配到 Filter 映射的 URL 模式时都会调用。
* 在这里可以对请求和响应进行预处理或后处理。
* @param request Servlet 请求对象
* @param response Servlet 响应对象
* @param chain Filter 链,用于将请求传递给下一个 Filter 或目标资源
* @throws IOException 如果发生 I/O 错误
* @throws ServletException 如果发生 Servlet 错误
*/
void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException;

/**
* 由 Web 容器在 Filter 实例被销毁前调用,只调用一次。
* 用于释放 Filter 所持有的资源。
*/
void destroy();
}

javax.servlet.FilterChain 接口

FilterChain(过滤器链)是Filter机制中一个至关重要的概念。当一个请求进入一个Web应用程序时,如果它需要经过多个Filter的处理,这些Filter就会被组织成一个链条。FilterChain 就是这个链条的抽象。

FilterChain 接口只有一个核心方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package javax.servlet;

import java.io.IOException;

public interface FilterChain {
/**
* 将请求和响应传递给链中的下一个过滤器。
* 如果这是链中的最后一个过滤器,则将请求传递给目标资源 (Servlet 或 JSP)。
* @param request Servlet 请求对象
* @param response Servlet 响应对象
* @throws IOException 如果发生 I/O 错误
* @throws ServletException 如果发生 Servlet 错误
*/
void doFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException;
}

在自定义Filter的doFilter()方法中,通常会使用到 chain.doFilter(request,response)

作用:

  • 如果当前Filter不是链中的最后一个Filter,它会将请求和响应对象传递给下一个Filter。
  • 如果当前Filter是链中的最后一个Filter,它会将请求和响应对象传递给最终的目标资源(例如Servlet或JSP)

如果没用调用 chain.doFilter(request, response),那么请求就不会继续传递下去,它将被中断,后续的Filter或目标资源将永远不会执行到。

javax.servlet.FilterConfig 接口

FilterConfig 对象提供了Filter的配置信息。当容器调用Filter的 init()方法时,会将一个 FilterConfig实例作为参数传递进来。

FilterConfig接口提供的方法包括:

  • String getFilterName():获取Filter的名称(在web.xml中 <filter-name>定义的名称,或者 @WebFilter注解的 filterName属性)
  • ServletContext getServletContext():获取与此Filter相关的 ServletContext 对象。
  • String getInitparameter(String name):根据参数名获取Filter的初始化参数值。(在配置文件中设置的参数)
  • Enumeration<String> getInitParameterNames():获取所有初始化参数的名称。

创建与配置Filter

创建Filter

创建Filter主要涉及两步:

  • 实现 javax.servlet.Filter接口
  • 重写其核心方法。

步骤一:创建一个Java类并实现Filter接口

我们需要创建一个普通的Java类,并让它实现 javax.servlet.Filter接口。

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
60
61
62
63
64
65
package com.example.filters;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;


public class MyCustomFilter implements Filter{

private String filterName; // 存储 Filter 的名称

/**
* Filter 初始化方法,在 Filter 实例创建后被 Web 容器调用一次。
* 可以在这里读取初始化参数,进行一些资源准备。
* @param filterConfig 包含 Filter 配置信息的对象
* @throws ServletException 如果初始化失败
*/
@Override
public void init(FilterConfig filterConfig) throws ServletException {
this.filterName = filterConfig.getFilterName();
System.out.println(filterName + " 初始化...");
// 示例:获取 Filter 的初始化参数
String paramValue = filterConfig.getInitParameter("myParam");
if (paramValue != null) {
System.out.println("获取到初始化参数 'myParam': " + paramValue);
}
}

/**
* 核心方法,每次请求匹配到该 Filter 时都会被调用。
* 在这里实现你的过滤逻辑。
* @param request ServletRequest 对象
* @param response ServletResponse 对象
* @param chain FilterChain 对象,用于将请求传递 给下一个 Filter 或目标资源
* @throws IOException 如果发生 I/O 错误
* @throws ServletException 如果发生 Servlet 错误
*/
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
System.out.println(filterName + " 正在执行 doFilter() (请求进入前)");


// 继续将请求传递给 Filter 链中的下一个 Filter 或目标资源
chain.doFilter(request, response);

// 后处理,在请求处理完毕、响应返回前被执行
System.out.println(filterName + " 正在执行 doFilter() (请求离开后)");
}

/**
* Filter 销毁方法,在 Filter 实例被销毁前被 Web 容器调用一次。
* 可以在这里释放资源。
*/
@Override
public void destroy() {
System.out.println(filterName + " 正在销毁...");
// 示例:关闭数据库连接、清理资源等
}
}

配置Filter

web.xml中配置Filter(传统方式)

这是Servlet 2.X 和早期版本的主要配置方式,在Servlet 3.0+中依然完全有效,并且在需要精准控制Filter顺序时非常有效。

  1. 打开或创建 web.xml文件
    通常位于你的Web项目的WEB-INF/web.xml路径下。

  2. 声明Filter
    使用 <filter>标签在web.xml中声明你的Filter。

    • <filter-name>:为你的Filter定义一个唯一的名称。
    • <filter-class>:指定你的Filter类的完全限定名。
    • <init-param>(可选):为Filter提供初始化参数,这些参数可以在 init()方法中通过 FilterConfig获取。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <filter>
    <filter-name>MyFilter</filter-name>
    <filter-class>com.example.filters.MyCustomFilter</filter-class>
    <init-param>
    <param-name>myParam</param-name>
    <param-value>HelloFilter</param-value>
    </init-param>
    <init-param>
    <param-name>anotherParam</param-name>
    <param-value>123</param-value>
    </init-param>
    </filter>
  3. 映射Filter到URL模式

    使用 <filter-mapping>标签将声明的Filter映射到特定的URL模式。只有匹配这些模式的请求才会经过此Filter。

    • <filter-name>
    • <url-pattern>
      • /*:拦截所有的请求
      • /user/*:拦截所有以 /user/开头的请求
      • *.jsp:拦截所有.jsp结尾的请求
      • /specific/path:拦截精确匹配的路径
    • <dispatcher>(可选):指定Filter应该在哪种请求调度类型下被使用。常见的有:
      • REQUEST(默认):当客户端直接请求资源时。
      • FORWARD:当通过 RequestDispatcher.forward()转发时。
      • INCLUDE:当通过 RequestDispatcher.include()包含时。
      • ERROR:当请求因为错误而转发到错误页面时。
      • ASYNC:当请求通过异步上下文处理时(Servlet3.0+)
    1
    2
    3
    4
    5
    6
    <filter-mapping>
    <filter-name>MyFilter</filter-name>
    <url-pattern>/*</url-pattern>
    <dispatcher>REQUEST</dispatcher>
    <dispatcher>FORWARD</dispatcher>
    </filter-mapping>

    web.xml中,Filter的执行顺序是根据它们在 <web-app>标签内 <filter-mapping>元素的出现顺序来决定的。容器会按照web.xml文件中 <filter-mapping>声明的从上到下的顺序来构建Filter链并依次执行。如果是url不匹配的Filter,则跳过。一直执行到最终资源。

使用注解 @WebFilter配置Filter(Servlet3.0+)

从Servlet3.0 开始,你可以使用注解来简化Filter的配置,省去了编写web.xml的麻烦。

在Filter类上添加 @WebFilter注解

  • filterName(可选):指定Filter的名称,默认为类名。
  • urlPatterns:一个字符串数组(可以拦截多个路径),用于指定Filter将拦截的URL模式
  • valueurlPatterns的别名。如果只有一个URL模式,可以直接用value。
  • initParams(可选):一个 @WebInitParam数组,用于设置初始化参数。
  • dispatcherTypes(可选):一个DispatcherType数组,指定Filter应该在哪种请求调度类型下被调用。
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
package com.example.filters;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.DispatcherType; // 导入 DispatcherType
import java.io.IOException;

// 使用注解配置 Filter
@WebFilter(
filterName = "AnnotatedMyFilter", // Filter 的名称
urlPatterns = {"/*"}, // 拦截所有请求
initParams = { // 初始化参数
@WebInitParam(name = "myParam", value = "HelloAnnotatedFilter")
},
dispatcherTypes = {DispatcherType.REQUEST, DispatcherType.FORWARD} // 调度类型
)
public class MyCustomFilter implements Filter {
// ... (与上面 web.xml 方式的 Filter 类内容相同) ...
private String filterName;

@Override
public void init(FilterConfig filterConfig) throws ServletException {
this.filterName = filterConfig.getFilterName();
System.out.println(filterName + " 初始化...");
String paramValue = filterConfig.getInitParameter("myParam");
if (paramValue != null) {
System.out.println("获取到初始化参数 'myParam': " + paramValue);
}
}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
System.out.println(filterName + " 正在执行 doFilter() (请求进入前)");
chain.doFilter(request, response);
System.out.println(filterName + " 正在执行 doFilter() (请求离开后)");
}

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

Filter顺序:如前所述,在使用 @WebFilter时,默认情况下Filter的执行顺序是不保证的(通常按照filterName的字母顺序或者类加载顺序),如果你需要精确的控制顺序,建议使用web.xml配置,或者在Spring等框架中使用 @Order 注解

Listener

在Java Servlet中,Listener(监听器)是一种特殊的组件,它的主要作用是”监听”Web应用程序中发生的特定事件。当某个预设事件发生时,Listener就会被触发,然后执行你为它定义的特定操作。

这些事件可以是:

  • Web应用程序自身的生命周期事件
  • 用户会话(Session)的生命周期事件
  • HTTP请求的生命周期事件
  • 特定作用域(应用程序、会话、请求)中属性的添加、移除或替换事件。

Listener的核心作用:

Listener的存在,让开发者能够在这些关键时刻介入并执行自定义的逻辑,而无需修改Servlet或JSP页面的核心业务代码。它的主要作用体现在以下几个方面:

  1. 资源初始化与清理:
    • 作用:在Web应用程序启动时,你可以使用Listener来加载配置文件、初始化数据库连接池、启动后台服务等一次性设置。而在应用程序关闭时,它可以用来关闭连接、释放资源、确保系统干净的退出。
    • 职责分离:Servlet或Filter的init()方法只在特定Servlet被初始化时,这些资源才会被初始化。如果我有多个Servlet都需要这些资源,那么每个Servlet都需要重复初始化逻辑,Filter同理。Listener专注于应用程序级别的生命周期管理和全局资源的初始化/清理。它的代码更清晰,专门处理“当应用程序启动/关闭时,我需要做什么”的问题。
  2. 数据统计与监控:
    • 作用:Listener可以用来实时跟踪网站的使用情况。例如,你可以计算当前在线用户数量,记录每天的访问量,或者监控某个特定功能的使用频率。
    • 例子:你想知道有多少用户同时在线。你可以使用Listener监听会话的创建和销毁事件,每次创建就+1,销毁就-1,从而得到在线用户数量。
  3. 日志记录:
    • 作用:在应用程序、会话或请求的关键生命周期点记录日志,帮助开发者追踪应用程序的行为、诊断问题或进行性能分析。
    • 例子:每当一个用户请求进入或离开服务时,你想记录下请求的URL和处理时间。Listener就可以做到这一点。

Litener接口

Java Servlet 规范提供了一系列Listener接口,它们被设计用来监听不同范围和类型的事件。掌握这些接口是使用Listener的基础。

应用(Web容器)生命周期Listener

这类Listener用于监听整个Web应用程序的启动和关闭事件。

ServletContextListener

  • 作用:监听Web应用程序(ServletContext)的生命周期事件。当整个Web应用程序被部署到Servlet容器(如Tomcat)并启动时,以及当应用程序被关闭或卸载时,它就会收到通知。
  • 接口方法
    • void contextInitialized(ServletContextEvent sce):当Web应用程序启动时,此方法会被调用。我们可以在这里执行全局性初始化工作。例如:
      • 初始化数据库连接池
      • 加载应用程序的全局配置参数
      • 启动后台服务或定时任务
      • 初始化日志系统
    • void contextDestroyed(ServletContextEvent sce):当Web应用程序关闭或被卸载时,此方法会被调用。我们可以在这里执行资源清理工作,例如:
      • 关闭数据库连接池
      • 释放占用的内存或文件句柄
      • 停止后台服务
  • 使用场景:这是最常用的Listener之一,适用于那些需要在应用程序级别进行一次性设置和清理的任务。

会话(Session)生命周期Listener

这类Listener用于监听用户会话的创建和销毁事件。

HttpSessionListener

  • 作用:监听HTTP会话(HttpSession)的生命周期事件。当用户第一次访问网站并创建会话时,以及当会话过期或被明确销毁时,它就收到通知。
  • 接口方法
    • void sessionCreated(HttpSessionEvent se):当新的Http会话被创建时调用此方法。我们可以用这个事件:
      • 统计在线用户数量
      • 为新会话设置默认属性
    • void sessionDestroyed(HttpSessionEvent se):当HTTP会话失效(过期或被显示调用 invalidate())时调用此方法。我们可以用这个事件来:
      • 统计在线用户数
      • 清理与该会话相关的资源

请求生命周期Listener

这类Listener用于监听每个HTTP请求的开始和结束事件。

ServletRequestListener

  • 作用:监听HTTP请求(ServletRequest)的生命周期事件。在每个HTTP请求被Web容器接收并开始处理时,以及在请求处理完毕即将发送响应回客户端时,它都会收到通知。
  • 接口方法
    • void requestInitialized(ServletRequestEvent sre):当每个HTTP请求开始被处理时调用此方法。我们可以在这里:
      • 记录请求的开始时间,用于性能分析。
      • 初始化与当前请求相关的特定资源。
    • void requestDestroyed(ServletRequestEvent sre):当每个HTTP请求处理完成并即将响应给客户端时调用此方法。我们可以在这里:
      • 记录请求的结束时间并计算处理耗时
      • 清理与当前请求相关的临时资源
  • 使用场景:常用于请求级别的性能监控,日志记录等。

属性变更Listener

这类Listener用于监听特定作用域(ServletContextHttpSessionServletRequest)中属性的添加、移除、替换事件。当调用setAttribute()、removeAttribute()或replaceAttribute()方法时,这些Listener就会被触发。

ServletContextAttributeListener

  • 作用:监听ServletContext(应用程序作用域)中属性的变化。
  • 接口方法
    • attributeAdded(ServletContextAttributeEvent scae)
    • attributeRemoved(ServletContextAttributeEvent scae)
    • attributeReplaced(ServletContextAttributeEvent scae)

HttpSessionAttributeListener

  • 作用:监听HttpSession(会话作用域)中属性的变化
  • 接口方法
    • attributeAdded(HttpSessionBindingEvent hsbe)
    • attributeRemoved(HttpSessionBindingEvent hsbe)
    • attributeReplaced(HttpSessionBindingEvent hsbe)

ServletRequestAttributeListener

  • 作用:监听ServletRequest(请求作用域)中属性的变化
  • 接口方法
    • attributeAdded(ServletRequestAttributeEvent srae)
    • attributeRemoved(ServletRequestAttributeEvent srae)
    • attributeReplaced(ServletRequestAttributeEvent srae)

使用场景:用于监控和响应特定作用域内数据的动态变化,例如:

  • 当某个关键配置属性在应用程序级别被修改时,触发重新加载
  • 跟踪会话中用户登录状态的变化
  • 请求处理的过程中,根据属性变化执行特定逻辑

会话绑定Listener

这类Listener专注于监听Java对象何时被绑定到 HttpSession或从 HttpSession中解除绑定。

HttpSessionBindingListener

  • 作用:这是一个特殊的Listener,它不是由容器注册的。而是被绑定到Session的Java对象本身实现。当一个实现了此接口的对象被HttpSession.setAttribute()存储到Session中时,其valueBound()方法会被调用;当它被HttpSession.removeAttribute()或Session失效时,其valueUnbound()方法会被调用。
  • 接口方法
    • void valueBound(HttpSessionBindingEvent event)
    • void valueUnbound(HttpSessionBindingEvent event)
  • 使用场景:当某个对象需要知道它何时被放入或移除Session时非常有用,例如:
    • 一个用户对象,当它被绑定到Session时,可以更新数据库中的在线状态。
    • 一个资源句柄,当它被从Session移除时,可以自动释放资源。

Listener与原来执行代码的执行顺序

当Listener被触发时,它与原来的代码之间,通常是顺序执行的关系,而不是并行执行。

  1. 请求生命周期:请求开始(调用Listener)->进入Filter链->进入Servlet->离开Servlet->离开Filter->请求结束(调用Listener)
  2. 属性变更setAttribute()等方法本身会先执行,完成属性的设置操作,然后相关的Listener才会被触发。

创建与配置Listener类

创建Listener类

步骤

  1. 创建一个普通的Java类
  2. 让这个类实现你想要监听的事件对应的Listener接口
  3. 重写该接口中定义的所有方法,并在这些方法中编写你的业务逻辑

示例:

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
package com.example.listeners; // 建议放在一个专门的包中

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener; // 如果你想使用注解方式配置

// 使用注解配置方式时需要添加此注解
// @WebListener
public class MyApplicationListener implements ServletContextListener {

/**
* 当Web应用程序启动时调用此方法。
* @param sce ServletContextEvent 事件对象,可以获取ServletContext
*/
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("🎉🎉🎉 Web应用程序启动啦!在 " + sce.getServletContext().getContextPath() + ".");
// 在这里可以执行数据库连接池初始化、加载配置文件等全局性设置
// ServletContext context = sce.getServletContext();
// context.setAttribute("myGlobalData", "Some initialized data");
}

/**
* 当Web应用程序关闭时调用此方法。
* @param sce ServletContextEvent 事件对象
*/
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("👋👋👋 Web应用程序关闭了。清理资源...");
// 在这里可以执行关闭数据库连接、释放内存等资源清理工作
}
}

配置Listener

创建好Listener类后,你需要告诉Servlet容器你的这个类是一个Listener,并且希望它在特定的事件发生时被调用。

方法一:使用 web.xml文件配置(传统方式)

这是在Servlet3.0之前以及需要最大兼容性时使用的标准方式。

  1. <web-app>标签的内部添加 <listener>标签
  2. <listener>标签内部,使用 <listener-class>标签指定Listener类的完全限定名(包括包名)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">

<display-name>MyWebAppWithListeners</display-name>

<listener>
<listener-class>com.example.listeners.MyApplicationListener</listener-class>
</listener>

</web-app>

方法二:使用注解(Annotations)配置

从Servlet3.0开始,你可以使用 @WebListener注解来简化Listener的配置,无需修改web.xml。这是目前主流更简洁的方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.example.listeners;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener; // 导入WebListener注解

@WebListener // 标记这个类为一个Web Listener
public class MyApplicationListener implements ServletContextListener {

@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("🎉🎉🎉 Web应用程序启动啦!(通过注解配置) 在 " + sce.getServletContext().getContextPath() + ".");
}

@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("👋👋👋 Web应用程序关闭了。(通过注解配置) 清理资源...");
}
}

简单实战

xxy1103/javaWeb: 一个简单的Servlet、Filter、Listener的演示示例。一个简单的登录验证系统,同时统计登录在线人数。


java-Web基础之Servlet、Filter、Listener
http://blog.ulna520.com/2025/05/19/java-Web基础_20250519_165356/
Veröffentlicht am
May 19, 2025
Urheberrechtshinweis