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之前和响应返回客户端之后执行逻辑
- 请求/响应修改:能够修改请求参数和响应内容
- 安全防护:提供统一的安全检查、认证授权机制
通过合理使用过滤器,你可以:
- 实现统一的日志记录和性能监控
- 构建强大的安全防护体系
- 处理跨域请求和编码问题
- 实现请求/响应的转换和增强
最佳实践提示:
保持过滤器职责单一,每个过滤器只关注一个特定功能
注意过滤器执行顺序,避免意外的副作用
考虑性能影响,避免在过滤器中执行耗时操作
妥善处理异常,确保请求链不会意外中断
过滤器与拦截器各有其适用场景,在实际项目中往往需要结合使用,共同构建完整的请求处理管道。
深入解析Servlet过滤器机制
1694

被折叠的 条评论
为什么被折叠?



