Skip to content

拦截器与过滤器

一、拦截器(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 MVCServlet API
作用范围Controller 方法所有请求(包括静态资源)
IOC 容器在容器内不在容器内
配置方式WebMvcConfigurerFilterRegistrationBean
执行时机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 的执行顺序?

@OrderFilterRegistrationBean.setOrder() 指定的顺序,数字越小越先执行

Q4: 如何让 Filter 获取 Spring Bean?

通过 @Component 注解让 Spring 管理,或在 FilterRegistrationBean 中手动注入

Q5: CORS 预检请求是什么?

  • OPTIONS 请求
  • 浏览器在跨域请求前自动发送
  • 检查服务器是否允许该跨域请求

九、下一章预告

下一章我们将学习 Spring MVC 异常处理机制

  • @ExceptionHandler
  • @ControllerAdvice
  • SimpleMappingExceptionResolver
  • 自定义异常处理器

基于 MIT 许可发布