1. 拦截器与过滤器的本质差异
在Java Web开发中,拦截器(Interceptor)和过滤器(Filter)是两种经常被混淆但又截然不同的机制。理解它们的本质区别,是正确选择和使用的基础。
1.1 工作层面的差异
过滤器是Servlet规范定义的标准组件,工作在Web容器层面。当请求到达Servlet容器(如Tomcat)时,过滤器链最先接触到请求,可以对请求和响应进行预处理和后处理。这意味着过滤器能处理所有进入容器的请求,包括静态资源。
拦截器则是Spring MVC框架提供的机制,工作在Spring的DispatcherServlet之后。只有当请求通过了Servlet容器并进入Spring MVC的处理流程后,拦截器才会介入。因此拦截器无法处理直接访问静态资源的请求。
关键区别:过滤器是Servlet容器级,拦截器是框架级。过滤器能处理所有请求,拦截器只能处理Spring MVC路由的请求。
1.2 实现原理对比
过滤器的实现基于Java EE的Filter接口:
java复制public interface Filter {
void init(FilterConfig filterConfig);
void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain);
void destroy();
}
拦截器基于Spring的HandlerInterceptor接口:
java复制public interface HandlerInterceptor {
boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler);
void postHandle(HttpServletRequest request, HttpServletResponse response,
Object handler, ModelAndView modelAndView);
void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex);
}
从接口设计就能看出两者的关键差异:
- 过滤器使用责任链模式,通过FilterChain传递请求
- 拦截器分为preHandle、postHandle和afterCompletion三个阶段
- 过滤器处理的是Servlet原生的Request/Response
- 拦截器处理的是Spring封装过的HttpServletRequest/Response
1.3 执行时机与作用范围
一个请求在Spring MVC中的完整处理流程如下:
- 客户端发送请求
- Servlet容器接收请求,应用过滤器链
- 请求到达DispatcherServlet
- 执行拦截器的preHandle方法
- 控制器方法执行
- 执行拦截器的postHandle方法
- 视图渲染
- 执行拦截器的afterCompletion方法
- 响应返回客户端
这个流程清晰地展示了过滤器和拦截器各自的作用位置。过滤器在更底层,拦截器在更上层。
2. 权限控制场景下的实现选择
权限控制是Web开发的常见需求,选择过滤器还是拦截器需要根据具体场景决定。
2.1 适合使用过滤器的场景
- 跨域处理:需要在所有请求(包括OPTIONS预检请求)上添加CORS头
- 全局字符编码:统一设置请求和响应的编码方式
- XSS防护:对所有请求参数进行过滤
- 敏感词过滤:对请求和响应内容进行扫描
- 全站压缩:对响应内容进行Gzip压缩
过滤器实现示例:
java复制public class AuthFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) {
HttpServletRequest req = (HttpServletRequest) request;
String token = req.getHeader("Authorization");
if (!validateToken(token)) {
((HttpServletResponse)response).sendError(401);
return;
}
chain.doFilter(request, response);
}
}
2.2 适合使用拦截器的场景
- 细粒度权限控制:基于方法注解的权限校验
- 业务参数预处理:统一处理分页参数、排序参数等
- 接口耗时监控:记录Controller方法的执行时间
- 登录状态检查:检查会话是否有效
- 数据绑定检查:验证模型绑定结果
拦截器实现示例:
java复制public class AuthInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) {
Method method = ((HandlerMethod)handler).getMethod();
RequireAuth auth = method.getAnnotation(RequireAuth.class);
if (auth != null && !checkPermission(auth.value())) {
response.sendError(403);
return false;
}
return true;
}
}
2.3 混合使用的典型案例
在实际项目中,经常需要同时使用过滤器和拦截器:
-
过滤器处理:
- 全局跨域设置
- 请求日志记录
- 基础认证检查
-
拦截器处理:
- 基于注解的权限控制
- 业务参数预处理
- 响应数据包装
这种分层设计既保证了基础功能的全覆盖,又能实现细粒度的业务控制。
3. Spring MVC拦截器深度实现
3.1 拦截器注册与配置
在Spring Boot中配置拦截器:
java复制@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LogInterceptor())
.addPathPatterns("/api/**")
.excludePathPatterns("/api/public/**");
registry.addInterceptor(new AuthInterceptor())
.addPathPatterns("/api/admin/**")
.order(1);
}
}
关键配置项:
addPathPatterns:指定拦截路径,支持Ant风格excludePathPatterns:排除路径order:设置拦截器执行顺序,值越小优先级越高
3.2 多拦截器执行顺序
当配置多个拦截器时,执行顺序如下:
- 拦截器A的preHandle
- 拦截器B的preHandle
- 控制器方法执行
- 拦截器B的postHandle
- 拦截器A的postHandle
- 视图渲染
- 拦截器B的afterCompletion
- 拦截器A的afterCompletion
如果某个preHandle返回false,后续拦截器和控制器方法都不会执行,但已通过preHandle的拦截器的afterCompletion仍会执行。
3.3 拦截器中的异常处理
拦截器各阶段的异常处理策略不同:
-
preHandle异常:
- 直接抛出,不会进入控制器方法
- 已通过的拦截器的afterCompletion仍会执行
-
postHandle异常:
- 如果preHandle通过,afterCompletion一定会执行
- 异常会传播,可以被@ControllerAdvice捕获
-
afterCompletion异常:
- 会被记录但不会影响响应
- 建议在此方法中进行资源清理等非关键操作
最佳实践是在preHandle中进行参数校验和权限检查,在postHandle中包装响应数据,在afterCompletion中释放资源。
4. 过滤器的高级应用技巧
4.1 过滤器链的执行机制
Servlet规范定义了过滤器链的执行顺序:
- 根据web.xml中
<filter-mapping>的顺序执行 - 在Spring Boot中,可以通过
FilterRegistrationBean控制顺序
java复制@Bean
public FilterRegistrationBean<AuthFilter> authFilter() {
FilterRegistrationBean<AuthFilter> reg = new FilterRegistrationBean<>();
reg.setFilter(new AuthFilter());
reg.setOrder(Ordered.HIGHEST_PRECEDENCE + 1);
return reg;
}
4.2 异步请求的特殊处理
对于异步请求(如Servlet 3.0的asyncSupported=true),过滤器需要特殊处理:
java复制public class AsyncFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) {
if (request.isAsyncStarted()) {
// 异步请求的特殊处理
AsyncContext context = request.getAsyncContext();
context.addListener(new AsyncListener() {
// 实现回调方法
});
}
chain.doFilter(request, response);
}
}
4.3 性能优化建议
- 避免频繁操作:不要在过滤器中做耗时的IO操作
- 合理使用缓存:对用户权限等数据进行缓存
- 减少包装类创建:谨慎使用HttpServletRequestWrapper
- 注意线程安全:过滤器是单例的,不要使用实例变量
- 合理设置order:高频过滤器应该放在前面
5. 权限控制实战案例
5.1 基于JWT的认证方案
完整流程实现:
- 登录接口颁发JWT令牌
java复制public String login(String username, String password) {
// 验证凭证
User user = userService.authenticate(username, password);
// 生成JWT
return Jwts.builder()
.setSubject(user.getId())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION))
.signWith(SignatureAlgorithm.HS512, SECRET)
.compact();
}
- 过滤器校验JWT
java复制public class JwtFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) {
String token = resolveToken(request);
if (token != null && validateToken(token)) {
Authentication auth = getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(auth);
}
chain.doFilter(request, response);
}
}
- 拦截器进行权限检查
java复制public class PermissionInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) {
Method method = ((HandlerMethod)handler).getMethod();
PreAuthorize auth = method.getAnnotation(PreAuthorize.class);
if (auth != null && !hasPermission(auth.value())) {
throw new AccessDeniedException("Forbidden");
}
return true;
}
}
5.2 动态权限控制方案
结合数据库实现动态权限:
- 权限数据模型设计:
sql复制CREATE TABLE permission (
id BIGINT PRIMARY KEY,
code VARCHAR(50) UNIQUE,
description VARCHAR(100)
);
CREATE TABLE role_permission (
role_id BIGINT,
permission_id BIGINT,
PRIMARY KEY (role_id, permission_id)
);
CREATE TABLE user_role (
user_id BIGINT,
role_id BIGINT,
PRIMARY KEY (user_id, role_id)
);
- 权限拦截器实现:
java复制public class DynamicAuthInterceptor implements HandlerInterceptor {
@Autowired
private PermissionService permissionService;
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) {
String userId = getCurrentUserId();
String requestURI = request.getRequestURI();
String method = request.getMethod();
if (!permissionService.hasPermission(userId, requestURI, method)) {
throw new AccessDeniedException("No permission");
}
return true;
}
}
5.3 接口级别的权限控制
结合Spring Security实现方法级权限:
- 启用方法安全:
java复制@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
@Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
// 自定义表达式处理器
return new CustomMethodSecurityExpressionHandler();
}
}
- 在Controller方法上使用注解:
java复制@RestController
@RequestMapping("/api/users")
public class UserController {
@PreAuthorize("hasPermission('user', 'read')")
@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {
// ...
}
@PreAuthorize("hasPermission('user', 'write')")
@PostMapping
public User createUser(@RequestBody User user) {
// ...
}
}
6. 常见问题与解决方案
6.1 拦截器不生效的排查步骤
-
检查拦截器是否被正确注册
- 确认配置类被@ComponentScan扫描到
- 检查addInterceptors方法是否被调用
-
验证路径匹配
- 检查addPathPatterns是否包含目标路径
- 确认excludePathPatterns没有意外排除
-
检查顺序问题
- 如果有多个拦截器,确认order设置正确
- 可能被更高order的拦截器阻断
-
确认HandlerMapping
- 某些自定义HandlerMapping可能需要特殊配置
6.2 过滤器执行顺序混乱问题
常见原因及解决:
-
web.xml与JavaConfig混用:
- 统一使用一种配置方式
- 如果混用,web.xml中的filter-mapping优先级更高
-
Spring Boot自动配置干扰:
- 使用@Order或FilterRegistrationBean明确指定顺序
- 禁用不需要的自动配置过滤器
-
Servlet容器差异:
- Tomcat和Jetty对filter-mapping顺序处理可能不同
- 建议完全使用Spring Boot的配置方式
6.3 性能瓶颈定位
当系统出现性能问题时,如何判断是否是过滤器/拦截器导致:
-
使用Spring Boot Actuator的metrics端点:
code复制/actuator/metrics/http.server.requests -
添加TraceFilter记录耗时:
java复制public class TraceFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) {
long start = System.currentTimeMillis();
chain.doFilter(request, response);
long cost = System.currentTimeMillis() - start;
if (cost > 100) {
log.warn("Slow request: {}ms {}", cost,
((HttpServletRequest)request).getRequestURI());
}
}
}
- 使用Profiling工具:
- Arthas的trace命令
- JProfiler/YourKit的CPU分析
7. 最佳实践与经验总结
7.1 设计原则
- 单一职责:每个过滤器/拦截器只做一件事
- 无状态:不要使用实例变量保存状态
- 快速失败:尽早拒绝非法请求
- 明确边界:过滤器做容器级处理,拦截器做业务级处理
- 可观测性:添加足够的日志和指标
7.2 性能优化
- 缓存验证结果:如JWT解析结果可以短期缓存
- 避免重复解析:如请求体只能读取一次,需要合理包装
- 异步处理:对于耗时操作考虑异步处理
- 精简逻辑:拦截器中的逻辑应该尽可能轻量
- 合理排序:高频通过的拦截器应该放在前面
7.3 可维护性建议
- 统一配置:集中管理所有拦截器/过滤器的配置
- 清晰命名:使用能反映功能的命名,如LoggingFilter而非Filter1
- 完善文档:为每个组件添加用途说明
- 单元测试:为关键拦截逻辑编写测试
- 版本兼容:考虑不同Spring Boot版本的行为差异
在实际项目中,我通常会建立一个基础的拦截器/过滤器框架,包含以下组件:
- 基础认证拦截器
- 请求日志过滤器
- 性能监控拦截器
- 异常处理拦截器
- 响应包装拦截器
这种架构既能满足大多数项目的需求,又能保持足够的灵活性应对特殊场景。关键是要根据项目的具体规模和需求,找到合适的抽象层级,避免过度设计。
