引言
在Java Servlet技术中,Filter是一个拦截器,它允许开发者在请求到达目标资源之前或响应发送给客户端之后,对请求或响应进行拦截和处理。这种机制为实现诸如身份验证、日志记录、请求修改等功能提供了极大的灵活性。
Filter基础
Filter接口定义了三个主要方法:init()
、doFilter()
和destroy()
。下面我们将逐一介绍这些方法的作用和使用场景。
init()方法
init()
方法在Filter实例化后由容器调用一次,用于初始化Filter。这个方法接收一个FilterConfig
对象,它包含了Filter的配置参数。如果在初始化过程中发生错误,可以通过抛出ServletException
来通知容器。
doFilter()方法
doFilter()
方法是Filter的核心,它在每次请求到达资源时被调用。Filter可以通过这个方法对请求和响应进行拦截和处理。doFilter()
方法接收三个参数:ServletRequest
、ServletResponse
和FilterChain
。FilterChain
允许Filter将请求传递给链中的下一个Filter或目标资源。
destroy()方法
destroy()
方法在Filter被容器移除服务之前调用,提供了清理资源的机会,如关闭文件句柄或停止线程。
Filter使用场景
Filter可以用于多种场景,包括但不限于:
- 身份验证:确保用户已登录并拥有访问资源的权限。
- 日志记录:记录请求和响应的详细信息,用于审计和监控。
- 数据压缩:在发送响应之前压缩数据,减少网络传输量。
- 请求修改:在请求到达目标资源之前修改请求参数。
使用Redis和Lua实现请求限流
限流是控制应用程序接收请求速率的一种机制,用于防止系统过载。以下是一个使用Redis和Lua脚本实现请求限流的示例。
环境准备
- Redis服务器:安装并运行Redis。
- Spring框架:使用Spring框架的
RedisTemplate
来简化Redis操作。
限流Filter实现
定义Lua脚本:用于原子性地检查和更新请求计数。
local limitResourceKey = KEYS[1] local limitTimeWindowMillis = tonumber(ARGV[1]) local currentMillis = tonumber(ARGV[2]) local limitCount = tonumber(ARGV[3]) local windowStartMs = currentMillis - limitTimeWindowMillis local current = redis.call('zcount', limitResourceKey, windowStartMs, currentMillis) if current and tonumber(current) >= limitCount then return false end redis.call("ZREMRANGEBYSCORE", limitResourceKey, 0, windowStartMs) math.randomseed(currentMillis) redis.call("zadd", limitResourceKey, currentMillis, tostring(currentMillis) .. tostring(math.random(1000,9999))) redis.call("expire", limitResourceKey, limitTimeWindowMillis/1000) return true
编写Filter类:
public class RateLimitingFilter implements Filter { private RedisTemplate<String, String> redisTemplate; private final String LUA_SCRIPT = "..."; // 将上面的Lua脚本赋值到这里 @Override public void init(FilterConfig filterConfig) throws ServletException { // 初始化RedisTemplate redisTemplate = new RedisTemplate<>(); // 配置RedisTemplate(省略具体配置代码) } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { String key = "rate_limit:" + request.getRemoteAddr(); long limitTimeWindowMillis = 60000; // 1分钟时间窗口 long limitCount = 100; // 最大请求次数 boolean allowed = (Boolean) redisTemplate.execute((RedisOperationsCallback<Boolean>) connection -> { DefaultRedisScript<Long> script = new DefaultRedisScript<>(LUA_SCRIPT, Long.class); script.getKeys().add(key); return (Long) script.execute(connection, Arrays.asList(limitTimeWindowMillis, System.currentTimeMillis(), limitCount)); }); if (allowed) { chain.doFilter(request, response); // 继续过滤链 } else { response.getWriter().write("Rate limit exceeded."); } } @Override public void destroy() { // 清理RedisTemplate资源 } }
部署和配置
将RateLimitingFilter添加到你的web.xml文件中,配置为全局Filter或针对特定URL模式。
结语
通过上述介绍,我们了解到了Servlet Filter的基本概念和使用方法,以及如何使用Redis和Lua脚本来实现请求限流。Filter提供了一种强大的方式来处理Web应用程序中的各种任务,而限流则是保护应用程序免受过度请求的一种有效手段。希望这篇博客能帮助你更好地理解和应用Servlet Filter。