04.3 — 拦截器与异常处理
定位: Spring MVC 的横切关注点——异常统一处理、请求拦截、跨域配置 面试高频度: ⭐⭐⭐⭐ 考查方式: Filter vs Interceptor 对比、@ControllerAdvice 异常处理、CORS 三种配置方式
一、这是什么?为什么需要它?
Spring MVC 需要处理三类横切关注点:异常处理(将 Controller 抛出的异常转换为友好响应)、请求拦截(在 Controller 执行前后执行通用逻辑)、跨域配置(处理浏览器的同源策略限制)。Spring 通过 @ControllerAdvice、HandlerInterceptor 和 @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 过滤器
对比
| 维度 | Filter | Interceptor |
|---|---|---|
| 规范级别 | Servlet 规范 | Spring 框架 |
| 可访问对象 | ServletRequest/ServletResponse | 额外可访问 Handler、ModelAndView |
| 执行时机 | Servlet 容器中,早于 DispatcherServlet | Spring 容器中,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 索引**