【JavaWeb学习 | 第19篇】Filter过滤器


🌈 个人主页: Hygge_Code
🔥 热门专栏:从0开始学习Java | Linux学习| 计算机网络
💫 个人格言: "既然选择了远方,便不顾风雨兼程"

文章目录

Filter------Web请求的过滤与拦截

一、Filter的核心概念

Filter(过滤器)是运行在服务器端的组件,用于拦截客户端的请求和响应,对请求和响应信息进行预处理或后处理。Filter不直接处理请求,而是对请求进行过滤(如编码转换、登录验证、权限控制)后,将请求传递给后续的Servlet或JSP。

二、Filter的体系结构🤔

三、重要接口 📖

1. Filter接口的核心方法

Filter接口定义了Filter的生命周期方法,由Servlet容器负责调用:

  • 初始化方法:default void init(FilterConfig filterConfig) throws ServletException;------Filter实例创建后调用,用于读取配置参数(默认实现为空,可重写)
  • 过滤方法:void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException;------核心方法,用于拦截和处理请求/响应
  • 销毁方法:default void destroy();------Filter容器关闭时调用,用于释放资源(默认实现为空,可重写)

2. FilterConfig接口的核心方法

FilterConfig接口用于获取Filter的初始化配置信息,核心方法如下:

  • 获取Filter名称:String getFilterName();------返回web.xml中配置的<filter-name>
  • 获取Servlet上下文:ServletContext getServletContext();------返回当前Web应用的ServletContext对象
  • 获取指定初始化参数:String getInitParameter(String parameterName);------返回web.xml中配置的指定名称的初始化参数值
  • 获取所有初始化参数名称:Enumeration<String> getInitParameterNames();------返回所有初始化参数名称的枚举集合

3. FilterChain接口的作用🍂

FilterChain(过滤器链)用于维护多个Filter的执行顺序,核心方法为:void doFilter(ServletRequest req, ServletResponse resp);

  • 当一个Filter处理完请求后,调用chain.doFilter(req, resp)可将请求传递给过滤器链中的下一个Filter
  • 若当前Filter是过滤器链中的最后一个,则将请求传递给目标Servlet或JSP
  • 若未调用chain.doFilter(req, resp),请求将被拦截,不会传递给后续组件

4. HttpFilter抽象类的核心方法

HttpFilter是HTTP协议专用的Filter抽象类,核心方法如下:

  • 重写doFilter(ServletRequest req, ServletResponse resp, FilterChain chain):将请求和响应对象强转为HttpServletRequest和HttpServletResponse,调用重载的doFilter(HttpServletRequest req, HttpServletResponse resp, FilterChain chain)方法
  • 重载doFilter(HttpServletRequest req, HttpServletResponse resp, FilterChain chain):HTTP协议专用的过滤方法,子类可重写该方法处理HTTP请求

四、Filter 三种配置方式🧾🐦‍🔥🧾

Filter 配置的核心目的就两个:告诉服务器"哪个Filter类要生效" + 指定"拦截哪些请求",三种配置方式只是实现这两个目的的不同途径

不管哪种配置,都要先写一个 Filter 类(实现 Filter 接口,重写 doFilter 核心方法),配置只是"激活"这个类的手段。

1. 注解无参数配置

适用场景

逻辑简单、不需要自定义参数(比如单纯记录日志、固定拦截某类请求)。

写法特点

只需要在 Filter 类上贴一个注解,不用写任何 XML,相当于"给类贴个标签,告诉服务器:你要生效,且拦截这些请求"。

步骤

  1. 写 Filter 类,实现 Filter 接口;
  2. 在类上添加 @WebFilter(urlPatterns = "拦截路径")
  3. doFilter 里写核心逻辑,最后必须调用 chain.doFilter(...) 放行。

例子(记录所有请求日志)

java 复制代码
// 注解直接指定"拦截所有请求(/*)",无其他配置
@WebFilter(urlPatterns = "/*")
public class LogFilter implements Filter {
    // 核心拦截方法:每次请求都会执行
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("有人访问网站啦!"); // 简单日志逻辑
        chain.doFilter(request, response); // 放行:让请求继续到目标资源(比如Servlet、JSP)
    }

    // 初始化和销毁方法:不用写逻辑,空实现即可
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {}
    @Override
    public void destroy() {}
}

