一、引言:Filter与Listener的重要性
在现代Java Web开发中,Filter(过滤器)和Listener(监听器)是Servlet规范中两个极其重要的组件,它们为开发者提供了强大的AOP(面向切面编程)能力和事件驱动编程模型。通过合理运用Filter和Listener,我们可以实现诸多高级功能,如权限控制、日志记录、性能监控、会话跟踪等,而无需修改核心业务代码。
本文将深入探讨Filter和Listener的工作原理、应用场景以及高级实践技巧,帮助开发者在实际项目中充分发挥它们的威力。
二、Filter深度解析
1. Filter基础概念
Filter是位于客户端与服务器资源之间的一个中间组件,可以对请求和响应进行预处理和后处理。它类似于一个"筛子",能够对通过的请求和响应进行过滤操作。
public class LoggingFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 初始化代码
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
// 请求预处理
long startTime = System.currentTimeMillis();
// 传递给下一个Filter或目标资源
chain.doFilter(request, response);
// 响应后处理
long duration = System.currentTimeMillis() - startTime;
System.out.println("Request processed in " + duration + "ms");
}
@Override
public void destroy() {
// 清理代码
}
}
2. Filter核心应用场景
2.1 认证与授权
public class AuthFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
HttpSession session = request.getSession(false);
if (session == null || session.getAttribute("user") == null) {
response.sendRedirect(request.getContextPath() + "/login");
} else {
chain.doFilter(request, response);
}
}
}
2.2 请求日志记录
public class RequestLoggingFilter implements Filter {
private static final Logger logger = LoggerFactory.getLogger(RequestLoggingFilter.class);
@Override
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
String requestURI = request.getRequestURI();
String remoteAddr = request.getRemoteAddr();
String method = request.getMethod();
logger.info("Request [{}] {} from {}", method, requestURI, remoteAddr);
long startTime = System.currentTimeMillis();
chain.doFilter(request, res);
long duration = System.currentTimeMillis() - startTime;
logger.info("Request [{}] {} completed in {} ms", method, requestURI, duration);
}
}
2.3 跨域处理
public class CorsFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "x-requested-with, Content-Type");
chain.doFilter(req, res);
}
}
3. Filter高级应用
3.1 请求/响应包装
public class CompressionFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
String acceptEncoding = request.getHeader("Accept-Encoding");
if (acceptEncoding != null && acceptEncoding.contains("gzip")) {
CompressionResponseWrapper wrappedResponse = new CompressionResponseWrapper(response);
wrappedResponse.setHeader("Content-Encoding", "gzip");
chain.doFilter(request, wrappedResponse);
wrappedResponse.finish();
} else {
chain.doFilter(request, response);
}
}
}
class CompressionResponseWrapper extends HttpServletResponseWrapper {
private GZIPOutputStream gzipStream;
private PrintWriter writer;
private ServletOutputStream output;
// 实现压缩逻辑的包装器
// ...
}
3.2 性能监控
public class PerformanceFilter implements Filter {
private static final Logger logger = LoggerFactory.getLogger(PerformanceFilter.class);
private static final int SLOW_REQUEST_THRESHOLD = 500; // ms
@Override
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
long startTime = System.currentTimeMillis();
chain.doFilter(req, res);
long duration = System.currentTimeMillis() - startTime;
if (duration > SLOW_REQUEST_THRESHOLD) {
HttpServletRequest request = (HttpServletRequest) req;
logger.warn("Slow request [{}] {} took {} ms",
request.getMethod(), request.getRequestURI(), duration);
}
}
}
三、Listener全面剖析
1. Listener基础概念
Listener用于监听Web应用中的各种事件,如ServletContext、HttpSession和ServletRequest的创建与销毁,属性变化等。它是观察者模式的典型实现。
2. 核心Listener类型
2.1 ServletContext监听器
public class AppContextListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
// 应用启动时初始化
ServletContext context = sce.getServletContext();
String appName = context.getInitParameter("appName");
System.out.println("Application " + appName + " is starting...");
// 初始化数据库连接池
DataSource dataSource = createDataSource();
context.setAttribute("dataSource", dataSource);
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
// 应用关闭时清理
ServletContext context = sce.getServletContext();
DataSource dataSource = (DataSource) context.getAttribute("dataSource");
closeDataSource(dataSource);
System.out.println("Application is shutting down...");
}
}
2.2 Session监听器
public class SessionTracker implements HttpSessionListener {
private static final AtomicInteger activeSessions = new AtomicInteger();
public static int getActiveSessions() {
return activeSessions.get();
}
@Override
public void sessionCreated(HttpSessionEvent se) {
activeSessions.incrementAndGet();
HttpSession session = se.getSession();
System.out.println("Session created: " + session.getId());
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
activeSessions.decrementAndGet();
HttpSession session = se.getSession();
System.out.println("Session destroyed: " + session.getId());
}
}
2.3 请求监听器
public class RequestAnalyzer implements ServletRequestListener {
@Override
public void requestInitialized(ServletRequestEvent sre) {
HttpServletRequest request = (HttpServletRequest) sre.getServletRequest();
request.setAttribute("startTime", System.currentTimeMillis());
}
@Override
public void requestDestroyed(ServletRequestEvent sre) {
HttpServletRequest request = (HttpServletRequest) sre.getServletRequest();
long startTime = (Long) request.getAttribute("startTime");
long duration = System.currentTimeMillis() - startTime;
System.out.println("Request to " + request.getRequestURI() + " took " + duration + "ms");
}
}
3. Listener高级应用
3.1 在线用户统计
public class OnlineUserListener implements HttpSessionListener, HttpSessionAttributeListener {
private static final Set<String> onlineUsers = Collections.synchronizedSet(new HashSet<>());
@Override
public void sessionCreated(HttpSessionEvent se) {
// 会话创建时不做处理
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
HttpSession session = se.getSession();
String username = (String) session.getAttribute("username");
if (username != null) {
onlineUsers.remove(username);
}
}
@Override
public void attributeAdded(HttpSessionBindingEvent event) {
if ("username".equals(event.getName())) {
onlineUsers.add((String) event.getValue());
}
}
@Override
public void attributeRemoved(HttpSessionBindingEvent event) {
if ("username".equals(event.getName())) {
onlineUsers.remove((String) event.getValue());
}
}
@Override
public void attributeReplaced(HttpSessionBindingEvent event) {
// 处理用户名变更情况
}
public static int getOnlineUserCount() {
return onlineUsers.size();
}
public static Set<String> getOnlineUsers() {
return new HashSet<>(onlineUsers);
}
}
3.2 应用启动初始化
public class AppInitializer implements ServletContextListener {
private ScheduledExecutorService scheduler;
@Override
public void contextInitialized(ServletContextEvent sce) {
// 初始化缓存
initCache();
// 启动定时任务
scheduler = Executors.newScheduledThreadPool(1);
scheduler.scheduleAtFixedRate(this::refreshCache, 1, 1, TimeUnit.HOURS);
// 注册JMX监控
registerMBeans();
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
// 优雅关闭定时任务
if (scheduler != null) {
scheduler.shutdownNow();
}
// 清理缓存
clearCache();
}
}
四、Filter与Listener的协同应用
1. 实现请求追踪
// 监听器记录请求开始时间
public class RequestTrackingListener implements ServletRequestListener {
@Override
public void requestInitialized(ServletRequestEvent sre) {
sre.getServletRequest().setAttribute("startTime", System.nanoTime());
}
}
// 过滤器计算处理时间并记录
public class RequestTrackingFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
long startTime = (Long) req.getAttribute("startTime");
chain.doFilter(req, res);
long duration = System.nanoTime() - startTime;
// 记录到监控系统
Metrics.recordRequestTime(duration);
}
}
2. 实现分布式会话管理
// 监听会话创建和销毁
public class DistributedSessionListener implements HttpSessionListener {
@Override
public void sessionCreated(HttpSessionEvent se) {
// 将会话信息同步到Redis
RedisSessionManager.registerSession(se.getSession());
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
// 从Redis移除会话信息
RedisSessionManager.unregisterSession(se.getSession());
}
}
// 过滤器检查会话有效性
public class SessionValidationFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpSession session = request.getSession(false);
if (session != null && !RedisSessionManager.isValid(session.getId())) {
session.invalidate();
((HttpServletResponse) res).sendRedirect("/session-expired");
return;
}
chain.doFilter(req, res);
}
}
五、Spring框架中的Filter与Listener
虽然Spring提供了更高级的AOP和事件机制,但原生的Filter和Listener仍然有其用武之地,特别是在与Servlet容器直接交互的场景。
1. 在Spring Boot中注册Filter
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean<LoggingFilter> loggingFilter() {
FilterRegistrationBean<LoggingFilter> registration = new FilterRegistrationBean<>();
registration.setFilter(new LoggingFilter());
registration.addUrlPatterns("/*");
registration.setOrder(Ordered.HIGHEST_PRECEDENCE);
return registration;
}
}
2. 使用Spring事件替代部分Listener
@Component
public class SessionEventListener {
@EventListener
public void handleSessionCreated(SessionCreatedEvent event) {
// 处理会话创建事件
}
@EventListener
public void handleSessionDestroyed(SessionDestroyedEvent event) {
// 处理会话销毁事件
}
}
六、性能优化与最佳实践
-
Filter链顺序优化:将最可能拦截请求的Filter放在前面,减少不必要的处理
-
合理使用异步处理:对于耗时操作,考虑使用异步Filter
@WebFilter(urlPatterns = "/*", asyncSupported = true)
public class AsyncFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
AsyncContext asyncContext = req.startAsync();
asyncContext.start(() -> {
try {
chain.doFilter(asyncContext.getRequest(), asyncContext.getResponse());
} catch (Exception e) {
// 处理异常
} finally {
asyncContext.complete();
}
});
}
}
-
避免在Listener中执行耗时操作:特别是contextInitialized方法,会影响应用启动速度
-
线程安全考虑:Listener通常是单例的,必须确保线程安全
-
合理使用初始化参数:通过web.xml或注解配置Filter和Listener的参数,提高灵活性
七、常见问题与解决方案
1. Filter不生效的可能原因
-
未正确配置urlPatterns
-
未在web.xml中声明或缺少@WebFilter注解
-
Filter顺序问题导致被其他Filter拦截
-
缺少chain.doFilter()调用
2. Listener事件不触发的原因
-
监听器未正确注册
-
事件源生命周期管理不当
-
在Spring环境中,可能需要同时使用原生Listener和Spring事件
3. 性能问题排查
-
检查Filter链是否过长
-
确认Listener中是否有阻塞操作
-
使用Profiler工具分析耗时
八、总结
Filter和Listener作为Java Web开发中的两大基石,为开发者提供了强大的扩展能力。通过本文的深入探讨,我们了解了:
-
Filter的拦截机制和常见应用场景
-
Listener的事件监听模型和典型用法
-
两者协同工作的高级模式
-
在Spring框架中的集成方式
-
性能优化和最佳实践
合理运用Filter和Listener,可以极大地提高Web应用的灵活性、可维护性和可扩展性,同时保持核心业务代码的整洁。希望本文能为你的Web开发实践提供有价值的参考。