Spring Boot实现全局异常处理案例

wen java案例 2

本文目录导读:

Spring Boot实现全局异常处理案例

  1. 项目依赖配置
  2. 统一响应结果封装
  3. 自定义业务异常
  4. 全局异常处理器(核心类)
  5. 应用配置
  6. 测试示例
  7. 自定义异常处理器增强版
  8. 前端统一处理示例
  9. 使用说明

我来为您提供一个完整的Spring Boot全局异常处理案例,包含常见的异常处理场景。

项目依赖配置

<!-- pom.xml -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>

统一响应结果封装

// Result.java
import lombok.Data;
import java.io.Serializable;
@Data
public class Result<T> implements Serializable {
    private static final long serialVersionUID = 1L;
    private Integer code;
    private String message;
    private T data;
    private long timestamp = System.currentTimeMillis();
    public Result() {}
    public Result(Integer code, String message, T data) {
        this.code = code;
        this.message = message;
        this.data = data;
    }
    public static <T> Result<T> success(T data) {
        return new Result<>(200, "操作成功", data);
    }
    public static <T> Result<T> success(String message, T data) {
        return new Result<>(200, message, data);
    }
    public static <T> Result<T> error(Integer code, String message) {
        return new Result<>(code, message, null);
    }
    public static <T> Result<T> error(Integer code, String message, T data) {
        return new Result<>(code, message, data);
    }
}
// ResultCode.java 状态码枚举
public enum ResultCode {
    SUCCESS(200, "操作成功"),
    BAD_REQUEST(400, "请求参数错误"),
    UNAUTHORIZED(401, "未认证"),
    FORBIDDEN(403, "无权限"),
    NOT_FOUND(404, "资源不存在"),
    INTERNAL_SERVER_ERROR(500, "系统内部错误"),
    SERVICE_UNAVAILABLE(503, "服务不可用");
    private final Integer code;
    private final String message;
    ResultCode(Integer code, String message) {
        this.code = code;
        this.message = message;
    }
    public Integer getCode() {
        return code;
    }
    public String getMessage() {
        return message;
    }
}

自定义业务异常

// BusinessException.java
import lombok.Getter;
@Getter
public class BusinessException extends RuntimeException {
    private final Integer code;
    private final String message;
    public BusinessException(String message) {
        super(message);
        this.code = ResultCode.INTERNAL_SERVER_ERROR.getCode();
        this.message = message;
    }
    public BusinessException(Integer code, String message) {
        super(message);
        this.code = code;
        this.message = message;
    }
    public BusinessException(ResultCode resultCode) {
        super(resultCode.getMessage());
        this.code = resultCode.getCode();
        this.message = resultCode.getMessage();
    }
    public BusinessException(ResultCode resultCode, String message) {
        super(message);
        this.code = resultCode.getCode();
        this.message = message;
    }
}

全局异常处理器(核心类)

// GlobalExceptionHandler.java
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.servlet.NoHandlerFoundException;
import org.springframework.web.servlet.resource.NoResourceFoundException;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import java.util.stream.Collectors;
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
    /**
     * 处理业务异常
     */
    @ExceptionHandler(BusinessException.class)
    public ResponseEntity<Result<Object>> handleBusinessException(BusinessException e, WebRequest request) {
        log.error("业务异常: {}", e.getMessage());
        log.error("请求路径: {}", request.getDescription(false));
        return ResponseEntity
                .status(HttpStatus.OK)
                .body(Result.error(e.getCode(), e.getMessage()));
    }
    /**
     * 处理参数校验异常(@RequestBody)
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<Result<Object>> handleMethodArgumentNotValid(MethodArgumentNotValidException e) {
        String message = e.getBindingResult()
                .getFieldErrors()
                .stream()
                .map(FieldError::getDefaultMessage)
                .collect(Collectors.joining("; "));
        log.warn("参数校验失败: {}", message);
        return ResponseEntity
                .status(HttpStatus.BAD_REQUEST)
                .body(Result.error(ResultCode.BAD_REQUEST.getCode(), message));
    }
    /**
     * 处理参数绑定异常(表单提交)
     */
    @ExceptionHandler(BindException.class)
    public ResponseEntity<Result<Object>> handleBindException(BindException e) {
        String message = e.getBindingResult()
                .getFieldErrors()
                .stream()
                .map(FieldError::getDefaultMessage)
                .collect(Collectors.joining("; "));
        log.warn("参数绑定失败: {}", message);
        return ResponseEntity
                .status(HttpStatus.BAD_REQUEST)
                .body(Result.error(ResultCode.BAD_REQUEST.getCode(), message));
    }
    /**
     * 处理单个参数校验异常
     */
    @ExceptionHandler(ConstraintViolationException.class)
    public ResponseEntity<Result<Object>> handleConstraintViolation(ConstraintViolationException e) {
        String message = e.getConstraintViolations()
                .stream()
                .map(ConstraintViolation::getMessage)
                .collect(Collectors.joining("; "));
        log.warn("参数校验失败: {}", message);
        return ResponseEntity
                .status(HttpStatus.BAD_REQUEST)
                .body(Result.error(ResultCode.BAD_REQUEST.getCode(), message));
    }
    /**
     * 处理参数类型不匹配异常
     */
    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    public ResponseEntity<Result<Object>> handleTypeMismatch(MethodArgumentTypeMismatchException e) {
        String message = "参数类型不匹配";
        log.warn("参数类型不匹配: {}", e.getMessage());
        return ResponseEntity
                .status(HttpStatus.BAD_REQUEST)
                .body(Result.error(ResultCode.BAD_REQUEST.getCode(), message));
    }
    /**
     * 处理404异常
     */
    @ExceptionHandler({NoHandlerFoundException.class, NoResourceFoundException.class})
    public ResponseEntity<Result<Object>> handleNotFoundException(Exception e, WebRequest request) {
        log.warn("请求资源不存在: {}", request.getDescription(false));
        return ResponseEntity
                .status(HttpStatus.NOT_FOUND)
                .body(Result.error(ResultCode.NOT_FOUND.getCode(), "请求的资源不存在"));
    }
    /**
     * 处理运行时异常
     */
    @ExceptionHandler(RuntimeException.class)
    public ResponseEntity<Result<Object>> handleRuntimeException(RuntimeException e, WebRequest request) {
        log.error("系统运行时异常: {}", e.getMessage(), e);
        return ResponseEntity
                .status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(Result.error(ResultCode.INTERNAL_SERVER_ERROR.getCode(), "系统内部错误,请稍后重试"));
    }
    /**
     * 处理所有未捕获的异常
     */
    @ExceptionHandler(Exception.class)
    public ResponseEntity<Result<Object>> handleException(Exception e, WebRequest request) {
        log.error("系统异常: {}", e.getMessage(), e);
        return ResponseEntity
                .status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(Result.error(ResultCode.INTERNAL_SERVER_ERROR.getCode(), "系统内部错误,请联系管理员"));
    }
}

