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)之前,或响应返回客户端之前,对请求和响应进行拦截、检查或修改。
过滤器的核心功能
- 预处理请求:在目标资源处理请求前,执行安全验证、参数校验、日志记录等操作。
- 后处理响应:在响应返回客户端前,修改响应内容(如压缩、加密)或添加额外信息(如性能统计)。
- 过滤器链:多个过滤器可以按顺序组成“过滤器链”,每个过滤器依次处理请求和响应。
形象比喻:
想象你进入一个机场,需要经过安检、体温检测、登机口验证等多个环节。每个环节对应一个过滤器,它们按顺序处理你的请求(登机),而最终目标(飞机)只有在通过所有过滤器后才会被访问。
过滤器的核心接口与生命周期
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 资源已释放");
}
}
代码解释
- 类型转换:通过
HttpServletRequest
获取请求的 URL 信息。 - 预处理逻辑:记录请求开始的时间戳。
- FilterChain 的调用:必须调用
chain.doFilter()
,否则请求无法到达目标资源。 - 后处理逻辑:计算并记录请求的耗时。
步骤 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 方法可留空或实现其他逻辑
}
案例说明
- URL 模式匹配:通过
@WebFilter("/admin/*")
,过滤器仅拦截/admin/
路径下的请求。 - 会话验证:检查
HttpSession
中是否存在用户对象。 - 重定向逻辑:未登录时强制跳转到登录页面。
过滤器的高级用法与注意事项
1. 过滤器链的顺序
当多个过滤器同时生效时,它们的执行顺序由配置决定:
web.xml
中的顺序:先声明的过滤器先执行预处理,后声明的过滤器后执行预处理。- 后处理的顺序:与声明顺序相反,后声明的过滤器先执行后处理逻辑。
2. 性能优化
- 避免在
doFilter
中执行耗时操作:如复杂的计算或数据库查询,这会阻塞请求响应。 - 缓存常用数据:如在
init
方法中读取配置参数,而非每次请求时重新读取。
3. 线程安全
由于 Filter 是单例对象,其成员变量可能被多个线程共享。因此,不要在 Filter 中使用非线程安全的成员变量,所有状态信息应从请求或线程局部变量中获取。
结论
Servlet 过滤器为开发者提供了一种灵活、高效的方式来处理请求和响应的通用逻辑。通过本文的讲解,读者可以掌握以下核心内容:
- 过滤器的生命周期与核心方法。
- 如何编写和配置过滤器以实现日志记录、权限验证等常见功能。
- 过滤器链的执行顺序与线程安全等进阶知识。
掌握过滤器的编写与应用,不仅能提升代码的复用性,还能显著简化 Web 应用的开发流程。建议读者通过实际项目练习,逐步深入理解其原理与最佳实践。
提示:过滤器的威力远不止于此。后续可尝试探索更复杂的场景,如动态代理、请求参数加密,或结合框架(如 Spring Security)实现更强大的功能。