JavaWeb快速入门: Filter与Listener核心详解

本文纲要

  1. Filter概述
  2. Filter快速入门
    开发步骤
    项目结构
    核心代码
  3. Filter执行流程
    流程图解
    代码演示
    数据处理时机
  4. Filter拦截路径配置
    四种配置方式
    示例演示
  5. 过滤器链
    概念与执行流程
    优先级规则
    示例代码与输出
  6. 案例:登录验证过滤器
    需求分析
    项目结构
    登录验证流程
    完整代码解析
  7. Listener概述
  8. Listener基本使用
    开发步骤
    代码示例
  9. 总结

Filter概述

在JavaWeb中,有三大核心组件:ServletFilterListener

Filter(过滤器) 的作用是拦截对Web资源的请求,并在请求到达目标资源前或响应返回客户端前进行一些通用的、重复性的处理。

常见应用场景包括:

  • 登录验证:统一判断用户是否已登录
  • 权限控制:细粒度访问权限校验
  • 统一编码处理:避免每个Servlet重复设置编码
  • 敏感词过滤:对请求参数或响应内容进行过滤

过滤器的工作模型如下:
#mermaid-svg-qLNoMtmtn1PggUZr{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-qLNoMtmtn1PggUZr .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-qLNoMtmtn1PggUZr .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-qLNoMtmtn1PggUZr .error-icon{fill:#552222;}#mermaid-svg-qLNoMtmtn1PggUZr .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-qLNoMtmtn1PggUZr .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-qLNoMtmtn1PggUZr .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-qLNoMtmtn1PggUZr .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-qLNoMtmtn1PggUZr .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-qLNoMtmtn1PggUZr .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-qLNoMtmtn1PggUZr .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-qLNoMtmtn1PggUZr .marker{fill:#333333;stroke:#333333;}#mermaid-svg-qLNoMtmtn1PggUZr .marker.cross{stroke:#333333;}#mermaid-svg-qLNoMtmtn1PggUZr svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-qLNoMtmtn1PggUZr p{margin:0;}#mermaid-svg-qLNoMtmtn1PggUZr .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-qLNoMtmtn1PggUZr .cluster-label text{fill:#333;}#mermaid-svg-qLNoMtmtn1PggUZr .cluster-label span{color:#333;}#mermaid-svg-qLNoMtmtn1PggUZr .cluster-label span p{background-color:transparent;}#mermaid-svg-qLNoMtmtn1PggUZr .label text,#mermaid-svg-qLNoMtmtn1PggUZr span{fill:#333;color:#333;}#mermaid-svg-qLNoMtmtn1PggUZr .node rect,#mermaid-svg-qLNoMtmtn1PggUZr .node circle,#mermaid-svg-qLNoMtmtn1PggUZr .node ellipse,#mermaid-svg-qLNoMtmtn1PggUZr .node polygon,#mermaid-svg-qLNoMtmtn1PggUZr .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-qLNoMtmtn1PggUZr .rough-node .label text,#mermaid-svg-qLNoMtmtn1PggUZr .node .label text,#mermaid-svg-qLNoMtmtn1PggUZr .image-shape .label,#mermaid-svg-qLNoMtmtn1PggUZr .icon-shape .label{text-anchor:middle;}#mermaid-svg-qLNoMtmtn1PggUZr .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-qLNoMtmtn1PggUZr .rough-node .label,#mermaid-svg-qLNoMtmtn1PggUZr .node .label,#mermaid-svg-qLNoMtmtn1PggUZr .image-shape .label,#mermaid-svg-qLNoMtmtn1PggUZr .icon-shape .label{text-align:center;}#mermaid-svg-qLNoMtmtn1PggUZr .node.clickable{cursor:pointer;}#mermaid-svg-qLNoMtmtn1PggUZr .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-qLNoMtmtn1PggUZr .arrowheadPath{fill:#333333;}#mermaid-svg-qLNoMtmtn1PggUZr .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-qLNoMtmtn1PggUZr .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-qLNoMtmtn1PggUZr .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-qLNoMtmtn1PggUZr .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-qLNoMtmtn1PggUZr .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-qLNoMtmtn1PggUZr .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-qLNoMtmtn1PggUZr .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-qLNoMtmtn1PggUZr .cluster text{fill:#333;}#mermaid-svg-qLNoMtmtn1PggUZr .cluster span{color:#333;}#mermaid-svg-qLNoMtmtn1PggUZr div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-qLNoMtmtn1PggUZr .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-qLNoMtmtn1PggUZr rect.text{fill:none;stroke-width:0;}#mermaid-svg-qLNoMtmtn1PggUZr .icon-shape,#mermaid-svg-qLNoMtmtn1PggUZr .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-qLNoMtmtn1PggUZr .icon-shape p,#mermaid-svg-qLNoMtmtn1PggUZr .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-qLNoMtmtn1PggUZr .icon-shape .label rect,#mermaid-svg-qLNoMtmtn1PggUZr .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-qLNoMtmtn1PggUZr .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-qLNoMtmtn1PggUZr .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-qLNoMtmtn1PggUZr :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 拦截
客户端请求
Filter
预处理
目标资源
后处理
客户端响应

