java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > JavaWeb过滤器与监听器

JavaWeb过滤器与监听器实战指南

作者:CarIise

本文系统讲解JavaWeb中过滤器(Filter)和监听器(Listener)的核心概念、生命周期、配置方式及典型应用场景,过滤器用于拦截请求并控制是否放行,支持预处理、条件拦截和后处理;重点演示了通过Filter解决POST中文乱码问题,感兴趣的朋友跟随小编一起看看吧

第一部分:过滤器(Filter)基础

1.1 过滤器的概念

⭐ 核心强调:过滤器是JavaWeb三大组件之一,与Servlet很相似。它是用来拦截请求的,而不是处理请求的。

请求处理流程: 当用户请求某个Servlet时,会先执行部署在这个请求上的Filter。

⭐ 核心强调: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 的匹配规则一模一样:

  1. 完全路径匹配:以 / 开头,如 /aaa、/aaa/bbb
  2. 目录匹配:以 / 开头,如 /aaa/*、/*
  3. 扩展名匹配:不能以 / 开头,如 *.do、*.action

1.3 生命周期与核心对象

Filter 的三大方法:

方法执行时机执行次数
init(FilterConfig)服务器启动时创建实例后调用仅一次
doFilter(...)用户每次访问目标资源时调用多次
destroy()服务器关闭销毁实例前调用仅一次

FilterConfig 的作用(与 ServletConfig 相似):

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 应用场景

  1. 预处理:在目标资源执行前做准备工作(如设置编码),通常都会放行。
  2. 条件拦截:判断是否放行,如校验用户是否登录、IP是否被禁用。
  3. 后处理:在目标资源执行后,对输出的数据进行特殊处理。

第二部分:过滤器实战 —— 解决 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。

分类:

  1. 监听域对象**"创建"与"销毁"**的监听器
  2. 监听域对象**"操作域属性"**的监听器
  3. 监听 HttpSession 的监听器

3.2 常用监听器接口

监听器接口监听对象触发时机
ServletContextListener整个应用启动/关闭时触发
HttpSessionListener会话创建/销毁 Session 时触发
ServletRequestListener请求创建/销毁 Request 时触发

方法说明:

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);
        }
    }
}

⭐ 核心强调:

  1. 在线人数必须存在 ServletContext(Application 域),所有用户共享。
  2. 统计的逻辑是:有人新建 Session 就 +1,有人销毁 Session 就 -1。
  3. 注意 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过滤器与监听器内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文