springboot全局异常

在Spring Boot中实现全局异常处理,以下是几种常用的方式:

1. @ControllerAdvice + @ExceptionHandler(推荐)

复制代码
@RestControllerAdvice
public class GlobalExceptionHandler {
    
    // 处理自定义业务异常
    @ExceptionHandler(BusinessException.class)
    public Result<?> handleBusinessException(BusinessException e) {
        log.error("业务异常: {}", e.getMessage());
        return Result.error(e.getCode(), e.getMessage());
    }
    
    // 处理数据校验异常
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result<?> handleValidException(MethodArgumentNotValidException e) {
        String message = e.getBindingResult()
                .getAllErrors()
                .stream()
                .map(DefaultMessageSourceResolvable::getDefaultMessage)
                .collect(Collectors.joining(", "));
        return Result.error(HttpStatus.BAD_REQUEST.value(), message);
    }
    
    // 处理参数绑定异常
    @ExceptionHandler(BindException.class)
    public Result<?> handleBindException(BindException e) {
        String message = e.getBindingResult()
                .getAllErrors()
                .stream()
                .map(DefaultMessageSourceResolvable::getDefaultMessage)
                .collect(Collectors.joining(", "));
        return Result.error(HttpStatus.BAD_REQUEST.value(), message);
    }
    
    // 处理所有未捕获的异常
    @ExceptionHandler(Exception.class)
    public Result<?> handleGlobalException(Exception e) {
        log.error("系统异常: ", e);
        return Result.error(HttpStatus.INTERNAL_SERVER_ERROR.value(), "系统繁忙,请稍后再试");
    }
}

2. 自定义异常类

复制代码
// 基础异常类
public class BaseException extends RuntimeException {
    private final Integer code;
    
    public BaseException(Integer code, String message) {
        super(message);
        this.code = code;
    }
    
    public BaseException(Integer code, String message, Throwable cause) {
        super(message, cause);
        this.code = code;
    }
    
    public Integer getCode() {
        return code;
    }
}

// 业务异常
public class BusinessException extends BaseException {
    public BusinessException(String message) {
        super(HttpStatus.BAD_REQUEST.value(), message);
    }
    
    public BusinessException(Integer code, String message) {
        super(code, message);
    }
}

3. 使用 ErrorController 处理 404 等异常

复制代码
@RestController
public class CustomErrorController implements ErrorController {
    
    @RequestMapping("/error")
    public Result<?> handleError(HttpServletRequest request) {
        Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
        String errorMessage = (String) request.getAttribute("javax.servlet.error.message");
        
        if (statusCode == HttpStatus.NOT_FOUND.value()) {
            return Result.error(statusCode, "请求的资源不存在");
        }
        
        return Result.error(statusCode, errorMessage != null ? errorMessage : "未知错误");
    }
}

4. 统一响应格式

复制代码
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Result<T> implements Serializable {
    private Integer code;
    private String message;
    private T data;
    private Long timestamp;
    
    public static <T> Result<T> success() {
        return success(null);
    }
    
    public static <T> Result<T> success(T data) {
        return Result.<T>builder()
                .code(HttpStatus.OK.value())
                .message("success")
                .data(data)
                .timestamp(System.currentTimeMillis())
                .build();
    }
    
    public static <T> Result<T> error(Integer code, String message) {
        return Result.<T>builder()
                .code(code)
                .message(message)
                .timestamp(System.currentTimeMillis())
                .build();
    }
}

5. 配置异常处理(配置文件)

复制代码
# application.yml
server:
  error:
    include-exception: false    # 不显示异常详情给前端
    include-stacktrace: never   # 不显示堆栈信息
    include-message: always
    include-binding-errors: always
    
spring:
  mvc:
    throw-exception-if-no-handler-found: true
  resources:
    add-mappings: false

6. 完整的全局异常处理器示例

