Servlet 编写过滤器(长文解析)

更新时间:

💡一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

  • 新项目:《从零手撸:仿小红书(微服务架构)》 正在持续爆肝中,基于 Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...点击查看项目介绍 ;
  • 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/ ;

截止目前, 星球 内专栏累计输出 82w+ 字,讲解图 3441+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 2900+ 小伙伴加入学习 ,欢迎点击围观

在现代 Web 开发中,Servlet 是 Java EE 技术栈中不可或缺的核心组件,它为处理 HTTP 请求和响应提供了标准的编程接口。然而,随着应用复杂度的提升,开发者常常需要在请求到达目标资源(如 Servlet 或 JSP 页面)之前,或响应返回客户端之前,执行一些通用逻辑。例如,验证用户身份、记录日志、修改编码格式等。此时,Servlet 过滤器(Filter)便成为了一个强大的工具。

本文将从零开始讲解如何编写和使用 Servlet 过滤器,通过循序渐进的案例和代码示例,帮助开发者理解其原理与应用场景。无论是编程新手还是有一定经验的开发者,都能通过本文掌握过滤器的核心知识,并将其应用到实际项目中。


什么是 Servlet 过滤器?

Servlet 过滤器可以理解为一个“请求处理的中间层”。它像一道“安检门”,在 HTTP 请求到达目标资源(如某个 Servlet)之前,或响应返回客户端之前,对请求和响应进行拦截、检查或修改。

过滤器的核心功能

  1. 预处理请求:在目标资源处理请求前,执行安全验证、参数校验、日志记录等操作。
  2. 后处理响应:在响应返回客户端前,修改响应内容(如压缩、加密)或添加额外信息(如性能统计)。
  3. 过滤器链:多个过滤器可以按顺序组成“过滤器链”,每个过滤器依次处理请求和响应。

形象比喻
想象你进入一个机场,需要经过安检、体温检测、登机口验证等多个环节。每个环节对应一个过滤器,它们按顺序处理你的请求(登机),而最终目标(飞机)只有在通过所有过滤器后才会被访问。


过滤器的核心接口与生命周期

Servlet 过滤器通过实现 javax.servlet.Filter 接口来定义。该接口包含三个关键方法,共同构成了过滤器的生命周期:

1. init(FilterConfig config)

在过滤器实例被创建后,容器会调用此方法进行初始化。开发者可以在此方法中读取配置参数(如日志路径、密钥等),或初始化资源(如数据库连接池)。

2. doFilter(ServletRequest request, ServletResponse response, FilterChain chain)

这是过滤器的核心方法。每当有请求匹配过滤器的 URL 模式时,容器会调用此方法。开发者可以在此方法中:

  • 执行预处理逻辑(如记录日志、设置编码)。
  • 调用 chain.doFilter(request, response) 将请求传递给下一个过滤器或目标资源。
  • 执行后处理逻辑(如压缩响应内容)。

3. destroy()

当容器决定销毁过滤器实例时,此方法会被调用。开发者可以在此释放资源(如关闭数据库连接)。


步骤 1:编写一个简单的过滤器

以下是一个过滤器的完整代码示例,用于记录请求的 URL 和时间戳:

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.time.LocalDateTime;

public class LoggingFilter implements Filter {

    @Override
    public void init(FilterConfig config) throws ServletException {
        System.out.println("LoggingFilter 初始化完成");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {

        // 获取请求的原始信息
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        String url = httpRequest.getRequestURI();
        LocalDateTime startTime = LocalDateTime.now();

        // 执行预处理逻辑(记录请求开始时间)
        System.out.println("请求开始:" + url + " @ " + startTime);

        // 调用 FilterChain 将请求传递给下一个过滤器或目标资源
        chain.doFilter(request, response);

        // 执行后处理逻辑(记录请求结束时间)
        LocalDateTime endTime = LocalDateTime.now();
        System.out.println("请求结束:" + url + " @ " + endTime);
        System.out.println("耗时:" + Duration.between(startTime, endTime).toMillis() + " ms");
    }

    @Override
    public void destroy() {
        System.out.println("LoggingFilter 资源已释放");
    }
}

代码解释