关键

  • 注解里只需要写 urlPatterns(拦截路径),其他都能省;
  • 服务器启动时会自动扫描这个注解,自动激活 Filter,不用手动配置。

2. 注解带参数配置

适用场景

需要动态配置参数(比如编码格式、登录页路径,不想写死在代码里)。

写法特点

还是用 @WebFilter 注解,但多了 initParams 属性,相当于"给类贴标签时,顺便传点参数,类里能直接用"。

步骤

  1. 写 Filter 类,实现 Filter 接口;
  2. 注解里加 initParams = @WebInitParam(name="参数名", value="参数值")(多个参数用逗号分隔);
  3. init 方法里,通过 FilterConfig 获取注解里的参数;
  4. doFilter 里使用参数,最后放行。

例子(动态配置编码格式)

java 复制代码
// 注解指定"拦截所有请求",同时传参数(encoding=UTF-8)
@WebFilter(
    urlPatterns = "/*",
    initParams = @WebInitParam(name = "encoding", value = "UTF-8") // 自定义参数:编码格式
)
public class EncodingFilter implements Filter {
    private String encoding; // 存储注解里的参数

    // 初始化方法:服务器启动时执行1次,用于获取参数
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // 从注解的参数中获取"encoding"的值(这里是UTF-8)
        encoding = filterConfig.getInitParameter("encoding");
    }

    // 核心拦截方法:使用参数设置编码
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        request.setCharacterEncoding(encoding); // 用配置的参数设置请求编码
        chain.doFilter(request, response); // 放行
    }

    @Override
    public void destroy() {}
}

关键

  • 参数存在注解里,修改时不用改核心逻辑,只改注解的 value 即可;
  • 必须通过 FilterConfig.getInitParameter("参数名") 获取参数。

3. web.xml 配置(兼容所有版本)

适用场景

项目用的是低版本 Servlet(比如 Servlet 2.5 及以下),或者想把所有配置集中在 web.xml 里统一管理。

写法特点

Filter 类上不贴任何注解,所有配置都写在 web.xml 文件里,相当于"在配置文件里告诉服务器:要激活哪个Filter类、给它传什么参数、拦截哪些请求"。

步骤

  1. 写纯 Filter 类(无任何注解);
  2. web.xml 里分两步配置:
    • 第一步:用 <filter> 标签注册 Filter 类,指定类的全路径,可选传参数;
    • 第二步:用 <filter-mapping> 标签绑定拦截路径(和上面注册的类同名关联);
  3. 类里通过 FilterConfig 获取 web.xml 里的参数,doFilter 里写逻辑并放行。

例子(和上面注解带参数功能一样,编码配置)

  1. 纯 Filter 类(无注解):
java 复制代码
public class EncodingFilter implements Filter {
    private String encoding;

    // 从web.xml里获取参数
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        encoding = filterConfig.getInitParameter("encoding");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        request.setCharacterEncoding(encoding);
        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {}
}
  1. web.xml 配置(核心):