应用配置

# application.yml
spring:
  mvc:
    throw-exception-if-no-handler-found: true
  web:
    resources:
      add-mappings: false
server:
  error:
    include-message: always

测试示例

// TestController.java
import jakarta.validation.Valid;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@RestController
@RequestMapping("/api/test")
@Validated
public class TestController {
    @PostMapping("/error")
    public Result<Object> testBusinessException() {
        throw new BusinessException(ResultCode.BAD_REQUEST, "业务处理失败");
    }
    @PostMapping("/valid")
    public Result<UserDTO> testValid(@Valid @RequestBody UserDTO userDTO) {
        return Result.success(userDTO);
    }
    @GetMapping("/param")
    public Result<Object> testParamValidation(
            @NotBlank(message = "用户名不能为空") @RequestParam String username,
            @Email(message = "邮箱格式不正确") @RequestParam String email) {
        return Result.success(Map.of("username", username, "email", email));
    }
    @GetMapping("/type")
    public Result<Object> testTypeMismatch(@RequestParam Integer age) {
        return Result.success(Map.of("age", age));
    }
    @GetMapping("/npe")
    public Result<Object> testNPE() {
        String str = null;
        str.length(); // 触发空指针异常
        return Result.success(null);
    }
    // 测试DTO
    static class UserDTO {
        @NotBlank(message = "用户名不能为空")
        private String username;
        @NotBlank(message = "密码不能为空")
        private String password;
        @Email(message = "邮箱格式不正确")
        private String email;
        @NotEmpty(message = "兴趣爱好不能为空")
        private List<String> hobbies;
        // getters and setters...
    }
}

自定义异常处理器增强版

// EnhancedGlobalExceptionHandler.java
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
@RestControllerAdvice
public class EnhancedGlobalExceptionHandler {
    /**
     * 完整的错误响应体,包含错误详情
     */
    private Map<String, Object> buildErrorBody(Integer code, String message, HttpServletRequest request) {
        Map<String, Object> errorBody = new HashMap<>();
        errorBody.put("code", code);
        errorBody.put("message", message);
        errorBody.put("path", request.getRequestURI());
        errorBody.put("timestamp", LocalDateTime.now().toString());
        errorBody.put("method", request.getMethod());
        return errorBody;
    }
    @ExceptionHandler(BusinessException.class)
    public ResponseEntity<Map<String, Object>> handleBusiness(BusinessException e) {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        return ResponseEntity
                .status(HttpStatus.OK)
                .body(buildErrorBody(e.getCode(), e.getMessage(), request));
    }
}

前端统一处理示例

// 前端统一错误处理
async function request(url, options = {}) {
    const response = await fetch(url, {
        headers: { 'Content-Type': 'application/json' },
        ...options
    });
    const result = await response.json();
    if (result.code !== 200) {
        // 统一错误提示
        ElMessage.error(result.message);
        // 401跳转登录
        if (result.code === 401) {
            window.location.href = '/login';
        }
        throw new Error(result.message);
    }
    return result.data;
}

使用说明

  1. 统一响应格式:所有成功和失败都返回 Result 对象
  2. 业务异常:自定义 BusinessException 用于业务逻辑错误
  3. 参数校验:使用 @Validated@Valid 结合异常处理
  4. 日志记录:异常都记录了日志,方便排查
  5. 错误信息:对外部返回友好提示,内部记录详细信息

这个全局异常处理方案能够覆盖项目中的绝大部分异常场景,具有很好的扩展性和维护性。

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