前端视角 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 销毁");
    }
}
相关推荐
淘源码A5 分钟前
小微企业SaaS ERP管理系统,SpringBoot+Vue+ElementUI+UniAPP
java·源码·saas·erp·erp系统·erp源码
我是唐青枫8 分钟前
Java 原生异步编程与Spring 异步编程 详解
java
小妖66614 分钟前
vue2 provide 后 inject 数据不是响应式的,不实时更新
java·服务器·前端
zhang238390615420 分钟前
idea如何快速生成测试类
java·ide·intellij-idea
琢磨先生David26 分钟前
Java 24:重构数字信任边界 —— 后量子时代的智能安全防御体系构建
java·安全·重构
西京刀客29 分钟前
Go多服务项目结构优化:为何每个服务单独设置internal目录?
开发语言·后端·golang
每次的天空38 分钟前
Android Handler 机制面试总结
java·开发语言·jvm
举一个梨子zz44 分钟前
Java—— 集合 List
java·开发语言·数据结构·intellij-idea
purrrew1 小时前
【Java ee初阶】网络编程 TCP
java·服务器·网络·网络协议·tcp/ip·udp·java-ee
码农飞哥1 小时前
互联网大厂Java求职面试实战:Spring Boot微服务与数据库优化详解
java·spring boot·微服务·mybatis·数据库优化·性能监控·安全框架