JavaWeb过滤器与监听器实战指南
作者:CarIise
第一部分:过滤器(Filter)基础
1.1 过滤器的概念
⭐ 核心强调:过滤器是JavaWeb三大组件之一,与Servlet很相似。它是用来拦截请求的,而不是处理请求的。
请求处理流程: 当用户请求某个Servlet时,会先执行部署在这个请求上的Filter。
- 如果Filter "放行" → 继续执行用户请求的Servlet
- 如果Filter "不放行" → Servlet将不会被执行
⭐ 核心强调:Filter来决定是否调用Servlet!当执行完成Servlet的代码后,还会回到Filter执行后面的代码。很多人错误地认为请求输出给客户端就算结束了,其实很多事情需要在给客户端响应之后才能完成!
单个过滤器示意图:
用户端(浏览器) ──请求──> 过滤器Filter ──放行──> Servlet
<─响应── <─响应──多个过滤器示意图:
浏览器 ──> Filter 1 ──> Filter 2 ──> Servlet
<── <── <──
执行顺序:1前 → 2前 → Servlet → 2后 → 1后1.2 快速入门示例
1. 实现 Filter 接口
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
System.out.println("Hello Filter!!!");
// 放行:让请求继续往下走,访问目标资源
filterChain.doFilter(servletRequest, servletResponse);
System.out.println("filter 执行完毕了!");
}
@Override
public void destroy() {}
}2. 在 web.xml 中配置
<filter>
<filter-name>firstFilter</filter-name>
<filter-class>cn.tx.filter.MyFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>firstFilter</filter-name>
<url-pattern>/*</url-pattern> <!-- 拦截所有请求 -->
</filter-mapping>⭐ 核心强调:url-pattern 和 Servlet 的匹配规则一模一样:
- 完全路径匹配:以
/开头,如/aaa、/aaa/bbb - 目录匹配:以
/开头,如/aaa/*、/* - 扩展名匹配:不能以
/开头,如*.do、*.action
1.3 生命周期与核心对象
Filter 的三大方法:
| 方法 | 执行时机 | 执行次数 |
|---|---|---|
init(FilterConfig) | 服务器启动时创建实例后调用 | 仅一次 |
doFilter(...) | 用户每次访问目标资源时调用 | 多次 |
destroy() | 服务器关闭销毁实例前调用 | 仅一次 |
FilterConfig 的作用(与 ServletConfig 相似):
getServletContext():获取 ServletContextgetFilterName():获取 Filter 名称getInitParameter(String name):获取初始化参数getInitParameterNames():获取所有初始化参数名称
web.xml 配置初始化参数:
<filter>
<filter-name>firstFilter</filter-name>
<filter-class>cn.tx.filter.MyFilter</filter-class>
<init-param>
<param-name>encode</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>FilterChain 的作用: FilterChain 只有一个方法 doFilter(ServletRequest, ServletResponse)。
⭐ 核心强调:调用该方法的意思是 "我(当前 Filter)放行了",但不代表其他人(其他过滤器)也放行。它表示执行下一个过滤器的
doFilter方法,或者执行目标资源。
1.4 过滤器执行顺序
⭐ 核心强调:一个目标资源可以指定多个过滤器,执行顺序取决于 web.xml 中的部署顺序(谁先配,谁先执行)。
示例配置:
<!-- FilterA 先部署 -->
<filter>
<filter-name>filterA</filter-name>
<filter-class>cn.txt.filter.FilterA</filter-class>
</filter>
<filter-mapping>...</filter-mapping>
<!-- FilterB 后部署 -->
<filter>
<filter-name>filterB</filter-name>
<filter-class>cn.txt.filter.FilterB</filter-class>
</filter>
<filter-mapping>...</filter-mapping>// Filter A 的 doFilter 方法
public void doFilter(...) {
System.out.println("filter A 被执行了!");
filterChain.doFilter(request, response); // 【关键点:放行,进入下一个环节】
System.out.println("filter A 执行完毕了!"); // 这一步会在最后执行
}
// Filter B 的 doFilter 方法
public void doFilter(...) {
System.out.println("filter B 被执行了!");
filterChain.doFilter(request, response); // 【关键点:放行,进入下一个环节】
System.out.println("filter B 执行完毕了!"); // 这一步会在倒数第二执行
}
// Servlet / Controller 的方法
public void doGet(...) {
System.out.println("servlet 被执行了!!!");
}执行结果:
filter A 被执行了!
filter B 被执行了!
servlet 被执行了!!!
filter B 执行完毕了!
filter A 执行完毕了!
1.5 拦截目标资源的两种方式
方式一:通过 URL 匹配
<url-pattern>/*</url-pattern> <!-- 拦截所有 --> <url-pattern>/admin/*</url-pattern> <!-- 只拦截admin下的资源 -->
方式二:通过 Servlet 名称匹配
<filter-mapping>
<filter-name>myfilter</filter-name>
<servlet-name>myservlet</servlet-name> <!-- 专门拦截名为myservlet的请求 -->
</filter-mapping>1.6 应用场景
- 预处理:在目标资源执行前做准备工作(如设置编码),通常都会放行。
- 条件拦截:判断是否放行,如校验用户是否登录、IP是否被禁用。
- 后处理:在目标资源执行后,对输出的数据进行特殊处理。
第二部分:过滤器实战 —— 解决 POST 中文乱码
前端页面:
<form action="/filter_demo/test/post" method="post">
姓名:<input type="text" name="name"></br>
<input type="submit" value="提交">
</form>Web.xml 配置:
<filter>
<filter-name>encodeFilter</filter-name>
<filter-class>cn.tx.filter.EncoderFilter</filter-class>
<init-param>
<param-name>encode</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodeFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>Filter 代码:
public class EncodeFilter implements Filter {
private String encode;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
this.encode = filterConfig.getInitParameter("encode");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
// 设置请求和响应的编码
servletRequest.setCharacterEncoding(encode);
servletResponse.setContentType("text/html;charSet=" + this.encode);
servletResponse.setCharacterEncoding(this.encode);
// 放行
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {}
}⭐ 核心强调:把编码设置写在 Filter 里,拦截所有请求,相当于把重复代码抽成了通用拦截层,比在每个 Servlet 里写
request.setCharacterEncoding优雅得多。
第三部分:监听器(Listener)
3.1 监听器概述
⭐ 核心强调:JavaWeb 被监听的事件源为三大域对象:
ServletContext、HttpSession、ServletRequest。
分类:
- 监听域对象**"创建"与"销毁"**的监听器
- 监听域对象**"操作域属性"**的监听器
- 监听
HttpSession的监听器
3.2 常用监听器接口
| 监听器接口 | 监听对象 | 触发时机 |
|---|---|---|
ServletContextListener | 整个应用 | 启动/关闭时触发 |
HttpSessionListener | 会话 | 创建/销毁 Session 时触发 |
ServletRequestListener | 请求 | 创建/销毁 Request 时触发 |
方法说明:
contextInitialized(ServletContextEvent evt):ServletContext 对象被创建后调用contextDestroyed(ServletContextEvent evt):ServletContext 对象被销毁前调用sessionCreated(HttpSessionEvent evt):HttpSession 对象被创建后调用sessionDestroyed(HttpSessionEvent evt):HttpSession 对象被销毁前调用requestInitialized(ServletRequestEvent evt):ServletRequest 对象被创建后调用requestDestroyed(ServletRequestEvent evt):ServletRequest 对象被销毁前调用
3.3 监听器使用配置
Web.xml 配置:
<listener>
<listener-class>cn.tx.listener.MyListener</listener-class>
</listener>Listener 代码(实现多个接口):
public class MyListener implements ServletContextListener, HttpSessionListener, ServletRequestListener {
public MyListener() {}
@Override
public void requestInitialized(ServletRequestEvent sre) {
System.out.println("requestInitialized");
}
@Override
public void requestDestroyed(ServletRequestEvent sre) {
System.out.println("requestDestroyed");
}
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("contextInitialized");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("contextDestroyed");
}
}第四部分:实战 —— 使用 HttpSessionListener 统计在线人数
4.1 前端页面
index.jsp(首页):
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>企业信息官网</title>
</head>
<body>
<h1>欢迎登陆:学员</h1>
<h2>当前在线人数:${count}</h2>
<hr>
<a href="logout.jsp" rel="external nofollow" >退出</a>
</body>
</html>logout.jsp(退出页,销毁 Session):
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<%
// 销毁当前会话
request.getSession().invalidate();
%>
<h1>已退出</h1>
</body>
</html>4.2 监听器代码
public class MyListener implements ServletContextListener, HttpSessionListener {
// 定义一个在线人数初始为 0
private Integer count = 0;
public MyListener() {}
// Servlet 容器初始化时,把在线人数存入上下文
public void contextInitialized(ServletContextEvent sce) {
ServletContext context = sce.getServletContext();
context.setAttribute("count", count);
System.out.println("contextInitialized");
}
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("contextDestroyed");
}
// 如果创建了一个 session,那么在线人数加 1
public void sessionCreated(HttpSessionEvent se) {
onLineCount(se.getSession().getServletContext(), true);
System.out.println("sessionCreated");
}
// 如果销毁了一个 session,那么在线人数减 1
public void sessionDestroyed(HttpSessionEvent se) {
onLineCount(se.getSession().getServletContext(), false);
System.out.println("sessionDestroyed");
}
/**
* 添加或删除在线人数
* @param context 上下文对象(Application 域)
* @param isAdd true=加1,false=减1
*/
private void onLineCount(ServletContext context, boolean isAdd) {
if(isAdd) {
count = (Integer) context.getAttribute("count");
context.setAttribute("count", ++count);
} else {
count = (Integer) context.getAttribute("count");
context.setAttribute("count", --count);
}
}
}⭐ 核心强调:
- 在线人数必须存在
ServletContext(Application 域),所有用户共享。 - 统计的逻辑是:有人新建 Session 就 +1,有人销毁 Session 就 -1。
- 注意
sessionDestroyed的方法体里,源码文档中写的是"--count"(字符串),这是笔误,正确逻辑应该是先减减再存入,即--count(如我上面代码所示)。
附录:核心易错点速查表
| 类别 | 易错点 | 正确理解 |
|---|---|---|
| Filter本质 | 以为 Filter 是用来处理请求的 | Filter 是拦截请求的,决定是否放行 |
| 请求结束点 | 以为响应给客户端就结束了 | 响应后还会回到 Filter 执行后续代码 |
| FilterChain | 以为调用就代表所有 Filter 都放行了 | 只代表当前 Filter 放行,执行下一个 |
| Filter顺序 | 以为是按名字或类名排序 | 严格按 web.xml 中部署的顺序 |
| URL匹配 | 扩展名匹配加了 /(如 /*.do) | 扩展名匹配不能以 / 开头(如 *.do) |
| 监听器作用域 | 把在线人数存在 request 或 session 里 | 必须存在 Application (ServletContext) |
| Session销毁 | 手动关浏览器不一定会立刻销毁 Session | 默认是超时销毁,或调用 invalidate() |
| 编码乱码 | Servlet 里挨个设 setCharacterEncoding | 用 Filter 统一拦截设置 |
⭐ 总结:过滤器(Filter)是把关者,负责在请求到达目标资源前进行拦截、过滤或预处理,核心是
FilterChain.doFilter()的放行机制。监听器(Listener)是观察员,不拦截请求,专门监听三大域对象的生命周期和属性变化。两者结合,既能保证应用数据流转的安全(如统一编码),又能实现全局状态的追踪(如在线人数统计)。
到此这篇关于JavaWeb过滤器与监听器实战指南的文章就介绍到这了,更多相关JavaWeb过滤器与监听器内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
