前端视角 Java Web 入门手册 4.3:Web 开发基础—— Filter

Filter 是 Java Servlet 技术中的一个组件,位于客户端请求和目标 Servlet(或 JSP、静态资源)之间。它们能够拦截请求和响应,对其进行预处理和后处理,从而实现功能的扩展和增强。

Filter 作用

Filter 可以在请求到达目标 Servlet 之前或之后拦截请求,并对请求进行处理或修改。在响应返回给客户端之前或之后,Filter 也可以拦截响应,并对响应进行处理或修改,和 Node.js 的 Koa web framework 的中间件机制及其洋葱模型设计极其类似

Filter 在身份验证授权、缓存控制、日志、数据压缩解密、异常处理、请求转发跳转等场景被高频使用

Filter 工作原理

Filter 的工作流程可以简单描述为:

  1. 请求拦截:当客户端发送请求到服务器时,Servlet 容器会根据配置的 URL 模式,按顺序调用匹配的 Filters。
  2. 预处理:Filter 对请求进行预处理,如修改请求参数、进行身份验证等。
  3. 链式调用 :Filter 调用 chain.doFilter(request, response) 以将请求传递给下一个 Filter 或目标 Servlet。
  4. 后处理:当目标 Servlet 处理完请求并生成响应后,Filter 可以对响应进行后处理,如修改响应内容、添加响应头等。
  5. 响应返回:最终,处理后的响应返回给客户端。

Filter 的执行顺序由配置方式决定,无论是通过注解还是 web.xml,都可以指定多个 Filter,并控制其执行顺序。

创建和配置 Filter

Filter 的使用包括实现 Filter 接口、配置 Filter 拦截请求两步

实现 Filter 接口

创建 Filter 需要创建一个 Java 类,并实现 javax.servlet.Filter 接口。该接口包含三个方法:

  • init(): 初始化方法,在 Filter 被实例化后调用一次。
  • doFilter(): 过滤逻辑方法,每次请求都会调用。
  • destroy(): 销毁方法,在 Filter 被卸载前调用一次,用于资源释放。
java 复制代码
package com.example;

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

@WebFilter("/*") // 通过注解配置 Filter,拦截所有请求
public class LoggingFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // 初始化代码(如果需要)
        System.out.println("LoggingFilter 初始化");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        // 预处理逻辑
        System.out.println("LoggingFilter: 请求开始处理");
        
        // 将请求传递给下一个 Filter 或目标 Servlet
        chain.doFilter(request, response);
        
        // 后处理逻辑
        System.out.println("LoggingFilter: 请求处理结束");
    }

    @Override
    public void destroy() {
        // 清理代码(如果需要)
        System.out.println("LoggingFilter 销毁");
    }
}

chain.doFilter(request, response);是不是有 Koa await next()的味儿

配置 Filter

使用注解

如上例所示,可以通过 @WebFilter 注解来配置 Filter 的拦截 URL 模式和其它属性,是个简单的场景

  • urlPatterns 或 value: 指定拦截的 URL 模式。
  • filterName: 过滤器名称。
  • dispatcherTypes: 指定过滤器应用的调度类型(REQUEST、FORWARD、INCLUDE、ERROR、ASYNC)。
  • initParams: 初始化参数。
java 复制代码
@WebFilter(
    filterName = "AuthenticationFilter",
    urlPatterns = {"/secure/*"},
    dispatcherTypes = {DispatcherType.REQUEST}
)
public class AuthenticationFilter implements Filter {
    // 实现 Filter 方法
}

在复杂的 Web 应用中存在多个 Filter,@WebFilter 注解本身不支持 order 属性,无法直接通过注解控制过滤器的执行顺序。在 Spring 中可以使用 @Order 注解或实现 Ordered 接口来指定过滤器的执行顺序。较低的 @Order 值具有较高的优先级,会先执行。

java 复制代码
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

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

@Component
@Order(1) // 优先级最高
@WebFilter("/*")
public class LoggingFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // 初始化逻辑
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        // 过滤逻辑
        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {
        // 清理逻辑
    }
}

@Component
@Order(2) // 次优先级
@WebFilter("/secure/*")
public class AuthenticationFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // 初始化逻辑
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        // 过滤逻辑
        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {
        // 清理逻辑
    }
}