凡是访问Web应用中的资源(Servlet、JSP、HTML等),都可以先经过过滤器,再决定是否放行。

Filter快速入门

1 ) 开发步骤

  1. 定义一个类,实现 javax.servlet.Filter 接口
  2. 重写三个方法:init()doFilter()destroy()
    • init():初始化时调用
    • doFilter():每次拦截请求时执行,是过滤器的核心
    • destroy():销毁时调用
  3. 使用 @WebFilter 注解配置拦截路径(注意:这里的路径是拦截资源的路径,不是过滤器的访问路径)
  4. doFilter() 中编写逻辑,并通过 chain.doFilter(request, response) 放行

2 ) 项目结构

dir 复制代码
filter-demo 
├── pom.xml 
├── src 
│   └── main 
│       ├── java/com/wb/web/filter 
│       │   ├── FilterDemo.java 
│       │   ├── FilterDemo2.java 
│       │   └── AFilterDemo2.java 
│       └── webapp 
│           ├── hello.jsp 
│           ├── index.jsp 
│           └── WEB-INF/web.xml 

3 ) 核心代码

3.1 定义一个简单的过滤器

java 复制代码
package com.wb.web.filter;
 
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
 
@WebFilter("/*")
public class FilterDemo implements Filter {
 
    @Override 
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
            throws IOException, ServletException {
        // 1. 放行前,对 request 数据进行处理 
        System.out.println("1.FilterDemo...");
 
        // 放行 
        chain.doFilter(request, response);
 
        // 2. 放行后,对 Response 数据进行处理 
        System.out.println("5.FilterDemo...");
    }
 
    @Override 
    public void init(FilterConfig filterConfig) throws ServletException { }
 
    @Override 
    public void destroy() { }
}

注意:@WebFilter("/*") 表示拦截所有资源。如果拦截后不调用 chain.doFilter(),请求将不会到达目标资源。

3.2 目标 JSP(hello.jsp)

jsp 复制代码
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head><title>Title</title></head>
<body>
    <h1>hello JSP~</h1>
    <%
        System.out.println("3.hello jsp");
    %>
</body>
</html>

访问 hello.jsp 时,控制台输出顺序为:1.FilterDemo... → 3.hello jsp → 5.FilterDemo...,证明过滤器先执行放行前逻辑,再访问资源,最后执行放行后逻辑。

Filter执行流程

