Spring MVC异常处理案例

wen java案例 1

本文目录导读:

Spring MVC异常处理案例

  1. 📖 目录导读
  2. 为什么Spring MVC异常处理如此重要?
  3. 基础篇:@ExceptionHandler注解的三种玩法
  4. 进阶篇:@ControllerAdvice全局异常拦截器实战
  5. 高级篇:HandlerExceptionResolver体系深度解析
  6. 分布式场景:异常信息如何统一响应给前端
  7. 常见面试题问答(附标准答案)
  8. 异常处理的最佳实践清单

Spring MVC异常处理从入门到实战:5个案例彻底根治系统报错

📖 目录导读

  1. 为什么Spring MVC异常处理如此重要?
  2. 基础篇:@ExceptionHandler注解的三种玩法
  3. 进阶篇:@ControllerAdvice全局异常拦截器实战
  4. 高级篇:HandlerExceptionResolver体系深度解析
  5. 分布式场景:异常信息如何统一响应给前端
  6. 常见面试题问答(附标准答案)

为什么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种内置解析器,按优先级排列:

  1. ExceptionHandlerExceptionResolver:处理@ExceptionHandler注解
  2. ResponseStatusExceptionResolver:处理@ResponseStatus
  3. DefaultHandlerExceptionResolver:处理框架内置异常(如类型不匹配)
  4. 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 GatewayGlobalFilter统一处理微服务异常透传

常见面试题问答(附标准答案)

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时,默认返回包含timestampstatuserrormessage等字段的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%的竞争者。

抱歉,评论功能暂时关闭!