如果需求变更,需要中间插入一个 Filter 会导致后面的所有 Filter order 发生变化,如果 Filter 的执行顺序非常重要,最好使用 web.xml 文件来配置 Filter,以确保 Filter 的执行顺序是可靠的

使用 web.xml

在 web.xml 文件中,可以通过 <filter><filter-mapping> 标签来配置 Filter,web.xml 提供了更多的配置选项,适合复杂的应用场景。在 web.xml 中,Filter 的执行顺序由 <filter-mapping> 在文件中的顺序决定。前面的 Filter 会先执行

xml 复制代码
<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">

  <!-- 定义 Filter -->
  <filter>
    <filter-name>LoggingFilter</filter-name>
    <filter-class>com.example.LoggingFilter</filter-class>
  </filter>

  <!-- 配置 Filter 拦截的 URL 模式 -->
  <filter-mapping>
    <filter-name>LoggingFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

  <!-- 定义另一个 Filter -->
  <filter>
    <filter-name>AuthenticationFilter</filter-name>
    <filter-class>com.example.AuthenticationFilter</filter-class>
  </filter>

  <filter-mapping>
    <filter-name>AuthenticationFilter</filter-name>
    <url-pattern>/secure/*</url-pattern>
  </filter-mapping>

</web-app>

Filter 链

Filter 可以串联形成一个 Filter 链 (Filter Chain) ,多个 Filter 按照配置的顺序依次拦截和处理请求。每个 Filter 都有机会对请求和响应进行修改或处理,过程和 Koa 的洋葱模型非常类似

  1. 请求到达 Filter 链的第一个 Filter
  2. 第一个 Filter 执行预处理
  3. 调用 chain.doFilter(request, response)
    • 请求继续传递到下一个 Filter
    • 或者最终传递到目标 Servlet
  4. Filter 执行后处理(响应返回时)
  5. 响应传递回上一个 Filter,以此类推,直到响应返回给客户端
plain 复制代码
请求 → Filter1 → Filter2 → Servlet → Filter2(后处理) → Filter1(后处理) → 响应

一个简单的日志记录 Filter

使用 Filter 记录每个 HTTP 请求的详细信息

java 复制代码
package org.example;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Date;

@WebFilter("/*") // 拦截所有请求
public class LoggingFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // 初始化日志记录器(如果需要)
        System.out.println("LoggingFilter 初始化");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
    throws IOException, ServletException {
        // 转换为 HttpServletRequest 以获取更多信息
        HttpServletRequest httpRequest = (HttpServletRequest) request;

        // 获取请求信息
        String method = httpRequest.getMethod();
        String uri = httpRequest.getRequestURI();
        String clientIP = request.getRemoteAddr();
        Date requestTime = new Date();

        // 记录日志
        System.out.printf("[%s] %s request for %s from %s%n", requestTime, method, uri, clientIP);

        // 继续处理请求
        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {
        // 清理资源(如果需要)
        System.out.println("LoggingFilter 销毁");
    }
}
相关推荐
欢乐少年190419 分钟前
SpringBoot集成Sentry日志收集-3 (Spring Boot集成)
spring boot·后端·sentry
夏天的味道٥1 小时前
使用 Java 执行 SQL 语句和存储过程
java·开发语言·sql
冰糖码奇朵3 小时前
大数据表高效导入导出解决方案,mysql数据库LOAD DATA命令和INTO OUTFILE命令详解
java·数据库·sql·mysql
好教员好3 小时前
【Spring】整合【SpringMVC】
java·spring
浪九天4 小时前
Java直通车系列13【Spring MVC】(Spring MVC常用注解)
java·后端·spring
堕落年代4 小时前
Maven匹配机制和仓库库设置
java·maven
功德+n5 小时前
Maven 使用指南:基础 + 进阶 + 高级用法
java·开发语言·maven
uhakadotcom5 小时前
Apache CXF 中的拒绝服务漏洞 CVE-2025-23184 详解
后端·面试·github
uhakadotcom5 小时前
CVE-2025-25012:Kibana 原型污染漏洞解析与防护
后端·面试·github
uhakadotcom5 小时前
揭秘ESP32芯片的隐藏命令:潜在安全风险
后端·面试·github