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

相关推荐
hans汉斯2 小时前
嵌入式操作系统技术发展趋势
大数据·数据库·物联网·rust·云计算·嵌入式实时数据库·汉斯出版社
产品设计大观2 小时前
6个宠物APP原型设计案例拆解:含AI问诊、商城、领养、托运
大数据·人工智能·ai·宠物·墨刀·app原型·宠物app
+VX:Fegn08952 小时前
计算机毕业设计|基于springboot + vue宠物寄养系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计·宠物
一 乐3 小时前
校园实验室|基于springboot + vue校园实验室管理系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端
Lisonseekpan3 小时前
Spring Boot Email 邮件发送完全指南
java·spring boot·后端·log4j
sheji34163 小时前
【开题答辩全过程】以 基于Springboot的体检中心信息管理系统设计与实现为例,包含答辩的问题和答案
java·spring boot·后端
天河归来3 小时前
本地windows环境升级dify到1.11.1版本
java·spring boot·docker
甜鲸鱼3 小时前
【Spring AOP】操作日志的完整实现与原理剖析
java·spring boot·spring
liliangcsdn4 小时前
LLM MoE 形式化探索
大数据·人工智能