本文目录导读:

- 📖 目录导读
- 为什么Spring MVC异常处理如此重要?
- 基础篇:@ExceptionHandler注解的三种玩法
- 进阶篇:@ControllerAdvice全局异常拦截器实战
- 高级篇:HandlerExceptionResolver体系深度解析
- 分布式场景:异常信息如何统一响应给前端
- 常见面试题问答(附标准答案)
- 异常处理的最佳实践清单
Spring MVC异常处理从入门到实战:5个案例彻底根治系统报错
📖 目录导读
- 为什么Spring MVC异常处理如此重要?
- 基础篇:@ExceptionHandler注解的三种玩法
- 进阶篇:@ControllerAdvice全局异常拦截器实战
- 高级篇:HandlerExceptionResolver体系深度解析
- 分布式场景:异常信息如何统一响应给前端
- 常见面试题问答(附标准答案)
为什么Spring MVC异常处理如此重要?
在真实的Web项目中,未捕获的异常会直接暴露堆栈信息给用户,这不仅造成极差的体验,更可能泄露数据库结构、业务逻辑等敏感信息。Spring MVC异常处理体系的核心价值在于:
- 统一响应格式:无论是Controller层还是Service层抛出的异常,都能转换成JSON/XML标准结构
- 业务逻辑解耦:DAO/Service层异常转换为业务异常码,而非原始堆栈
- 精准日志追踪:通过AOP切面记录异常上下文(请求参数、用户会话等)
根据StackOverflow 2023年开发者调查,82%的Java后端项目存在异常处理代码冗余问题,下面通过5个案例逐步攻破。
基础篇:@ExceptionHandler注解的三种玩法
案例1:局部异常处理器(Controller内)
@RestController
@RequestMapping("/user")
public class UserController {
@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {
if (id <= 0) {
throw new IllegalArgumentException("用户ID非法");
}
return userService.getById(id);
}
// 局部异常处理,仅对当前Controller生效
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<ErrorResult> handleIllegalArg(IllegalArgumentException e) {
return ResponseEntity.badRequest()
.body(new ErrorResult(400, e.getMessage()));
}
}
关键坑点:局部异常方法不能声明返回值类型为通用ModelAndView,否则无法自动转换JSON。
案例2:@ControllerAdvice全局异常拦截
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(value = {BizException.class, DataAccessException.class})
@ResponseBody
public ErrorResult handleBizException(Exception e) {
// 日志记录:log.error("业务异常!", e);
return ErrorResult.of(500, "系统繁忙,请稍后重试");
}
@ExceptionHandler(Exception.class)
public ErrorResult handleUnknownException(Exception e) {
// 兜底处理,避免裸堆栈泄露
return ErrorResult.of(500, "未知异常");
}
}
注意点:当同一异常被多个handler匹配时,优先级最高的是最具体的异常类型。
案例3:配合@ResponseStatus自定义HTTP状态码
@ResponseStatus(HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException {
public ResourceNotFoundException(String message) {
super(message);
}
}
进阶篇:@ControllerAdvice全局异常拦截器实战
完整实战案例(含异步异常处理):
@Slf4j
@ControllerAdvice(basePackages = "com.example.web")
public class ApiExceptionHandler {
// 统一异常入参校验
@ExceptionHandler(MethodArgumentNotValidException.class)
public ErrorResult handleValidException(MethodArgumentNotValidException ex) {
String msg = ex.getBindingResult().getFieldErrors().stream()
.map(FieldError::getDefaultMessage)
.collect(Collectors.joining(";"));
log.warn("参数校验失败:{}", msg);
return ErrorResult.of(400, msg);
}
// 异步请求异常处理
@ExceptionHandler(AsyncRequestTimeoutException.class)
public ErrorResult handleTimeoutException() {
return ErrorResult.of(408, "请求处理超时");
}
// 兜底异常 + 邮件告警
@ExceptionHandler(Throwable.class)
public ErrorResult handleThrowable(Throwable t, HttpServletRequest request) {
log.error("系统异常-> URL:[{}]", request.getRequestURI(), t);
// 发送告警邮件...
return ErrorResult.of(500, "系统内部错误");
}
}
高级篇:HandlerExceptionResolver体系深度解析
Spring MVC异常处理的底层有4种内置解析器,按优先级排列:
- ExceptionHandlerExceptionResolver:处理
@ExceptionHandler注解 - ResponseStatusExceptionResolver:处理
@ResponseStatus - DefaultHandlerExceptionResolver:处理框架内置异常(如类型不匹配)
- SimpleMappingExceptionResolver:基于配置文件的异常映射
自定义实现案例:
@Component
public class CustomHandlerExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request,
HttpServletResponse response,
Object handler, Exception ex) {
// 业务逻辑:记录数据库错误码
if (ex instanceof BizException) {
response.setStatus(500);
try {
response.getWriter().write("自定义JSON格式");
} catch (IOException e) {
e.printStackTrace();
}
return new ModelAndView();
}
return null; // 交给下一个resolver处理
}
}
注意:如果自定义resolver返回null,异常会继续传播到下一个resolver,最终才会抛出到Servlet容器。
分布式场景:异常信息如何统一响应给前端
在微服务架构中,建议使用异常码+错误细节的标准结构:
{
"code": 1001,
"message": "用户不存在",
"details": {
"field": "userId",
"reason": "id非法",
"timestamp": "2024-01-15T10:00:00"
}
}
核心策略:
- 利用ThreadLocal保存请求ID,异常时通过MDC日志追踪
- 通过Feign/Hystrix降级时封装异常为
RemoteException - 使用Spring Cloud Gateway的
GlobalFilter统一处理微服务异常透传
常见面试题问答(附标准答案)
Q1:@ExceptionHandler和@ControllerAdvice有什么区别?
A:
@ControllerAdvice是一个全局拦截器切面,它集成了多个@ExceptionHandler,并且可以配合@InitBinder、@ModelAttribute使用,而@ExceptionHandler可以单独在Controller内使用,作用域局限于当前Controller,当两者同时存在时,优先匹配更精确的异常类型,其次匹配全局处理器。
Q2:如何区分业务异常和系统异常?
A:最佳实践是自定义
BizException继承RuntimeException,并通过构造器传入业务错误码和消息,在全局异常处理器中,通过instanceof判断异常类型,业务异常返回前端友好提示,系统异常直接返回“系统繁忙”,并记录完整堆栈日志。
Q3:如何在异常处理中拿到完整的请求上下文?
A:在
@ExceptionHandler方法中直接注入HttpServletRequest参数,Spring会自动传递,更优雅的方式是使用RequestContextHolder.currentRequestAttributes()静态方法获取当前请求的Attribute,常用于获取用户身份信息。
Q4:Spring Boot中默认的异常处理机制是什么?
A:Spring Boot提供了
/error端点,由BasicErrorController处理,当未配置@ControllerAdvice时,默认返回包含timestamp、status、error、message等字段的JSON或错误页面,可通过server.error.whitelabel.enabled=false关闭默认页。
Q5:如何统一处理拦截器中抛出的异常?
A:拦截器(HandlerInterceptor)的
preHandle方法如果抛出异常,会直接进入HandlerExceptionResolver阶段,不会被@ControllerAdvice捕获,解决方案:在preHandle内部使用try-catch包装,或将异常类型转换为自定义的AuthException,再通过继承HandlerInterceptorAdapter重写afterCompletion来统一记录。
异常处理的最佳实践清单
- ✅ 全局使用
@RestControllerAdvice统一拦截 - ✅ 业务异常定义携带错误码的
BizException - ✅ 所有异常必须记录日志(包含请求参数)
- ✅ 对外隐藏堆栈,返回标准错误结构
- ✅ 异步线程异常需手动捕获或使用
AsyncUncaughtExceptionHandler
通过上述5个案例,无论是单体应用还是微服务架构,都能构建最健壮、易维护的异常处理体系,掌握这套方案,你在面试中遇到“Spring MVC异常处理”相关问题时,已远超90%的竞争者。