1. 引言:过滤器在Web架构中的核心地位
在现代Web应用开发中,请求处理往往需要经过多个层次的检查和加工。Spring拦截器虽然功能强大,但它仅仅是Spring MVC框架内部的处理机制。而在更广泛的Web层面,我们需要一个更加基础、更加通用的机制来处理所有HTTP请求,这就是Servlet过滤器(Filter)。
过滤器是Java Servlet规范定义的标准组件,它位于Web容器层面,能够拦截所有进入应用的HTTP请求和响应。与Spring拦截器相比,过滤器的控制范围更广,它可以处理静态资源、JSP页面以及其他非Spring管理的请求。
比喻:如果将Web应用比作一座城堡,那么过滤器就是城堡外围的安检系统和防御工事。所有进出城堡的人员(请求和响应)都必须经过这些关卡的检查和处理,而Spring拦截器更像是城堡内部特定区域的警卫。
2. 过滤器 vs 拦截器:职责边界与协作关系
为了更好地理解过滤器的定位,我们需要明确它与拦截器的区别和协作方式:
特性 |
过滤器 (Filter) |
拦截器 (Interceptor) |
规范标准 |
Java Servlet规范 |
Spring框架特有 |
作用范围 |
所有HTTP请求(包括静态资源) |
仅Spring MVC控制的请求 |
依赖关系 |
不依赖Spring,属于Web容器 |
依赖Spring容器和上下文 |
执行时机 |
在Servlet之前执行 |
在DispatcherServlet之后执行 |
访问能力 |
只能访问Servlet API |
可以访问Spring的Bean和上下文 |
下面是过滤器与拦截器在请求处理流程中的协同工作示意图:
3. 过滤器核心接口与工作机制
3.1 Filter接口详解
Servlet过滤器的核心是javax.servlet.Filter接口,它定义了三个方法:
public interface Filter { // 初始化方法 - 容器启动时调用一次 default void init(FilterConfig filterConfig) throws ServletException {} // 过滤方法 - 每次请求都会调用 void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException; // 销毁方法 - 容器关闭时调用一次 default void destroy() {} }
3.2 过滤器链机制
过滤器的核心机制是过滤器链(FilterChain),多个过滤器按照配置顺序形成处理链:
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { // 1. 预处理逻辑(在调用chain.doFilter之前) System.out.println("过滤器预处理: " + ((HttpServletRequest) request).getRequestURI()); // 2. 调用过滤器链中的下一个过滤器 // 如果没有更多过滤器,最终会调用目标Servlet chain.doFilter(request, response); // 3. 后处理逻辑(在调用chain.doFilter之后) System.out.println("过滤器后处理: " + ((HttpServletResponse) response).getStatus()); }
4. 实战演练:自定义过滤器开发
4.1 创建自定义过滤器
让我们通过几个实际案例来演示如何创建和使用过滤器。
示例1:请求日志过滤器
@Component @Order(1) // 指定过滤器执行顺序 public class LoggingFilter implements Filter { private static final Logger logger = LoggerFactory.getLogger(LoggingFilter.class); @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpRequest = (HttpServletRequest) request; HttpServletResponse httpResponse = (HttpServletResponse) response; long startTime = System.currentTimeMillis(); // 包装Response以捕获响应内容(可选) ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(httpResponse); try { // 记录请求开始日志 logger.info("请求开始: {} {}, 客户端IP: {}", httpRequest.getMethod(), httpRequest.getRequestURI(), httpRequest.getRemoteAddr()); // 继续过滤器链 chain.doFilter(request, responseWrapper); } finally { // 记录请求完成日志 long duration = System.currentTimeMillis() - startTime; logger.info("请求完成: {} {}, 状态码: {}, 耗时: {}ms", httpRequest.getMethod(), httpRequest.getRequestURI(), httpResponse.getStatus(), duration); // 确保响应内容被写入客户端 responseWrapper.copyBodyToResponse(); } } }
示例2:身份认证过滤器
@Component @Order(2) public class AuthenticationFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpRequest = (HttpServletRequest) request; HttpServletResponse httpResponse = (HttpServletResponse) response; // 检查是否需要认证 if (requiresAuthentication(httpRequest)) { // 检查认证令牌 String token = httpRequest.getHeader("Authorization"); if (token == null || !isValidToken(token)) { // 认证失败,返回401错误 httpResponse.setStatus(HttpStatus.UNAUTHORIZED.value()); httpResponse.setContentType(MediaType.APPLICATION_JSON_VALUE); String errorResponse = "{\"error\": \"未授权的访问\", \"code\": 401}"; response.getWriter().write(errorResponse); return; // 中断请求处理 } // 认证成功,可以设置用户信息到请求属性中 String userId = extractUserIdFromToken(token); request.setAttribute("userId", userId); } // 继续过滤器链 chain.doFilter(request, response); } private boolean requiresAuthentication(HttpServletRequest request) { String path = request.getRequestURI(); // 排除登录接口和静态资源 return !path.contains("/login") && !path.contains("/public/"); } private boolean isValidToken(String token) { // 实际的令牌验证逻辑 return token != null && token.startsWith("Bearer "); } private String extractUserIdFromToken(String token) { // 从令牌中提取用户ID(实际项目应使用JWT等标准令牌) return "user123"; } }
示例3:CORS跨域过滤器
@Component @Order(3) public class CorsFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpRequest = (HttpServletRequest) request; HttpServletResponse httpResponse = (HttpServletResponse) response; // 设置CORS头部 httpResponse.setHeader("Access-Control-Allow-Origin", "http://localhost:3000"); httpResponse.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"); httpResponse.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization"); httpResponse.setHeader("Access-Control-Allow-Credentials", "true"); httpResponse.setHeader("Access-Control-Max-Age", "3600"); // 处理预检请求 if ("OPTIONS".equalsIgnoreCase(httpRequest.getMethod())) { httpResponse.setStatus(HttpStatus.OK.value()); return; } chain.doFilter(request, response); } }
4.2 注册过滤器的多种方式
在Spring Boot中,有多种方式注册过滤器:
方式1:使用@Component + @Order(推荐)
@Component @Order(1) public class MyFilter implements Filter { // 自动注册,顺序通过@Order指定 }
方式2:使用FilterRegistrationBean(更灵活控制)
@Configuration public class FilterConfig { @Bean public FilterRegistrationBean<LoggingFilter> loggingFilter() { FilterRegistrationBean<LoggingFilter> registrationBean = new FilterRegistrationBean<>(); registrationBean.setFilter(new LoggingFilter()); registrationBean.addUrlPatterns("/*"); registrationBean.setOrder(1); registrationBean.setName("loggingFilter"); return registrationBean; } @Bean public FilterRegistrationBean<AuthenticationFilter> authFilter() { FilterRegistrationBean<AuthenticationFilter> registrationBean = new FilterRegistrationBean<>(); registrationBean.setFilter(new AuthenticationFilter()); registrationBean.addUrlPatterns("/api/*"); registrationBean.setOrder(2); registrationBean.setName("authenticationFilter"); return registrationBean; } }
4.3 测试过滤器效果
创建测试控制器验证过滤器工作:
@RestController @RequestMapping("/api") public class TestController { @GetMapping("/public/data") public ResponseEntity<String> publicData() { return ResponseEntity.ok("这是公开数据,不需要认证"); } @GetMapping("/private/data") public ResponseEntity<String> privateData(@RequestAttribute(value = "userId", required = false) String userId) { if (userId == null) { return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("需要认证"); } return ResponseEntity.ok("这是私有数据,用户ID: " + userId); } @PostMapping("/login") public ResponseEntity<Map<String, String>> login(@RequestParam String username, @RequestParam String password) { // 模拟登录逻辑 Map<String, String> response = new HashMap<>(); response.put("token", "Bearer mock_token_" + username); response.put("username", username); return ResponseEntity.ok(response); } }
使用curl命令测试过滤器:
# 测试公开接口(应正常返回) curl http://localhost:8080/api/public/data # 测试私有接口(应返回401错误) curl http://localhost:8080/api/private/data # 登录获取令牌 curl -X POST -d "username=test&password=123" http://localhost:8080/api/login # 使用令牌访问私有接口 curl -H "Authorization: Bearer mock_token_test" http://localhost:8080/api/private/data
5. 高级应用与最佳实践
5.1 请求/响应包装器
过滤器可以修改请求和响应,但需要包装原始对象:
public class CustomWrapperFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { // 包装请求以修改参数 HttpServletRequest wrappedRequest = new HttpServletRequestWrapper((HttpServletRequest) request) { @Override public String getParameter(String name) { String value = super.getParameter(name); return value != null ? value.toUpperCase() : null; } }; // 包装响应以捕获内容 ContentCachingResponseWrapper wrappedResponse = new ContentCachingResponseWrapper((HttpServletResponse) response); chain.doFilter(wrappedRequest, wrappedResponse); // 处理响应内容 byte[] content = wrappedResponse.getContentAsByteArray(); if (content.length > 0) { String contentString = new String(content, wrappedResponse.getCharacterEncoding()); // 可以修改响应内容 // ... // 更新响应 wrappedResponse.resetBuffer(); wrappedResponse.getWriter().write(contentString); } wrappedResponse.copyBodyToResponse(); } }
5.2 性能监控过滤器
@Component public class MetricsFilter implements Filter { private final MeterRegistry meterRegistry; public MetricsFilter(MeterRegistry meterRegistry) { this.meterRegistry = meterRegistry; } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpRequest = (HttpServletRequest) request; long startTime = System.currentTimeMillis(); try { chain.doFilter(request, response); } finally { long duration = System.currentTimeMillis() - startTime; int status = ((HttpServletResponse) response).getStatus(); // 记录指标 meterRegistry.timer("http.requests") .tags("uri", httpRequest.getRequestURI(), "method", httpRequest.getMethod(), "status", String.valueOf(status)) .record(duration, TimeUnit.MILLISECONDS); } } }
5.3 安全防护过滤器
@Component public class SecurityFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpRequest = (HttpServletRequest) request; HttpServletResponse httpResponse = (HttpServletResponse) response; // 1. XSS防护:清理请求参数 Map<String, String[]> parameterMap = httpRequest.getParameterMap(); for (String key : parameterMap.keySet()) { String[] values = parameterMap.get(key); for (int i = 0; i < values.length; i++) { values[i] = cleanXSS(values[i]); } } // 2. CSRF防护:检查CSRF令牌 if ("POST".equalsIgnoreCase(httpRequest.getMethod()) || "PUT".equalsIgnoreCase(httpRequest.getMethod()) || "DELETE".equalsIgnoreCase(httpRequest.getMethod())) { String csrfToken = httpRequest.getHeader("X-CSRF-TOKEN"); String sessionToken = (String) httpRequest.getSession().getAttribute("csrfToken"); if (csrfToken == null || !csrfToken.equals(sessionToken)) { httpResponse.setStatus(HttpStatus.FORBIDDEN.value()); response.getWriter().write("CSRF token validation failed"); return; } } // 3. 设置安全相关的HTTP头 httpResponse.setHeader("X-Content-Type-Options", "nosniff"); httpResponse.setHeader("X-Frame-Options", "DENY"); httpResponse.setHeader("X-XSS-Protection", "1; mode=block"); httpResponse.setHeader("Strict-Transport-Security", "max-age=31536000"); chain.doFilter(request, response); } private String cleanXSS(String value) { if (value == null) return null; return value.replaceAll("<", "<") .replaceAll(">", ">") .replaceAll("\"", """) .replaceAll("'", "'") .replaceAll("/", "/"); } }
6. 常见问题与解决方案
6.1 过滤器执行顺序问题
问题:多个过滤器的执行顺序不符合预期。
解决方案:
- 使用@Order注解指定顺序(数字越小优先级越高)
- 使用FilterRegistrationBean明确设置order值
- 避免循环依赖,确保过滤器之间没有复杂的依赖关系
6.2 性能优化建议
- 避免频繁的对象创建:在init方法中初始化资源,而不是在doFilter中
- 使用异步处理:对于耗时操作,考虑使用异步过滤器
- 合理设置过滤路径:避免对静态资源等不需要处理的请求进行过滤
- 使用缓存:对重复的计算结果进行缓存
6.3 异常处理策略
public class ExceptionHandlingFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { try { chain.doFilter(request, response); } catch (Exception e) { // 统一异常处理 handleException(e, (HttpServletRequest) request, (HttpServletResponse) response); } } private void handleException(Exception e, HttpServletRequest request, HttpServletResponse response) throws IOException { logger.error("请求处理异常: {} {}", request.getMethod(), request.getRequestURI(), e); response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); response.setContentType(MediaType.APPLICATION_JSON_VALUE); Map<String, Object> errorResponse = new HashMap<>(); errorResponse.put("timestamp", System.currentTimeMillis()); errorResponse.put("status", HttpStatus.INTERNAL_SERVER_ERROR.value()); errorResponse.put("error", "Internal Server Error"); errorResponse.put("message", "系统内部错误,请稍后重试"); errorResponse.put("path", request.getRequestURI()); // 开发环境显示详细错误信息 if (isDevelopmentEnvironment()) { errorResponse.put("exception", e.getClass().getName()); errorResponse.put("detail", e.getMessage()); } response.getWriter().write(new ObjectMapper().writeValueAsString(errorResponse)); } }
7. 总结
Servlet过滤器是Java Web开发中不可或缺的基础组件,它提供了以下核心价值:
- 全局性处理能力:能够拦截所有HTTP请求,包括静态资源和非Spring管理的请求
- 预处理和后处理:可以在请求到达Servlet之前和响应返回客户端之后执行逻辑
- 请求/响应修改:能够修改请求参数和响应内容
- 安全防护:提供统一的安全检查、认证授权机制
通过合理使用过滤器,你可以:
- 实现统一的日志记录和性能监控
- 构建强大的安全防护体系
- 处理跨域请求和编码问题
- 实现请求/响应的转换和增强
最佳实践提示:
保持过滤器职责单一,每个过滤器只关注一个特定功能
注意过滤器执行顺序,避免意外的副作用
考虑性能影响,避免在过滤器中执行耗时操作
妥善处理异常,确保请求链不会意外中断
过滤器与拦截器各有其适用场景,在实际项目中往往需要结合使用,共同构建完整的请求处理管道。