Spring Boot 全局异常处理

Spring Boot 全局异常处理

ErrorCode.java (此枚举类中包含了异常的唯一标识、HTTP 状态码以及错误信息)

这个类的主要作用就是统一管理系统中可能出现的异常,比较清晰明了。但是,可能出现的问题是当系统过于复杂,出现的异常过多之后,这个类会比较庞大。有一种解决办法:将多种相似的异常统一为一个,比如将用户找不到异常和订单信息未找到的异常都统一为"未找到该资源"这一种异常,然后前端再对相应的情况做详细处理。

java 复制代码
import org.springframework.http.HttpStatus;


public enum ErrorCode {
	/**
	 * 注意写错误码的几点:
	 * 1.是 public enum 不是 public class
	 * 2.只需要写get方法和全构造
	 * 3.错误参数构造之间用逗号隔开
	 */
    RESOURCE_NOT_FOUND(1001, HttpStatus.NOT_FOUND, "未找到该资源"),
    REQUEST_VALIDATION_FAILED(1002, HttpStatus.BAD_REQUEST, "请求数据格式验证失败");
    
    private final int code;

    private final HttpStatus status;

    private final String message;

    ErrorCode(int code, HttpStatus status, String message) {
        this.code = code;
        this.status = status;
        this.message = message;
    }

    public int getCode() {
        return code;
    }

    public HttpStatus getStatus() {
        return status;
    }

    public String getMessage() {
        return message;
    }

    @Override
    public String toString() {
        return"ErrorCode{" +
                "code=" + code +
                ", status=" + status +
                ", message='" + message + '\'' +
                '}';
    }
}

ErrorResponse.java(返回给客户端具体的异常对象)

这个类作为异常信息返回给客户端,里面包括了当出现异常时我们想要返回给客户端的所有信息。

java 复制代码
import org.springframework.util.ObjectUtils;

import java.time.Instant;
import java.util.HashMap;
import java.util.Map;

public class ErrorResponse {
    // 唯一标示异常的 code
    private int code;
    // HTTP状态码
    private int status;
    // 错误的具体信息
    private String message;
    // 错误路径
    private String path;
    // 发生错误的时间戳
    private Instant timestamp;
    private HashMap<String, Object> data = new HashMap<String, Object>();

    public ErrorResponse() {
    }

    public ErrorResponse(BaseException ex, String path) {
        this(ex.getError().getCode(), ex.getError().getStatus().value(), ex.getError().getMessage(), path, ex.getData());
    }

    public ErrorResponse(int code, int status, String message, String path, Map<String, Object> data) {
        this.code = code;
        this.status = status;
        this.message = message;
        this.path = path;
        this.timestamp = Instant.now();
        if (!ObjectUtils.isEmpty(data)) {
            this.data.putAll(data);
        }
    }

	// 省略 getter/setter 方法

    @Override
    public String toString() {
        return"ErrorReponse{" +
                "code=" + code +
                ", status=" + status +
                ", message='" + message + '\'' +
                ", path='" + path + '\'' +
                ", timestamp=" + timestamp +
                ", data=" + data +
                '}';
    }
}

BaseException.java(继承自 RuntimeException 的抽象类,可以看做系统中其他异常类的父类)

系统中的异常类都要继承自这个类

java 复制代码
public abstract class BaseException extends RuntimeException {
    private final ErrorCode error;
    private final HashMap<String, Object> data = new HashMap<>();

    public BaseException(ErrorCode error, Map<String, Object> data) {
        super(error.getMessage());
        this.error = error;
        if (!ObjectUtils.isEmpty(data)) {
            this.data.putAll(data);
        }
    }

    protected BaseException(ErrorCode error, Map<String, Object> data, Throwable cause) {
        super(error.getMessage(), cause);
        this.error = error;
        if (!ObjectUtils.isEmpty(data)) {
            this.data.putAll(data);
        }
    }

    public ErrorCode getError() {
        return error;
    }

    public Map<String, Object> getData() {
        return data;
    }

}

ResourceNotFoundException.java (自定义异常)

可以看出通过继承 BaseException 类自定义异常会变的非常简单!

java 复制代码
import java.util.Map;

public class ResourceNotFoundException extends BaseException {

    public ResourceNotFoundException(Map<String, Object> data) {
        super(ErrorCode.RESOURCE_NOT_FOUND, data);
    }
}

GlobalExceptionHandler.java(全局异常捕获)

定义了两个异常捕获方法。

这里再说明一下,实际上这个类只需要 handleAppException() 这一个方法就够了,因为它是本系统所有异常的父类。只要是抛出了继承 BaseException 类的异常后都会在这里被处理。

java 复制代码
import com.twuc.webApp.web.ExceptionController;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;

@ControllerAdvice(assignableTypes = {ExceptionController.class})
@ResponseBody
public class GlobalExceptionHandler {