复制代码
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
    
    /**
     * 处理自定义业务异常
     */
    @ExceptionHandler(BusinessException.class)
    public Result<?> businessExceptionHandler(BusinessException e) {
        log.error("业务异常: {}", e.getMessage(), e);
        return Result.error(e.getCode(), e.getMessage());
    }
    
    /**
     * 处理参数校验异常
     */
    @ExceptionHandler(value = {MethodArgumentNotValidException.class, 
                               BindException.class})
    public Result<?> validExceptionHandler(Exception e) {
        String message = null;
        if (e instanceof MethodArgumentNotValidException validException) {
            message = validException.getBindingResult().getFieldErrors().stream()
                    .map(FieldError::getDefaultMessage)
                    .collect(Collectors.joining(", "));
        } else if (e instanceof BindException bindException) {
            message = bindException.getBindingResult().getFieldErrors().stream()
                    .map(FieldError::getDefaultMessage)
                    .collect(Collectors.joining(", "));
        }
        
        log.error("参数校验异常: {}", message);
        return Result.error(HttpStatus.BAD_REQUEST.value(), message);
    }
    
    /**
     * 处理 HTTP 方法不支持异常
     */
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public Result<?> methodNotSupportedExceptionHandler(
            HttpRequestMethodNotSupportedException e) {
        log.error("HTTP方法不支持: {}", e.getMessage());
        return Result.error(HttpStatus.METHOD_NOT_ALLOWED.value(), 
                "不支持" + e.getMethod() + "请求方法");
    }
    
    /**
     * 处理媒体类型不支持异常
     */
    @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
    public Result<?> mediaTypeNotSupportedExceptionHandler(
            HttpMediaTypeNotSupportedException e) {
        log.error("媒体类型不支持: {}", e.getMessage());
        return Result.error(HttpStatus.UNSUPPORTED_MEDIA_TYPE.value(), 
                "不支持的媒体类型");
    }
    
    /**
     * 处理文件上传大小限制异常
     */
    @ExceptionHandler(MaxUploadSizeExceededException.class)
    public Result<?> maxUploadSizeExceededExceptionHandler(
            MaxUploadSizeExceededException e) {
        log.error("文件大小超出限制: {}", e.getMessage());
        return Result.error(HttpStatus.PAYLOAD_TOO_LARGE.value(), 
                "文件大小超出限制");
    }
    
    /**
     * 处理重复键异常
     */
    @ExceptionHandler(DuplicateKeyException.class)
    public Result<?> duplicateKeyExceptionHandler(DuplicateKeyException e) {
        log.error("数据重复: {}", e.getMessage());
        return Result.error(HttpStatus.BAD_REQUEST.value(), 
                "数据已存在,请勿重复添加");
    }
    
    /**
     * 处理乐观锁异常
     */
    @ExceptionHandler(OptimisticLockingFailureException.class)
    public Result<?> optimisticLockingFailureExceptionHandler(
            OptimisticLockingFailureException e) {
        log.error("乐观锁异常: {}", e.getMessage());
        return Result.error(HttpStatus.CONFLICT.value(), 
                "数据已被修改,请刷新后重试");
    }
    
    /**
     * 处理所有未知异常
     */
    @ExceptionHandler(Exception.class)
    public Result<?> exceptionHandler(Exception e) {
        log.error("系统异常: ", e);
        return Result.error(HttpStatus.INTERNAL_SERVER_ERROR.value(), 
                "系统繁忙,请稍后再试");
    }
}

7. 使用示例

复制代码
@RestController
@RequestMapping("/api/user")
public class UserController {
    
    @GetMapping("/{id}")
    public Result<User> getUser(@PathVariable Long id) {
        User user = userService.getById(id);
        if (user == null) {
            throw new BusinessException("用户不存在");
        }
        return Result.success(user);
    }
    
    @PostMapping
    public Result<String> createUser(@Valid @RequestBody UserDTO userDTO) {
        userService.create(userDTO);
        return Result.success("创建成功");
    }
}

8. 高级配置:自定义异常处理顺序

复制代码
@Order(Ordered.HIGHEST_PRECEDENCE)  // 设置处理顺序
@RestControllerAdvice(assignableTypes = {UserController.class})
public class UserExceptionHandler {
    // 处理UserController特定的异常
}

最佳实践建议:

  1. 分层处理

    • 在Controller层处理参数校验异常

    • 在Service层处理业务逻辑异常

    • 在全局处理器处理系统级异常

  2. 异常分类

    • 业务异常:返回400-499状态码

    • 系统异常:返回500状态码

    • 认证授权异常:返回401/403状态码

  3. 日志记录

    • 业务异常记录WARN级别

    • 系统异常记录ERROR级别

    • 生产环境隐藏敏感信息

  4. 国际化支持

    @ExceptionHandler(BusinessException.class)
    public Result<?> handleBusinessException(BusinessException e,
    Locale locale) {
    String message = messageSource.getMessage(e.getMessage(),
    null, e.getMessage(), locale);
    return Result.error(e.getCode(), message);
    }

相关推荐
行百里er1 天前
WebSocket 在 Spring Boot 中的实战解析:实时通信的技术利器
spring boot·后端·websocket
皮皮林5511 天前
SpringBoot 集成 Hera,让日志查看从 “找罪证” 变 “查答案”!
spring boot
yangminlei1 天前
Spring 事务探秘:核心机制与应用场景解析
java·spring boot
商业讯网11 天前
国家电投海外项目运营经验丰富
大数据·人工智能·区块链
面向Google编程1 天前
Flink源码阅读:Mailbox线程模型
大数据·flink
Elastic 中国社区官方博客1 天前
使用 Elastic 中的 OpenTelemetry 为 Nginx 实现端到端分布式追踪的实用指南
大数据·运维·分布式·elasticsearch·搜索引擎·信息可视化·全文检索
+VX:Fegn08951 天前
计算机毕业设计|基于springboot + vue小型房屋租赁系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
aliprice1 天前
逆向拆解:用速卖通图片搜索破解竞品设计,找到你的差异化定价空间
大数据·跨境电商·电商
hg01181 天前
埃及:在变局中重塑发展韧性
大数据·人工智能·物联网
向量引擎小橙1 天前
“2026数据枯竭”警报拉响:合成数据如何成为驱动AI进化的“新石油”?
大数据·人工智能·深度学习·集成学习