杰瑞科技汇

Servlet过滤器如何实现请求拦截与处理?

什么是 Servlet 过滤器?

Servlet 过滤器 是可以用来拦截客户端请求和服务器响应的对象,它就像一个“安检门”或者“中间人”,位于客户端和目标 Servlet 之间,可以对请求进行预处理,也可以对响应进行后处理。

Servlet过滤器如何实现请求拦截与处理?-图1
(图片来源网络,侵删)

核心概念:

  • 拦截:过滤器可以决定是否将请求传递给下一个资源(如 Servlet、JSP 或另一个过滤器)。
  • 链式处理:一个 Web 应用中可以配置多个过滤器,它们会形成一个“过滤器链”(Filter Chain),请求和响应会依次通过这个链上的每一个过滤器。
  • 可重用:过滤器是独立于业务逻辑的组件,可以跨多个 Servlet 或 JSP 使用,一个用于字符编码的过滤器可以应用于整个 Web 应用。

(一个简单的请求流程图)


过滤器的主要用途

过滤器非常灵活,用途广泛,常见的场景包括:

  1. 认证与授权:检查用户是否登录,是否有权限访问某个资源。
  2. 日志记录:记录所有请求的详细信息,如 IP 地址、访问时间、请求参数等,用于监控和审计。
  3. 数据压缩:对响应数据进行 GZIP 压缩,减少网络传输量,加快页面加载速度。
  4. 字符编码转换:统一处理请求和响应的字符编码,解决中文乱码问题(这是最经典的用途之一)。
  5. 输入数据验证:在请求到达 Servlet 之前,对请求参数进行格式验证(如检查邮箱格式、手机号格式)。
  6. 图片处理:动态地修改响应中的图片,例如添加水印、缩放等。
  7. 响应头信息处理:统一添加或修改响应头,如 Cache-ControlX-Frame-Options 等,用于增强安全性或缓存控制。

如何创建一个过滤器?

创建一个自定义的过滤器非常简单,只需要遵循以下三步:

Servlet过滤器如何实现请求拦截与处理?-图2
(图片来源网络,侵删)

步骤 1:创建一个类并实现 Filter 接口

import javax.servlet.*;
import java.io.IOException;
public class MyFirstFilter implements Filter {
    // 初始化方法,当容器创建 Filter 实例后调用,只调用一次
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("MyFirstFilter 初始化...");
        // 可以从 filterConfig 中获取初始化参数
        String param = filterConfig.getInitParameter("paramName");
        System.out.println("初始化参数 paramName 的值为: " + param);
    }
    // 核心过滤方法,每次请求匹配该过滤器时都会调用
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        System.out.println("MyFirstFilter - 开始处理请求");
        // --- 1. 前置处理 ---
        // 在这里可以对请求进行预处理
        // 设置请求编码
        request.setCharacterEncoding("UTF-8");
        // --- 2. 将请求传递给下一个资源 ---
        // chain.doFilter() 是关键!
        // 如果不调用这个方法,请求将被拦截,不会到达目标 Servlet。
        // 调用后,请求会继续向下传递,通过过滤器链上的下一个过滤器,最终到达目标 Servlet。
        // 目标 Servlet 处理完后,响应会再次回到这个 doFilter 方法的这里。
        chain.doFilter(request, response);
        // --- 3. 后置处理 ---
        // 在这里可以对响应进行后处理
        // 在响应内容后面追加一段文字
        System.out.println("MyFirstFilter - 开始处理响应");
    }
    // 销毁方法,当容器销毁 Filter 实例前调用,只调用一次
    @Override
    public void destroy() {
        System.out.println("MyFirstFilter 销毁...");
        // 在这里进行资源清理工作
    }
}

