1. JavaWeb过滤器与监听器核心概念解析
在JavaWeb开发中,过滤器和监听器是两种至关重要的组件,它们以不同的方式介入Web应用的生命周期。过滤器(Filter)就像安检通道的X光机,对每个经过的请求和响应进行检查和过滤;而监听器(Listener)则如同监控中心的传感器,时刻关注着应用内部各种状态的变化。
1.1 过滤器的工作原理与典型场景
过滤器基于责任链模式实现,多个过滤器可以串联形成处理管道。当请求到达时,容器会按照web.xml中定义的顺序依次调用过滤器的doFilter()方法。我常把这种机制比作自来水净化系统 - 原水依次经过沉淀、过滤、消毒等多道处理工序后才成为可饮用的清水。
实际开发中最常见的三种过滤器应用:
- 认证授权:检查用户会话和权限,未登录用户重定向到登录页
- 请求预处理:统一设置字符编码、压缩响应内容
- 日志记录:记录请求耗时、参数等关键信息
重要提示:过滤器中不要直接抛出异常,应该使用response.sendRedirect()或request.getRequestDispatcher().forward()处理异常流程
1.2 监听器的类型与触发时机
监听器根据监听对象的不同可分为六大类,其中最常用的是以下三种:
-
ServletContext监听器
- contextInitialized:应用启动时触发,适合初始化全局配置
- contextDestroyed:应用关闭时触发,用于释放资源
-
Session监听器
- sessionCreated:新会话创建时
- sessionDestroyed:会话失效时(超时或手动失效)
-
Request监听器
- requestInitialized:请求到达时
- requestDestroyed:请求处理完成时
在电商项目中,我常用Session监听器实现购物车超时处理 - 当session失效时自动将未结算商品存入数据库。
2. 过滤器深度实现指南
2.1 自定义过滤器开发步骤
下面以一个解决中文乱码的过滤器为例,展示完整实现过程:
java复制public class CharsetFilter implements Filter {
private String encoding;
@Override
public void init(FilterConfig config) {
this.encoding = config.getInitParameter("encoding");
if(this.encoding == null) {
this.encoding = "UTF-8";
}
}
@Override
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {
req.setCharacterEncoding(encoding);
resp.setCharacterEncoding(encoding);
chain.doFilter(req, resp);
}
@Override
public void destroy() {
// 清理资源
}
}
配置示例(web.xml):
xml复制<filter>
<filter-name>charsetFilter</filter-name>
<filter-class>com.example.CharsetFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>charsetFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
2.2 过滤器链的执行顺序问题
当多个过滤器匹配同一个请求时,执行顺序由filter-mapping在web.xml中的声明顺序决定。但在Servlet 3.0之后,也可以通过@WebFilter注解的filterName属性配合@Order注解控制顺序。
常见踩坑点:
- 日志过滤器应该放在最外层,确保记录完整请求
- 安全过滤器需要优先执行,尽早阻断非法请求
- 编码过滤器要在所有处理参数的过滤器之前
3. 监听器实战应用案例
3.1 在线用户统计实现
通过HttpSessionListener可以准确统计当前在线用户数:
java复制public class OnlineUserListener implements HttpSessionListener {
private static final AtomicInteger counter = new AtomicInteger();
@Override
public void sessionCreated(HttpSessionEvent se) {
counter.incrementAndGet();
se.getSession().getServletContext()
.setAttribute("onlineUsers", counter.get());
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
counter.decrementAndGet();
se.getSession().getServletContext()
.setAttribute("onlineUsers", counter.get());
}
}
3.2 应用启动初始化技巧
利用ServletContextListener实现缓存预热:
java复制public class CacheInitializer implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.submit(() -> {
// 加载热门商品到缓存
loadHotProducts();
// 初始化城市列表
loadCityList();
});
executor.shutdown();
}
private void loadHotProducts() {
// 数据库查询和缓存加载逻辑
}
}
经验分享:初始化耗时操作应该放在异步线程中执行,避免阻塞应用启动
4. 过滤器与拦截器的本质区别
虽然过滤器和拦截器都能对请求进行预处理,但它们的运行机制有根本不同:
| 特性 | 过滤器(Filter) | 拦截器(Interceptor) |
|---|---|---|
| 运行层级 | Servlet容器层面 | Spring等框架层面 |
| 依赖关系 | 不依赖任何框架 | 需要框架支持 |
| 实现方式 | 基于函数回调 | 基于反射和动态代理 |
| 作用范围 | 所有Web资源 | 仅限Controller方法 |
| 执行时机 | 在Servlet前后 | 在Handler前后 |
在实际项目中,我通常这样分工:
- 过滤器处理:跨域、编码、压缩等底层HTTP处理
- 拦截器处理:权限校验、日志记录、参数校验等业务逻辑
5. 性能优化与常见问题排查
5.1 过滤器性能优化技巧
- 减少doFilter中的同步操作:避免在过滤器中直接进行数据库查询等IO操作
- 合理设置过滤路径:不要滥用/*匹配,精确指定需要过滤的URL模式
- 使用FilterRegistrationBean动态注册(Spring环境):
java复制@Bean
public FilterRegistrationBean<LogFilter> loggingFilter() {
FilterRegistrationBean<LogFilter> reg = new FilterRegistrationBean<>();
reg.setFilter(new LogFilter());
reg.addUrlPatterns("/api/*");
reg.setOrder(Ordered.HIGHEST_PRECEDENCE);
return reg;
}
5.2 典型问题排查指南
问题1:过滤器不生效
- 检查web.xml配置是否正确,特别是filter-class的包路径
- 确认url-pattern能匹配到目标请求
- 查看是否有更高优先级的过滤器中断了过滤器链
问题2:监听器事件重复触发
- 检查是否在web.xml中重复声明了监听器
- 确认没有在代码中手动创建多余的监听器实例
- 对于Spring项目,避免同时使用@Component和web.xml两种注册方式
问题3:内存泄漏问题
- 在contextDestroyed和sessionDestroyed中及时释放资源
- 移除对HttpSession的强引用
- 使用WeakHashMap代替普通Map存储会话数据
在大型项目中,我习惯使用Arthas工具动态监控过滤器和监听器的执行情况,通过trace命令可以清晰看到每个请求经过的过滤器链和触发的事件。