  1. 类型转换:通过 HttpServletRequest 获取请求的 URL 信息。
  2. 预处理逻辑:记录请求开始的时间戳。
  3. FilterChain 的调用:必须调用 chain.doFilter(),否则请求无法到达目标资源。
  4. 后处理逻辑:计算并记录请求的耗时。

步骤 2:配置过滤器

编写完过滤器后,需要将其与 Web 应用关联。可以通过两种方式实现:

方法 1:在 web.xml 中配置

web.xml 文件中添加以下代码:

<!-- 定义过滤器 -->
<filter>
    <filter-name>LoggingFilter</filter-name>
    <filter-class>com.example.LoggingFilter</filter-class>
</filter>

<!-- 配置过滤器的 URL 模式 -->
<filter-mapping>
    <filter-name>LoggingFilter</filter-name>
    <url-pattern>/*</url-pattern> <!-- 匹配所有请求 -->
</filter-mapping>

方法 2:使用注解(Servlet 3.0+)

在过滤器类上直接添加 @WebFilter 注解:

import javax.servlet.annotation.WebFilter;
import javax.servlet.annotation.WebInitParam;

@WebFilter(
    urlPatterns = "/*", 
    initParams = {
        @WebInitParam(name = "logLevel", value = "DEBUG")
    }
)
public class LoggingFilter implements Filter { /* ... */ }

实战案例:登录拦截过滤器

接下来,我们编写一个更实用的过滤器,用于拦截未登录用户的访问:

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;

@WebFilter("/admin/*") // 仅拦截以 /admin/ 开头的 URL
public class AuthFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {

        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpSession session = httpRequest.getSession(false); // 不自动创建新 Session

        if (session != null && session.getAttribute("user") != null) {
            // 用户已登录,放行请求
            chain.doFilter(request, response);
        } else {
            // 未登录,重定向到登录页面
            HttpServletResponse httpResponse = (HttpServletResponse) response;
            httpResponse.sendRedirect("/login.jsp");
        }
    }

    // init 和 destroy 方法可留空或实现其他逻辑
}

案例说明

  1. URL 模式匹配:通过 @WebFilter("/admin/*"),过滤器仅拦截 /admin/ 路径下的请求。
  2. 会话验证:检查 HttpSession 中是否存在用户对象。
  3. 重定向逻辑:未登录时强制跳转到登录页面。

过滤器的高级用法与注意事项

1. 过滤器链的顺序

当多个过滤器同时生效时,它们的执行顺序由配置决定:

  • web.xml 中的顺序:先声明的过滤器先执行预处理,后声明的过滤器后执行预处理。
  • 后处理的顺序:与声明顺序相反,后声明的过滤器先执行后处理逻辑。

2. 性能优化

  • 避免在 doFilter 中执行耗时操作:如复杂的计算或数据库查询,这会阻塞请求响应。
  • 缓存常用数据:如在 init 方法中读取配置参数,而非每次请求时重新读取。

3. 线程安全

由于 Filter 是单例对象,其成员变量可能被多个线程共享。因此,不要在 Filter 中使用非线程安全的成员变量,所有状态信息应从请求或线程局部变量中获取。


结论

Servlet 过滤器为开发者提供了一种灵活、高效的方式来处理请求和响应的通用逻辑。通过本文的讲解,读者可以掌握以下核心内容:

  1. 过滤器的生命周期与核心方法。
  2. 如何编写和配置过滤器以实现日志记录、权限验证等常见功能。
  3. 过滤器链的执行顺序与线程安全等进阶知识。

掌握过滤器的编写与应用,不仅能提升代码的复用性,还能显著简化 Web 应用的开发流程。建议读者通过实际项目练习,逐步深入理解其原理与最佳实践。

提示:过滤器的威力远不止于此。后续可尝试探索更复杂的场景,如动态代理、请求参数加密,或结合框架(如 Spring Security)实现更强大的功能。

最新发布