    // 也可以将 BaseException 换为 RuntimeException
    // 因为 RuntimeException 是 BaseException 的父类
    @ExceptionHandler(BaseException.class)
    public ResponseEntity<?> handleAppException(BaseException ex, HttpServletRequest request) {
        ErrorReponse representation = new ErrorReponse(ex, request.getRequestURI());
        return new ResponseEntity<>(representation, new HttpHeaders(), ex.getError().getStatus());
    }

    @ExceptionHandler(value = ResourceNotFoundException.class)
    public ResponseEntity<ErrorReponse> handleResourceNotFoundException(ResourceNotFoundException ex, HttpServletRequest request) {
        ErrorReponse errorReponse = new ErrorReponse(ex, request.getRequestURI());
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorReponse);
    }
}

当抛出了ResourceNotFoundException异常,会被handleResourceNotFoundException()方法捕获。因为 @ExceptionHandler 捕获异常的过程中,会优先找到最匹配的。

补充内容:

  1. @ControllerAdvice可以指定捕获异常的控制器范围,比如这里的assignableTypes=ExceptionController.class,表示只处理ExceptionController抛出的异常。不指定的话默认对所有controller有效。

  2. 可以在全局异常处理方法中获取更多上下文信息,如请求参数,用户信息等,方便异常处理和日志记录。

  3. 可以定义不同的全局异常处理类拦截不同范围的异常,比如定义一个全局未捕获异常处理器,一个控制器异常处理器等。

    java 复制代码
    /**
     * 定义一个全局未捕获异常处理器类
     * 该异常处理器的优先级低于其他具体的异常处理方法
     * 可以配合其他具体异常处理方法,处理未覆盖到的异常情况
     * 可以保证系统兜底捕获所有异常,避免未处理的异常直接抛出到用户
     */
    @ControllerAdvice
    public class GlobalExceptionHandler {
    
        // 通过Exception.class参数捕获一切Exception类及其子类的异常
        @ExceptionHandler(value = Exception.class)  
        public ResponseEntity<String> handleException(Exception e){
            // 处理未捕获的异常
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.getMessage()); 
        }
    
    }
    java 复制代码
    /**
     * 在控制器类中定义异常处理方法,加上@ExceptionHandler注解
     * 与全局异常处理器配合使用,控制器内的异常处理器优先级更高
     * 这样可以在控制器层面处理控制器范围内的特定异常
     * 所以Controller中的异常处理器可以更加具体地处理控制器业务场景下的异常情况
     */
    @RestController
    public class UserController {
    
        // 该方法可以捕获在本控制器类中的UserNotFoundException异常
        @ExceptionHandler(UserNotFoundException.class)
        public ResponseEntity<String> handleUserNotFound(UserNotFoundException e) {
            // ...
            return ResponseEntity.status(HttpStatus.NOT_FOUND).body("User not found");
        }
    
    }
  4. 如上例可以使用@ExceptionHandler在控制器内处理控制器范围内的异常,与全局异常处理器配合使用。

  5. 正确设置HTTP状态码很重要,比如404找不到资源等,有助于客户端判断异常情况。

  6. 可以在异常处理方法中获取bindingResult,分析参数校验的错误信息。

  7. 返回给客户端的错误信息体中可以包含代码、信息等字段,以便客户端准确判断和处理。

相关推荐
wrx繁星点点4 分钟前
事务的四大特性(ACID)
java·开发语言·数据库
IT学长编程12 分钟前
计算机毕业设计 Java酷听音乐系统的设计与实现 Java实战项目 附源码+文档+视频讲解
java·spring boot·毕业设计·课程设计·毕业论文·音乐系统·计算机毕业设计选题
IT学长编程29 分钟前
计算机毕业设计 基于协同过滤算法的个性化音乐推荐系统的设计与实现 Java实战项目 附源码+文档+视频讲解
java·spring boot·毕业设计·毕业论文·协同过滤算法·计算机毕业设计选题·个性化音乐推荐系统
小小娥子34 分钟前
Redis的基础认识与在ubuntu上的安装教程
java·数据库·redis·缓存
几何心凉41 分钟前
已解决:org.springframework.web.HttpMediaTypeNotAcceptableException
java
华农第一蒟蒻44 分钟前
Java中JWT(JSON Web Token)的运用
java·前端·spring boot·json·token
两点王爷1 小时前
使用WebClient 快速发起请求(不使用WebClientUtils工具类)
java·网络
计算机学姐1 小时前
基于SpringBoot+Vue的高校运动会管理系统
java·vue.js·spring boot·后端·mysql·intellij-idea·mybatis
平凡的小码农1 小时前
JAVA实现大写金额转小写金额
java·开发语言
一直在进步的派大星1 小时前
Docker 从安装到实战
java·运维·docker·微服务·容器