过滤器的执行流程可以概括为:放行前逻辑 → 放行 → 访问资源 → 放行后逻辑
目标资源 过滤器 客户端 目标资源 过滤器 客户端 #mermaid-svg-zK2SnYWWxzUPKTAN{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-zK2SnYWWxzUPKTAN .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-zK2SnYWWxzUPKTAN .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-zK2SnYWWxzUPKTAN .error-icon{fill:#552222;}#mermaid-svg-zK2SnYWWxzUPKTAN .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-zK2SnYWWxzUPKTAN .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-zK2SnYWWxzUPKTAN .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-zK2SnYWWxzUPKTAN .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-zK2SnYWWxzUPKTAN .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-zK2SnYWWxzUPKTAN .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-zK2SnYWWxzUPKTAN .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-zK2SnYWWxzUPKTAN .marker{fill:#333333;stroke:#333333;}#mermaid-svg-zK2SnYWWxzUPKTAN .marker.cross{stroke:#333333;}#mermaid-svg-zK2SnYWWxzUPKTAN svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-zK2SnYWWxzUPKTAN p{margin:0;}#mermaid-svg-zK2SnYWWxzUPKTAN .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-zK2SnYWWxzUPKTAN text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-zK2SnYWWxzUPKTAN .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-zK2SnYWWxzUPKTAN .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-zK2SnYWWxzUPKTAN .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-zK2SnYWWxzUPKTAN .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-zK2SnYWWxzUPKTAN #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-zK2SnYWWxzUPKTAN .sequenceNumber{fill:white;}#mermaid-svg-zK2SnYWWxzUPKTAN #sequencenumber{fill:#333;}#mermaid-svg-zK2SnYWWxzUPKTAN #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-zK2SnYWWxzUPKTAN .messageText{fill:#333;stroke:none;}#mermaid-svg-zK2SnYWWxzUPKTAN .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-zK2SnYWWxzUPKTAN .labelText,#mermaid-svg-zK2SnYWWxzUPKTAN .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-zK2SnYWWxzUPKTAN .loopText,#mermaid-svg-zK2SnYWWxzUPKTAN .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-zK2SnYWWxzUPKTAN .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-zK2SnYWWxzUPKTAN .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-zK2SnYWWxzUPKTAN .noteText,#mermaid-svg-zK2SnYWWxzUPKTAN .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-zK2SnYWWxzUPKTAN .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-zK2SnYWWxzUPKTAN .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-zK2SnYWWxzUPKTAN .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-zK2SnYWWxzUPKTAN .actorPopupMenu{position:absolute;}#mermaid-svg-zK2SnYWWxzUPKTAN .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-zK2SnYWWxzUPKTAN .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-zK2SnYWWxzUPKTAN .actor-man circle,#mermaid-svg-zK2SnYWWxzUPKTAN line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-zK2SnYWWxzUPKTAN :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 放行前逻辑 执行业务逻辑 放行后逻辑 请求chain.doFilter() 放行返回响应

代码演示

在过滤器 FilterDemo 和目标 JSP 中分别打印标记:

java 复制代码
// FilterDemo.java 
System.out.println("1.FilterDemo...");   // 放行前 
chain.doFilter(request, response);
System.out.println("5.FilterDemo...");   // 放行后 
jsp 复制代码
<%-- hello.jsp --%>
<%
    System.out.println("3.hello jsp");
%>

输出结果:1.FilterDemo... → 3.hello jsp → 5.FilterDemo...,验证了上述执行顺序。

数据处理时机

  • 放行前:一般对 request 数据进行处理(如编码设置、参数过滤)。因为此时 response 还没有业务数据。
  • 放行后:一般对 response 数据进行处理(如压缩、敏感词替换)。因为资源执行完毕后 response 中才填充了真正的响应内容。

Filter拦截路径配置

使用 @WebFilter 注解的 value 属性(或 urlPatterns)配置拦截路径,支持四种方式:

配置方式 示例 说明
具体资源路径拦截 "/login.jsp" 只有访问 login.jsp 时过滤器才生效
目录拦截 "/user/*" 访问 /user/ 下所有资源时触发
后缀名拦截 "*.jsp" 访问所有 .jsp 后缀资源时触发
拦截所有 "/*" 任何资源请求都会被过滤(最常用)

示例演示

1 ) 拦截特定资源

java 复制代码
@WebFilter("/hello.jsp")
public class FilterDemo2 implements Filter {
    @Override 
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
            throws IOException, ServletException {
        System.out.println("FilterDemo2 executed...");
        chain.doFilter(request, response);
    }
    //...
}

此时只有访问 hello.jsp 时控制台才输出信息,访问 index.jsp 则不触发过滤器。

2 ) 拦截所有资源

java 复制代码
@WebFilter("/*")
public class FilterDemo implements Filter { ... }

这种方式在统一登录验证、编码处理等场景下最常用。

过滤器链

1 ) 概念与执行流程

