拦截器与过滤器
一、拦截器(HandlerInterceptor)
接口定义
java
public interface HandlerInterceptor {
// 前置处理,返回 true 继续执行,返回 false 中断
default boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
return true;
}
// 后置处理(视图渲染之前)
default void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler,
ModelAndView modelAndView) throws Exception {
}
// 完成后处理(视图渲染之后)
default void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) throws Exception {
}
}执行时机
┌─────────────────────────────────────────────────────────────────┐
│ HandlerInterceptor 执行时机 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ DispatcherServlet.doDispatch() │
│ │ │
│ ├─ preHandle() ──────────────▶ Controller ──────────┐ │
│ │ │ │
│ │ ↓ │ │
│ │ postHandle() │ │
│ │ ↓ │ │
│ │ 视图渲染 │ │
│ │ ↓ │ │
│ ├─ afterCompletion() ◀────────────────────────────────┘ │
│ │ │
└─────────────────────────────────────────────────────────────────┘实现示例
java
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
// 1. 登录检查
HttpSession session = request.getSession();
User user = (User) session.getAttribute("user");
if (user == null) {
// 未登录,重定向到登录页
response.sendRedirect("/login");
return false; // 中断执行
}
// 2. 权限检查
if (!hasPermission(user, handler)) {
response.sendRedirect("/403");
return false;
}
return true; // 继续执行
}
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler,
ModelAndView modelAndView) throws Exception {
// 3. 视图渲染前处理
if (modelAndView != null) {
modelAndView.addObject("currentUser", getCurrentUser());
}
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) throws Exception {
// 4. 请求完成后处理(如清理资源)
long startTime = (long) request.getAttribute("startTime");
System.out.println("请求处理耗时: " + (System.currentTimeMillis() - startTime) + "ms");
}
}二、注册拦截器
WebMvcConfigurer 方式
java
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
@Autowired
private LoggingInterceptor loggingInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/api/**") // 拦截路径
.excludePathPatterns("/api/login", // 排除路径
"/api/public/**",
"/api/health");
registry.addInterceptor(loggingInterceptor)
.addPathPatterns("/**")
.order(1); // 执行顺序,数字越小越先执行
}
}URL 路径模式
java
registry.addInterceptor(interceptor)
.addPathPatterns("/api/**") // 匹配 /api/ 下的所有路径
.addPathPatterns("/users/{id}/**") // 支持路径变量
.excludePathPatterns("/api/login") // 精确排除
.excludePathPatterns("/api/public/*") // 单层通配符
.excludePathPatterns("/api/static/**") // 多层通配符三、过滤器(Filter)
Filter 接口
java
public interface Filter {
default void init(FilterConfig filterConfig) throws ServletException {
// 初始化
}
void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException;
default void destroy() {
// 销毁
}
}实现示例
java
// 字符编码过滤器
@Component
@Order(1)
public class EncodingFilter implements Filter {
private String encoding = "UTF-8";
@Override
public void init(FilterConfig filterConfig) {
String enc = filterConfig.getInitParameter("encoding");
if (enc != null) {
this.encoding = enc;
}
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
req.setCharacterEncoding(encoding);
resp.setCharacterEncoding(encoding);
chain.doFilter(request, response); // 继续执行
}
@Override
public void destroy() {
// 清理资源
}
}
// 请求日志过滤器
@Component
@Order(2)
public class LoggingFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
long start = System.currentTimeMillis();
try {
chain.doFilter(request, response); // 注意:这里传的是原 request
} finally {
long duration = System.currentTimeMillis() - start;
System.out.printf("%s %s - %dms%n",
req.getMethod(), req.getRequestURI(), duration);
}
}
}注册过滤器
java
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean<EncodingFilter> encodingFilter() {
FilterRegistrationBean<EncodingFilter> registration = new FilterRegistrationBean<>();
registration.setFilter(new EncodingFilter());
registration.addUrlPatterns("/*");
registration.setOrder(1);
return registration;
}
}四、拦截器 vs 过滤器
| 对比项 | 拦截器 (Interceptor) | 过滤器 (Filter) |
|---|---|---|
| 来源 | Spring MVC | Servlet API |
| 作用范围 | Controller 方法 | 所有请求(包括静态资源) |
| IOC 容器 | 在容器内 | 不在容器内 |
| 配置方式 | WebMvcConfigurer | FilterRegistrationBean |
| 执行时机 | DispatcherServlet 之后 | Servlet 容器层面 |
| 可以获取 | Controller 方法、ModelAndView | 仅 ServletRequest/Response |
| 用途 | 登录校验、权限、日志 | 字符编码、CORS、Security |
┌─────────────────────────────────────────────────────────────────┐
│ 请求处理链 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Servlet Container │
│ │ │
│ ├─ Filter 1 (字符编码) │
│ │ │ │
│ ├─ Filter 2 (Security) │
│ │ │ │
│ ├─ Filter 3 (CORS) │
│ │ │ │
│ └─ DispatcherServlet │
│ │ │
│ ├─ Interceptor 1 (preHandle) │
│ │ │ │
│ ├─ Controller │
│ │ │ │
│ └─ Interceptor 1 (postHandle/afterCompletion) │
│ │
└─────────────────────────────────────────────────────────────────┘五、CORS 跨域处理
@CrossOrigin 注解
java
// 方法级
@RestController
@RequestMapping("/api")
public class UserController {
@CrossOrigin(origins = "http://example.com", maxAge = 3600)
@GetMapping("/users")
public List<User> getUsers() {
return users;
}
// 类级
@CrossOrigin(origins = "*")
@RestController
@RequestMapping("/api")
public class AllController { }
}全局 CORS 配置
java
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("http://example.com", "http://localhost:3000")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true) // 允许携带 Cookie
.maxAge(3600); // 预检请求缓存时间
}
}Filter 方式(Spring Security 中使用)
java
@Configuration
public class CorsFilterConfig {
@Bean
public CorsFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(Arrays.asList("http://example.com"));
config.setAllowedMethods(Arrays.asList("GET", "POST"));
config.setAllowedHeaders(Arrays.asList("*"));
config.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/api/**", config);
return new CorsFilter(source);
}
}六、请求日志记录
拦截器实现
java
@Component
public class RequestLoggingInterceptor implements HandlerInterceptor {
private final Logger log = LoggerFactory.getLogger(getClass());
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
long startTime = System.currentTimeMillis();
request.setAttribute("startTime", startTime);
HandlerMethod handlerMethod = (HandlerMethod) handler;
log.info("▶ {}.{}() 开始处理请求",
handlerMethod.getBeanType().getSimpleName(),
handlerMethod.getMethod().getName());
return true;
}
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler,
ModelAndView modelAndView) throws Exception {
HandlerMethod handlerMethod = (HandlerMethod) handler;
log.info("◀ {}.{}() 处理完成",
handlerMethod.getBeanType().getSimpleName(),
handlerMethod.getMethod().getName());
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) throws Exception {
long startTime = (long) request.getAttribute("startTime");
long duration = System.currentTimeMillis() - startTime;
if (ex != null) {
log.error("✗ 请求处理异常: {}ms - {}", duration, ex.getMessage());
} else {
log.info("✓ 请求处理完成: {}ms", duration);
}
}
}Filter 实现
java
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class RequestLogFilter implements Filter {
private final Logger log = LoggerFactory.getLogger(getClass());
@Override
public void doFilter(ServletRequest servletRequest,
ServletResponse servletResponse,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
// 包装请求,缓存 body
CachedBodyHttpServletRequest wrappedRequest = new CachedBodyHttpServletRequest(request);
long start = System.currentTimeMillis();
try {
chain.doFilter(wrappedRequest, servletResponse);
} finally {
long duration = System.currentTimeMillis() - start;
log.info("{} {} {} - {}ms",
request.getMethod(),
request.getRequestURI(),
request.getQueryString() != null ? "?" + request.getQueryString() : "",
duration);
}
}
}七、登录拦截实战
登录拦截器
java
@Component
public class AuthInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
// 放行 OPTIONS 预检请求
if ("OPTIONS".equals(request.getMethod())) {
return true;
}
// 检查登录状态
String token = request.getHeader("Authorization");
if (StringUtils.isBlank(token)) {
token = request.getParameter("token");
}
if (StringUtils.isBlank(token)) {
sendUnauthorized(response, "请先登录");
return false;
}
// 验证 token
User user = tokenService.validate(token);
if (user == null) {
sendUnauthorized(response, "Token 无效或已过期");
return false;
}
// 将用户信息存入 request
request.setAttribute("currentUser", user);
return true;
}
private void sendUnauthorized(HttpServletResponse response, String message)
throws IOException {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write("{\"code\":401,\"message\":\"" + message + "\"}");
}
}注册
java
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private AuthInterceptor authInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authInterceptor)
.addPathPatterns("/api/**")
.excludePathPatterns(
"/api/user/login", // 登录
"/api/user/register", // 注册
"/api/public/**", // 公开接口
"/api/health", // 健康检查
"/error" // 错误页
);
}
}八、面试高频问题
Q1: 拦截器和过滤器的区别?
- 过滤器:Servlet 规范,基于函数回调
- 拦截器:Spring MVC,基于 AOP
Q2: preHandle 返回 false 会怎样?
请求处理中断,后续拦截器的 preHandle 和 Controller 都不会执行,但已执行的 afterCompletion 会被调用
Q3: Filter 的执行顺序?
按 @Order 或 FilterRegistrationBean.setOrder() 指定的顺序,数字越小越先执行
Q4: 如何让 Filter 获取 Spring Bean?
通过 @Component 注解让 Spring 管理,或在 FilterRegistrationBean 中手动注入
Q5: CORS 预检请求是什么?
OPTIONS请求- 浏览器在跨域请求前自动发送
- 检查服务器是否允许该跨域请求
九、下一章预告
下一章我们将学习 Spring MVC 异常处理机制:
- @ExceptionHandler
- @ControllerAdvice
- SimpleMappingExceptionResolver
- 自定义异常处理器