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);
    }

相关推荐
MuMuMu12231 小时前
工业园区 VOCs 绿岛集中治理:越华环保集团项目技术方案与工程实践
大数据·运维
品牌测评1 小时前
大模型推理算力平台推荐分享|六家平台计费与架构拆解
大数据·人工智能·架构
LedgerNinja1 小时前
WEEX 真实情况如何?交易平台如何判断是否可靠
大数据·人工智能·区块链
微三云 - 廖会灵 (私域系统开发)1 小时前
智慧社区运营破局:“消费返物业费” 模式的数字化架构与落地实践
大数据·架构
Databuff3 小时前
workbuddy能不能私有化部署
大数据·人工智能·开源软件
自由能燃气设备3 小时前
燃气容积式热水器厂家选购指南:2026年商用热水设备采购避坑手册
大数据·数据库·数据仓库·人工智能
ApacheSeaTunnel3 小时前
从 60万行/秒说起,看 SeaTunnel Zeta 基准测试如何做到”测得准“
大数据·数据集成·基准测试·seatunnel·数据同步
2601_963869954 小时前
【计算机毕业设计】基于Java的线上就医平台设计与实现
大数据·人工智能·课程设计
今天的砖头有点烫手啊5 小时前
接口太慢?Spring Boot 缓存体系 @Cacheable 全链路拆解
spring boot·后端·缓存
TDengine (老段)5 小时前
TDengine taosAdapter — 多协议网关详解
大数据·数据库·物联网·时序数据库·iot·tdengine·涛思数据