一个Web应用可以配置多个过滤器 ,它们按照一定顺序形成一个过滤器链 。请求会依次经过每个过滤器,直到最后一个过滤器放行后到达目标资源,返回时再按相反顺序执行放行后的逻辑。
#mermaid-svg-CU7q0TjSKFTdvFCm{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-CU7q0TjSKFTdvFCm .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-CU7q0TjSKFTdvFCm .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-CU7q0TjSKFTdvFCm .error-icon{fill:#552222;}#mermaid-svg-CU7q0TjSKFTdvFCm .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-CU7q0TjSKFTdvFCm .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-CU7q0TjSKFTdvFCm .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-CU7q0TjSKFTdvFCm .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-CU7q0TjSKFTdvFCm .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-CU7q0TjSKFTdvFCm .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-CU7q0TjSKFTdvFCm .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-CU7q0TjSKFTdvFCm .marker{fill:#333333;stroke:#333333;}#mermaid-svg-CU7q0TjSKFTdvFCm .marker.cross{stroke:#333333;}#mermaid-svg-CU7q0TjSKFTdvFCm svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-CU7q0TjSKFTdvFCm p{margin:0;}#mermaid-svg-CU7q0TjSKFTdvFCm .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-CU7q0TjSKFTdvFCm .cluster-label text{fill:#333;}#mermaid-svg-CU7q0TjSKFTdvFCm .cluster-label span{color:#333;}#mermaid-svg-CU7q0TjSKFTdvFCm .cluster-label span p{background-color:transparent;}#mermaid-svg-CU7q0TjSKFTdvFCm .label text,#mermaid-svg-CU7q0TjSKFTdvFCm span{fill:#333;color:#333;}#mermaid-svg-CU7q0TjSKFTdvFCm .node rect,#mermaid-svg-CU7q0TjSKFTdvFCm .node circle,#mermaid-svg-CU7q0TjSKFTdvFCm .node ellipse,#mermaid-svg-CU7q0TjSKFTdvFCm .node polygon,#mermaid-svg-CU7q0TjSKFTdvFCm .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-CU7q0TjSKFTdvFCm .rough-node .label text,#mermaid-svg-CU7q0TjSKFTdvFCm .node .label text,#mermaid-svg-CU7q0TjSKFTdvFCm .image-shape .label,#mermaid-svg-CU7q0TjSKFTdvFCm .icon-shape .label{text-anchor:middle;}#mermaid-svg-CU7q0TjSKFTdvFCm .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-CU7q0TjSKFTdvFCm .rough-node .label,#mermaid-svg-CU7q0TjSKFTdvFCm .node .label,#mermaid-svg-CU7q0TjSKFTdvFCm .image-shape .label,#mermaid-svg-CU7q0TjSKFTdvFCm .icon-shape .label{text-align:center;}#mermaid-svg-CU7q0TjSKFTdvFCm .node.clickable{cursor:pointer;}#mermaid-svg-CU7q0TjSKFTdvFCm .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-CU7q0TjSKFTdvFCm .arrowheadPath{fill:#333333;}#mermaid-svg-CU7q0TjSKFTdvFCm .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-CU7q0TjSKFTdvFCm .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-CU7q0TjSKFTdvFCm .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-CU7q0TjSKFTdvFCm .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-CU7q0TjSKFTdvFCm .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-CU7q0TjSKFTdvFCm .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-CU7q0TjSKFTdvFCm .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-CU7q0TjSKFTdvFCm .cluster text{fill:#333;}#mermaid-svg-CU7q0TjSKFTdvFCm .cluster span{color:#333;}#mermaid-svg-CU7q0TjSKFTdvFCm div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-CU7q0TjSKFTdvFCm .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-CU7q0TjSKFTdvFCm rect.text{fill:none;stroke-width:0;}#mermaid-svg-CU7q0TjSKFTdvFCm .icon-shape,#mermaid-svg-CU7q0TjSKFTdvFCm .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-CU7q0TjSKFTdvFCm .icon-shape p,#mermaid-svg-CU7q0TjSKFTdvFCm .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-CU7q0TjSKFTdvFCm .icon-shape .label rect,#mermaid-svg-CU7q0TjSKFTdvFCm .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-CU7q0TjSKFTdvFCm .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-CU7q0TjSKFTdvFCm .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-CU7q0TjSKFTdvFCm :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 客户端请求
Filter1 放行前
Filter2 放行前
目标资源
Filter2 放行后
Filter1 放行后
客户端响应