步骤 2:在 web.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-name>MyFirstFilter</filter-name>
        <!-- 过滤器的全限定类名 -->
        <filter-class>com.example.filter.MyFirstFilter</filter-class>
        <!-- 可选:初始化参数 -->
        <init-param>
            <param-name>paramName</param-name>
            <param-value>paramValue</param-value>
        </init-param>
    </filter>
    <!-- 配置过滤器要拦截的 URL 模式 -->
    <filter-mapping>
        <!-- 指定是哪个过滤器 -->
        <filter-name>MyFirstFilter</filter-name>
        <!-- 拦截的 URL 模式 -->
        <url-pattern>/*</url-pattern> <!-- /* 表示拦截所有请求 -->
        <!-- 也可以指定拦截特定的 Servlet -->
        <!-- <servlet-name>MyServlet</servlet-name> -->
    </filter-mapping>
</web-app>

步骤 3:使用注解方式配置(推荐,Servlet 3.0+)

从 Servlet 3.0 开始,推荐使用注解来配置过滤器,这样更简洁,无需修改 web.xml

  1. 在过滤器类上添加 @WebFilter 注解:
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
// @WebFilter 注解用来配置过滤器
// urlPatterns 指定要拦截的 URL 模式
// filterName 指定过滤器的名称,与 web.xml 中的 <filter-name> 对应
// initParams 可以配置初始化参数
@WebFilter(
    urlPatterns = {"/*"}, 
    filterName = "MyFirstFilter",
    initParams = {
        @WebInitParam(name = "paramName", value = "paramValue")
    }
)
public class MyFirstFilter implements Filter {
    // ... init, doFilter, destroy 方法保持不变 ...
}
  1. web.xml 中(如果存在)添加 metadata-complete="false",或者直接删除 <web-app> 标签,确保容器会扫描注解。web.xml 完全不存在,容器默认会扫描。

过滤器链 的工作原理

当一个请求的 URL 匹配了多个 <filter-mapping> 时,这些过滤器就会形成一个过滤器链,它们的执行顺序由 <filter-mapping>web.xml 中声明的顺序决定(注解方式则按类名字典序,或使用 @WebFilterdispatcherTypesdispatcher 等属性控制)。

执行流程:

  1. 客户端发送一个请求。
  2. 容器找到第一个过滤器 Filter1,调用其 doFilter() 方法。
  3. Filter1doFilter() 方法中,调用 chain.doFilter()
  4. 容器将请求传递给链上的下一个过滤器 Filter2,调用其 doFilter() 方法。
  5. Filter2doFilter() 方法中,调用 chain.doFilter()
  6. ...这个过程一直持续,直到链上的最后一个过滤器。
  7. 在最后一个过滤器中调用 chain.doFilter() 后,请求终于到达了目标 Servlet。
  8. 目标 Servlet 执行业务逻辑,生成响应。
  9. 响应原路返回,再次进入最后一个过滤器的 doFilter() 方法中 chain.doFilter()下一行代码(即后置处理部分)。
  10. 然后返回到上一个过滤器,执行其后置处理部分。
  11. ...直到响应返回到第一个过滤器,最终发送给客户端。

图示:

Servlet过滤器如何实现请求拦截与处理?-图3
(图片来源网络,侵删)
客户端
    |
    |---请求---> [Filter1] --doFilter()--> [Filter2] --doFilter()--> ... --> [Servlet]
    |             ^   |                      ^   |                     ^
    |             |   |后置处理              |   |后置处理             | 处理请求
    |             |   v                      |   v                     | 生成响应
    |<---响应----- [Filter1] <--doFilter()-- [Filter2] <--doFilter()-- ... <-- [Servlet]
    |             |   |                      |   |
    |             |   |前置处理              |   |前置处理
    |             |   v                      |   v
    |<---请求----- [Filter1] --doFilter()--> [Filter2] --doFilter()--> ... --> [Servlet]
    |
客户端

实战案例:解决全站中文乱码的过滤器

这是一个非常实用且经典的过滤器。

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
@WebFilter("/*")
public class CharacterEncodingFilter implements Filter {
    private String encoding = "UTF-8";
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // 可以从 web.xml 中读取编码配置
        String configEncoding = filterConfig.getInitParameter("encoding");
        if (configEncoding != null) {
            this.encoding = configEncoding;
        }
    }
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        // 1. 设置请求编码
        request.setCharacterEncoding(encoding);
        // 2. 设置响应编码 (注意:response 的 setCharacterEncoding 必须在 getWriter() 之前调用)
        response.setCharacterEncoding(encoding);
        // 同时设置 Content-Type 头,更推荐的方式
        response.setContentType("text/html;charset=" + encoding);
        // 3. 放行
        chain.doFilter(request, response);
    }
    @Override
    public void destroy() {
        // 不需要做任何事
    }
}

配置 web.xml (可选):

<filter>
    <filter-name>CharacterEncodingFilter</filter-name>
    <filter-class>com.example.filter.CharacterEncodingFilter</filter-class>
    <init-param>
        <param-name>encoding</param-name>
        <param-value>UTF-8</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>CharacterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

DispatcherType (分发类型)

从 Servlet 3.0 开始,<filter-mapping>@WebFilter 支持一个 dispatcherTypes 属性,可以指定过滤器在哪些类型的请求分发下生效。

DispatcherType 包括:

  • REQUEST:默认值,由客户端直接发起的请求。
  • FORWARD:由 RequestDispatcher.forward() 转发的请求。
  • INCLUDE:由 RequestDispatcher.include() 包含的请求。
  • ASYNC:异步处理的请求。
  • ERROR:由错误页面机制(<error-page>)触发的请求。

示例:

// 只对直接请求和转发请求生效
@WebFilter(urlPatterns = "/admin/*", dispatcherTypes = {DispatcherType.REQUEST, DispatcherType.FORWARD})
public class AdminAuthFilter implements Filter {
    // ...
}

特性 描述
核心作用 拦截请求和响应,进行预处理和后处理。
生命周期 init() -> doFilter() (多次) -> destroy()
关键方法 chain.doFilter(request, response):必须调用,用于将控制权传递给下一个资源。
配置方式 web.xml (传统) 和 @WebFilter 注解 (现代)。
核心优势 解耦可重用,将通用的横切关注点(如日志、安全)与业务逻辑分离。
执行顺序 过滤器链的顺序由配置决定。

Servlet 过滤器是 Java Web 开发中不可或缺的工具,掌握它对于编写结构清晰、易于维护、功能强大的 Web 应用至关重要。

分享:
扫描分享到社交APP
上一篇
下一篇