Skip to content

04.3 — 拦截器与异常处理

定位: Spring MVC 的横切关注点——异常统一处理、请求拦截、跨域配置 面试高频度: ⭐⭐⭐⭐ 考查方式: Filter vs Interceptor 对比、@ControllerAdvice 异常处理、CORS 三种配置方式

一、这是什么?为什么需要它?

Spring MVC 需要处理三类横切关注点:异常处理(将 Controller 抛出的异常转换为友好响应)、请求拦截(在 Controller 执行前后执行通用逻辑)、跨域配置(处理浏览器的同源策略限制)。Spring 通过 @ControllerAdviceHandlerInterceptor@CrossOrigin 分别解决这些问题。

二、原理拆解

2.1 统一异常处理

java
@RestControllerAdvice  // = @ControllerAdvice + @ResponseBody
public class GlobalExceptionHandler {
    
    @ExceptionHandler(ResourceNotFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public Result handleNotFound(ResourceNotFoundException e) {
        return Result.error(404, e.getMessage());
    }
    
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Result handleValidation(MethodArgumentNotValidException e) {
        String msg = e.getBindingResult().getFieldErrors().stream()
            .map(f -> f.getField() + ": " + f.getDefaultMessage())
            .collect(Collectors.joining(", "));
        return Result.error(400, msg);
    }
    
    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public Result handleUnknown(Exception e) {
        log.error("Unexpected error", e);
        return Result.error(500, "服务器内部错误");
    }
}

@ControllerAdvice 还能做什么?

  • @ExceptionHandler — 异常处理
  • @ModelAttribute — 全局数据绑定
  • @InitBinder — 全局参数绑定器(日期格式等)

2.2 拦截器 vs 过滤器

对比

维度FilterInterceptor
规范级别Servlet 规范Spring 框架
可访问对象ServletRequest/ServletResponse额外可访问 HandlerModelAndView
执行时机Servlet 容器中,早于 DispatcherServletSpring 容器中,Controller 调用前后
应用场景字符编码、跨域(CORS)、请求日志权限校验、API 速率限制、请求审计
粒度粗粒度(URL 模式匹配)细粒度(可匹配具体 Handler)
是否依赖 IoC是(可注入 Spring Bean)

拦截器使用示例

java
@Component
public class AuthInterceptor implements HandlerInterceptor {
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, 
                             Object handler) {
        // Controller 执行前
        String token = request.getHeader("Authorization");
        if (token == null || !token.startsWith("Bearer ")) {
            response.setStatus(401);
            return false;  // 中断请求
        }
        return true;  // 继续执行
    }
    
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, 
                           Object handler, ModelAndView modelAndView) {
        // Controller 执行后,视图渲染前
    }
    
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, 
                                Object handler, Exception ex) {
        // 请求完成后(视图渲染后),常用于资源清理
    }
}

// 注册拦截器
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(authInterceptor())
                .addPathPatterns("/api/**")
                .excludePathPatterns("/api/auth/**", "/api/public/**");
    }
}

2.3 跨域处理(CORS)

方式一:@CrossOrigin 注解

java
@RestController
@RequestMapping("/api/users")
@CrossOrigin(origins = "https://frontend.example.com")
public class UserController {
    
    @CrossOrigin(origins = "*")  // 方法级别覆盖
    @GetMapping("/public")
    public List<User> list() { ... }
}

方式二:全局配置

java
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")
                .allowedOrigins("https://app.example.com")
                .allowedMethods("GET", "POST", "PUT", "DELETE")
                .allowedHeaders("*")
                .allowCredentials(true)
                .maxAge(3600);
    }
}

方式三:CorsFilter(Spring Boot 推荐用于安全场景)

java
@Bean
public CorsFilter corsFilter() {
    CorsConfiguration config = new CorsConfiguration();
    config.setAllowCredentials(true);
    config.setAllowedOrigins(Arrays.asList("https://frontend.com"));
    config.setAllowedMethods(Arrays.asList("*"));
    config.setAllowedHeaders(Arrays.asList("*"));
    
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/api/**", config);
    return new CorsFilter(source);
}

三、面试视角

追问答案要点
Filter 和 Interceptor 的区别?规范级别、可访问对象、执行时机、粒度、是否依赖 IoC
@ControllerAdvice 的实现原理?AOP + 异常解析器机制捕获 Controller 层异常
CORS 的三种配置方式?@CrossOrigin 注解、WebMvcConfigurer 全局配置、CorsFilter Bean
拦截器返回 false 会怎样?中断请求链,不会执行 Controller、postHandle、视图渲染

📚 相关链接

  • **DispatcherServlet 流程** — 拦截器在请求流程中的位置
  • **参数绑定与返回值** — 异常处理中的参数校验异常
  • ← 返回 **返回 Spring MVC 索引**

Knowledge4J — Java 知识库