2 ) 优先级规则

当使用 @WebFilter 注解配置时,过滤器的执行顺序由类名字符串的自然排序决定。例如:

  • AFilterDemo2 会优于 FilterDemo 执行
  • FilterDemo 会优于 FilterDemo2 执行(因为 Demo < Demo2

3 ) 示例代码与输出

过滤器1:FilterDemo

java 复制代码
@WebFilter("/*")
public class FilterDemo implements Filter {
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
            throws IOException, ServletException {
        System.out.println("1.FilterDemo...");
        chain.doFilter(request, response);
        System.out.println("5.FilterDemo...");
    }
    //...
}

过滤器2:FilterDemo2

java 复制代码
@WebFilter("/*")
public class FilterDemo2 implements Filter {
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
            throws IOException, ServletException {
        System.out.println("2.FilterDemo...");
        chain.doFilter(request, response);
        System.out.println("4.FilterDemo...");
    }
    //...
}

访问任意资源时,控制台输出:

log 复制代码
1.FilterDemo...
2.FilterDemo...
3.hello jsp 
4.FilterDemo...
5.FilterDemo...

体现了 FilterDemo(类名字典序更小)先执行,且放行后按反向顺序执行。

案例:登录验证过滤器

1 ) 需求分析

在没有登录验证时,用户可以不登录直接访问品牌列表等受保护资源,登录功能形同虚设。

我们需要编写一个全局过滤器,对所有资源进行登录校验:

  • 已登录:放行
  • 未登录:跳转到登录页面,并提示"您尚未登录"

同时,必须将登录/注册相关的资源直接放行,否则这些页面的CSS、图片等也会被拦截,导致无法登录。

2 ) 项目结构

dir 复制代码
brand-demo 
├── pom.xml 
├── src/main/java/com/wb 
│   ├── web 
│   │   ├── filter 
│   │   │   └── LoginFilter.java         # 登录验证过滤器 
│   │   ├── listener 
│   │   │   └── ContextLoaderListener.java # 监听器示例 
│   │   ├── LoginServlet.java 
│   │   ├── RegisterServlet.java 
│   │   ├── SelectAllServlet.java 
│   │   └── ... (其他Servlet)
│   └── ... (service, mapper, pojo, util)
└── src/main/webapp 
    ├── login.jsp 
    ├── register.jsp 
    ├── brand.jsp 
    └── ...