xml 复制代码
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" version="4.0">
    <!-- 第一步:注册Filter类 + 配置参数 -->
    <filter>
        <filter-name>EncodingFilter</filter-name> <!-- 给Filter起个名字(随便起,但要和下面一致) -->
        <filter-class>com.example.EncodingFilter</filter-class> <!-- Filter的全类名(包名+类名) -->
        <!-- 可选:配置参数(和注解的initParams一样) -->
        <init-param>
            <param-name>encoding</param-name> <!-- 参数名 -->
            <param-value>UTF-8</param-value> <!-- 参数值 -->
        </init-param>
    </filter>

    <!-- 第二步:配置拦截路径(和上面的Filter名字绑定) -->
    <filter-mapping>
        <filter-name>EncodingFilter</filter-name> <!-- 必须和上面的名字一致,否则找不到类 -->
        <url-pattern>/*</url-pattern> <!-- 拦截所有请求 -->
    </filter-mapping>
</web-app>

总结

配置方式 核心操作 优点 适用场景
注解无参数 类上贴 @WebFilter(urlPatterns="路径") 最简洁,不用写XML 简单逻辑(日志、固定拦截)
注解带参数 注解里加 initParams 传参数,类内获取 简洁又灵活,参数可配 需动态参数(编码、路径)
web.xml配置 配置文件分 <filter><filter-mapping> 兼容所有版本,统一管理 低版本Servlet、集中配置

五、Filter的应用

场景1:中文乱码处理(CharacterEncodingFilter)

中文乱码的核心原因是请求/响应的字符编码不一致,通过Filter可统一设置编码:

java 复制代码
package com.cyx.jsp.filter;

import javax.servlet.*;
import java.io.IOException;

public class CharacterEncodingFilter implements Filter {
    private String characterEncoding; // 存储编码格式

    @Override
    public void init(FilterConfig config) throws ServletException {
        System.out.println("过滤器初始化");
        // 读取web.xml中配置的初始化参数
        this.characterEncoding = config.getInitParameter("characterEncoding");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("过滤器过滤操作");
        // 设置请求字符编码
        request.setCharacterEncoding(characterEncoding);
        // 设置响应字符编码
        response.setCharacterEncoding(characterEncoding);
        // 将请求传递给下一个Filter或Servlet(必须调用,否则请求被拦截)
        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {
        System.out.println("过滤器销毁");
    }
}
  • 这是过滤器的核心方法,每次客户端请求匹配的资源时都会执行
  • **核心功能:**在请求到达 Servlet 之前,统一设置请求(request)和响应(response)的字符编码(如 UTF-8),确保请求参数的解析和响应内容的输出都使用正确的编码,避免中文乱码
  • chain.doFilter(request, response)如果不调用这行代码,请求会被当前过滤器拦截,无法传递到后续的过滤器或 Servlet,导致请求处理中断

运行结果图示:

说明:UserInfoServlet类中并没有设置UTF-8编码,而在显示时却并没有乱码,说明编码设置在Filter过滤器中统一设置了

说明:过滤器仅初始化一次,并且在Servlet初始化前完成;过滤器操作了四次,对应了user中的四个属性;过滤器在Servlet销毁后也销毁

配置web.xml(注册字符编码过滤器)
xml 复制代码
<filter>
    <filter-name>characterEncodingFilter</filter-name>
    <filter-class>com.cyx.jsp.filter.CharacterEncodingFilter</filter-class>
    <!-- 初始化参数:设置编码格式为UTF-8 -->
    <init-param>
        <param-name>characterEncoding</param-name>
        <param-value>UTF-8</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>characterEncodingFilter</filter-name>
    <!-- 通配符/*:拦截所有请求 -->
    <url-pattern>/*</url-pattern>
</filter-mapping>
场景2:登录超时处理(TimeoutFilter)

登录超时指用户登录后长时间未操作,session过期,此时需拦截请求并跳转到登录页:

java 复制代码
package com.cyx.jsp.filter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;

public class TimeoutFilter extends HttpFilter {
    @Override
    protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        // 获取当前会话(不创建新会话)
        HttpSession session = request.getSession(false);
        // 检查session是否存在且是否存储了用户名(登录标识)
        if (session == null || session.getAttribute("username") == null) {
            // 未登录或session过期,重定向到首页(登录页)
            String homePageUrl = request.getContextPath();
            if ("".equalsIgnoreCase(homePageUrl)) {
                homePageUrl = "/"; // 上下文路径为空时,设置为根路径
            }
            response.sendRedirect(homePageUrl);
        } else {
            // 已登录且session有效,传递请求到下一个组件
            chain.doFilter(request, response);
        }
    }
}
配置web.xml(注册登录超时过滤器)
xml 复制代码
<filter>
    <filter-name>timeoutFilter</filter-name>
    <filter-class>com.cyx.jsp.filter.TimeoutFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>timeoutFilter</filter-name>
    <!-- 拦截所有请求(可根据需求调整,如仅拦截需要登录的路径) -->
    <url-pattern>/*</url-pattern>
</filter-mapping>

六、FilterChain过滤器执行细节⚠️🧐⚠️

对应的代码示例🌰

步骤1:编写2个过滤器(Filter1、Filter2)

模拟图中的"前置代码、chain.doFilter、后置代码"逻辑:

Filter1
java 复制代码
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

// 拦截所有请求(/*),参与过滤器链
@WebFilter(urlPatterns = "/*")
public class Filter1 implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        // 1. 前置代码(请求到达时执行)
        System.out.println("Filter1:前置代码执行(请求阶段)");

        // 2. 放行请求:执行下一个Filter/目标资源
        chain.doFilter(request, response);

        // 3. 后置代码(响应返回时执行)
        System.out.println("Filter1:后置代码执行(响应阶段)");
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {}
    @Override
    public void destroy() {}
}
Filter2
java 复制代码
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

// 同样拦截所有请求,参与过滤器链
@WebFilter(urlPatterns = "/*")
public class Filter2 implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        // 1. 前置代码(请求到达时执行)
        System.out.println("Filter2:前置代码执行(请求阶段)");

        // 2. 放行请求:执行目标资源
        chain.doFilter(request, response);

        // 3. 后置代码(响应返回时执行)
        System.out.println("Filter2:后置代码执行(响应阶段)");
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {}
    @Override
    public void destroy() {}
}
步骤2:编写Servlet

模拟一个简单的Servlet,作为请求的最终目标:

java 复制代码
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

// 目标资源:处理 /target 请求
@WebServlet(urlPatterns = "/target")
public class TargetServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html;charset=utf-8");
        PrintWriter out = response.getWriter();
        out.write("<h1>目标资源(Servlet)已执行</h1>");
        
        // 控制台打印,方便观察执行顺序
        System.out.println("目标资源(TargetServlet):doGet方法执行");
    }
}
运行结果

启动项目后,访问对应网址后,控制台运行结果如下:

复制代码
Filter1:前置代码执行(请求阶段)
Filter2:前置代码执行(请求阶段)
目标资源(TargetServlet):doGet方法执行
Filter2:后置代码执行(响应阶段)
Filter1:后置代码执行(响应阶段)

代码对应图的核心说明

  1. 过滤器链顺序

    • 注解配置下,Filter按类名字母顺序 执行(Filter1 先于 Filter2);
    • 若用web.xml配置,顺序由<filter-mapping>上下顺序决定。
  2. chain.doFilter的作用

    • 执行 chain.doFilter(...) 时,请求会跳转到下一个Filter (有Filter时),或跳转到目标资源(无Filter时);
    • 目标资源执行完后,会回到当前Filter的chain.doFilter之后的代码(即"后置代码")。
  3. 图中的"前置/后置代码"

    • chain.doFilter 之前的代码:请求到达时执行
    • chain.doFilter 之后的代码:响应返回时执行

如果我的内容对你有帮助,请 点赞 , 评论 , 收藏 。创作不易,大家的支持就是我坚持下去的动力!

相关推荐
nwsuaf_huasir1 小时前
深度学习2-pyTorch学习-第一个神经网络
pytorch·深度学习·学习
我的xiaodoujiao1 小时前
使用 Python 语言 从 0 到 1 搭建完整 Web UI自动化测试学习系列 30--开源电商商城系统项目实战--配置测试环境地址
python·学习·测试工具·pytest
YJlio1 小时前
Active Directory 工具学习笔记(10.2):AdExplorer 实战(二)— 对象 / 属性 / 搜索 / 快照
java·笔记·学习
Allen_LVyingbo1 小时前
多模态知识图谱赋能大学医疗AI精准教学研究(上)
学习·知识图谱·健康医疗
diegoXie1 小时前
【R】正则的惰性和贪婪匹配
java·前端·r语言
stereohomology1 小时前
用大模型学习everything 1.5a的特殊用法
学习·everything
whltaoin4 小时前
【Java SE】Java IO体系深度剖析:从原理到实战的全方位讲解(包含流操作、序列化与 NIO 优化技巧)
java·开发语言·nio·se·io体系
Tony Bai10 小时前
Go 安全新提案:runtime/secret 能否终结密钥残留的噩梦?
java·开发语言·jvm·安全·golang
oioihoii10 小时前
C++11到C++23语法糖万字详解
java·c++·c++23