3 ) 登录验证流程
#mermaid-svg-x0xnHjv5ynDbDzf5{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-x0xnHjv5ynDbDzf5 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-x0xnHjv5ynDbDzf5 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-x0xnHjv5ynDbDzf5 .error-icon{fill:#552222;}#mermaid-svg-x0xnHjv5ynDbDzf5 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-x0xnHjv5ynDbDzf5 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-x0xnHjv5ynDbDzf5 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-x0xnHjv5ynDbDzf5 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-x0xnHjv5ynDbDzf5 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-x0xnHjv5ynDbDzf5 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-x0xnHjv5ynDbDzf5 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-x0xnHjv5ynDbDzf5 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-x0xnHjv5ynDbDzf5 .marker.cross{stroke:#333333;}#mermaid-svg-x0xnHjv5ynDbDzf5 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-x0xnHjv5ynDbDzf5 p{margin:0;}#mermaid-svg-x0xnHjv5ynDbDzf5 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-x0xnHjv5ynDbDzf5 .cluster-label text{fill:#333;}#mermaid-svg-x0xnHjv5ynDbDzf5 .cluster-label span{color:#333;}#mermaid-svg-x0xnHjv5ynDbDzf5 .cluster-label span p{background-color:transparent;}#mermaid-svg-x0xnHjv5ynDbDzf5 .label text,#mermaid-svg-x0xnHjv5ynDbDzf5 span{fill:#333;color:#333;}#mermaid-svg-x0xnHjv5ynDbDzf5 .node rect,#mermaid-svg-x0xnHjv5ynDbDzf5 .node circle,#mermaid-svg-x0xnHjv5ynDbDzf5 .node ellipse,#mermaid-svg-x0xnHjv5ynDbDzf5 .node polygon,#mermaid-svg-x0xnHjv5ynDbDzf5 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-x0xnHjv5ynDbDzf5 .rough-node .label text,#mermaid-svg-x0xnHjv5ynDbDzf5 .node .label text,#mermaid-svg-x0xnHjv5ynDbDzf5 .image-shape .label,#mermaid-svg-x0xnHjv5ynDbDzf5 .icon-shape .label{text-anchor:middle;}#mermaid-svg-x0xnHjv5ynDbDzf5 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-x0xnHjv5ynDbDzf5 .rough-node .label,#mermaid-svg-x0xnHjv5ynDbDzf5 .node .label,#mermaid-svg-x0xnHjv5ynDbDzf5 .image-shape .label,#mermaid-svg-x0xnHjv5ynDbDzf5 .icon-shape .label{text-align:center;}#mermaid-svg-x0xnHjv5ynDbDzf5 .node.clickable{cursor:pointer;}#mermaid-svg-x0xnHjv5ynDbDzf5 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-x0xnHjv5ynDbDzf5 .arrowheadPath{fill:#333333;}#mermaid-svg-x0xnHjv5ynDbDzf5 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-x0xnHjv5ynDbDzf5 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-x0xnHjv5ynDbDzf5 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-x0xnHjv5ynDbDzf5 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-x0xnHjv5ynDbDzf5 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-x0xnHjv5ynDbDzf5 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-x0xnHjv5ynDbDzf5 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-x0xnHjv5ynDbDzf5 .cluster text{fill:#333;}#mermaid-svg-x0xnHjv5ynDbDzf5 .cluster span{color:#333;}#mermaid-svg-x0xnHjv5ynDbDzf5 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-x0xnHjv5ynDbDzf5 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-x0xnHjv5ynDbDzf5 rect.text{fill:none;stroke-width:0;}#mermaid-svg-x0xnHjv5ynDbDzf5 .icon-shape,#mermaid-svg-x0xnHjv5ynDbDzf5 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-x0xnHjv5ynDbDzf5 .icon-shape p,#mermaid-svg-x0xnHjv5ynDbDzf5 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-x0xnHjv5ynDbDzf5 .icon-shape .label rect,#mermaid-svg-x0xnHjv5ynDbDzf5 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-x0xnHjv5ynDbDzf5 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-x0xnHjv5ynDbDzf5 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-x0xnHjv5ynDbDzf5 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是



收到请求
请求路径是否

与登录/注册相关?
直接放行
Session中

是否有user对象?
放行
存储提示信息

转发到login.jsp

4 ) 完整代码解析

LoginFilter.java

java 复制代码
package com.wb.web.filter;
 
import javax.servlet.*;
import javax.servlet.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.io.IOException;
 
@WebFilter("/*")
public class LoginFilter implements Filter {
    @Override 
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
            throws ServletException, IOException {
        HttpServletRequest req = (HttpServletRequest) request;
 
        // 1. 判断访问路径是否与登录/注册相关(应该直接放行)
        String[] urls = {"/login.jsp","/imgs/","/css/","/loginServlet",
                         "/register.jsp","/registerServlet","/checkCodeServlet"};
        String url = req.getRequestURL().toString();
 
        for (String u : urls) {
            if (url.contains(u)) {
                // 直接放行,不再执行后续判断 
                chain.doFilter(request, response);
                return;
            }
        }
 
        // 2. 判断Session中是否存在user对象 
        HttpSession session = req.getSession();
        Object user = session.getAttribute("user");
 
        if (user != null) {
            // 已登录,放行 
            chain.doFilter(request, response);
        } else {
            // 未登录,存储提示信息并跳转到登录页 
            req.setAttribute("login_msg", "您尚未登录!");
            req.getRequestDispatcher("/login.jsp").forward(req, response);
        }
    }
 
    public void init(FilterConfig config) throws ServletException {}
    public void destroy() {}
}

要点分析:

  • 先将 ServletRequest 强转为 HttpServletRequest,以便获取 Session 和请求路径。
  • 白名单路径使用 return 直接退出方法,避免代码继续向下执行。
  • 如果用户未登录,设置 login_msg 属性后转发到 login.jsp,前端通过 ${loginmsg} 显示提示。
  • 登录成功后,LoginServlet 会将 user 对象存入 Session,后续请求就能通过过滤器。

5 ) 登录成功后设置 Session:

java 复制代码
// LoginServlet 片段 
HttpSession session = request.getSession();
session.setAttribute("user", user);

这样,过滤器就可以通过 session.getAttribute("user") 判断登录状态。

Listener概述

Listener(监听器) 是 JavaWeb 三大组件之一,用于监听 ServletContextHttpSessionServletRequest 等域对象的创建销毁 以及属性变化。当监听到指定事件时会自动执行相应方法。

虽然现在监听器使用频率不如 Filter 和 Servlet 高,但在框架初始化等场景下仍非常重要(例如 Spring 利用监听器加载上下文)。

JavaWeb 提供了 8 个监听器接口

监听对象 监听器接口(创建/销毁) 监听器接口(属性变化)
ServletContext ServletContextListener ServletContextAttributeListener
HttpSession HttpSessionListener HttpSessionAttributeListener
HttpSession HttpSessionActivationListener(钝化/活化) HttpSessionBindingListener(绑定)
ServletRequest ServletRequestListener ServletRequestAttributeListener

本文重点介绍最常用的 ServletContextListener

Listener基本使用

1 ) 开发步骤

  1. 定义一个类,实现对应的监听器接口(如 ServletContextListener
  2. 重写响应方法(如 contextInitialized()contextDestroyed()
  3. 添加 @WebListener 注解(无需配置路径,自动生效)

2 ) 代码示例

ContextLoaderListener.java

java 复制代码
package com.wb.web.listener;
 
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
 
@WebListener 
public class ContextLoaderListener implements ServletContextListener {
    @Override 
    public void contextInitialized(ServletContextEvent sce) {
        // Web应用启动时加载资源 
        System.out.println("ContextLoaderListener...");
    }
 
    @Override 
    public void contextDestroyed(ServletContextEvent sce) {
        // Web应用关闭时释放资源 
    }
}

当应用启动时,控制台会输出 ContextLoaderListener...,表明监听器已自动执行。

ServletContext 对象代表整个Web应用,它的初始化标志着应用加载成功,因此可以在 contextInitialized() 中执行一些全局初始化操作(如加载配置文件、建立数据库连接池等)。

总结

  • Filter(过滤器) :拦截请求,实现通用预处理和后处理,通过 @WebFilter 配置拦截路径,通过 chain.doFilter() 放行,多个过滤器形成链式调用,执行顺序由类名的自然排序决定。
  • Listener(监听器) :监听域对象的生命周期和属性变化,通过 @WebListener 自动注册,常用于应用启动时的资源加载。
  • 两者配合 Servlet 构成了 JavaWeb 开发的三大基石,理解它们能够帮助我们编写更加优雅、可维护的 Web 应用。

以上便是 JavaWeb 中 FilterListener 的核心知识,通过本文的学习,你已经掌握了过滤器的开发、执行流程、路径配置、过滤器链以及实战登录验证案例,同时也了解了监听器的基本用法。

在实际项目中,这些组件将极大程度地简化重复代码,提升开发效率。

相关推荐
zfoo-framework1 小时前
mongodb性能调优 1.从慢查询分析索引 2.掌握索引最左匹配原则!!!
java
AIGS0011 小时前
企业AI落地:从RAG到AgentRAG的技术跃迁
java·人工智能·人工智能ai大模型应用
2601_962341302 小时前
计算机毕业设计之jsp考研在线复习平台
java·大数据·开发语言·hadoop·python·考研·课程设计
huangjiazhi_2 小时前
纯C++实现ini文件操作
java·后端·spring
zhixingheyi_tian2 小时前
JVM 之 GCLocker
java·jvm
码哥字节3 小时前
把 Cursor 的烂输出变成好代码,这 10 个 Prompt 改造让我省了 80% 改稿时间
java·ai编程工具·cursor 配置·prompt 技巧
2503_931712483 小时前
10m/s超高速电梯:西奥XO-NEWIII如何树立行业速度标杆
java·大数据·数据库
折哥的程序人生 · 物流技术专研3 小时前
第4篇:抽象工厂模式——产品族一键创建
java·设计模式·抽象工厂模式·创建型模式·编程进阶·产品族·扩充系列