【SpringBoot3】统一数据响应,全局异常处理以及通用响应处理

一、设计一个优秀的异常处理机制

spring Boot中设计一个优秀的异常处理机制,可以确保应用程序在遇到错误时提供清晰、一致的响应,同时提高系统的健壮性和可维护性。

以下是一个关于如何设计Spring Boot异常处理机制的步骤和建议:

1)定义自定义异常类

  • 创建自定义异常类来代表应用程序中可能发生的特定错误情况。
  • 自定义异常类应该扩展标准的RuntimeExceptionException类,并提供一个明确的消息来描述错误。

2)创建全局异常处理器

  • 使用@ControllerAdvice注解创建一个全局异常处理器类。
  • 在该类中,使用@ExceptionHandler注解来指定处理特定异常的方法。

3)定义统一的异常响应格式

  • 定义一个统一的异常响应格式,比如一个包含状态码、错误消息和可能还有其他相关信息的JSON对象。
  • 在全局异常处理器中,确保所有捕获的异常都被转换成这种统一的格式。

4)异常分层

  • 根据错误的严重程度和影响范围,对异常进行分层。
  • 可以定义不同的异常类来表示不同的错误级别,例如:业务逻辑错误、系统错误、安全错误等。

5)日志记录

  • 在异常处理过程中,确保记录适当的日志信息。
  • 使用Spring Boot的日志框架(如Logback或Log4j)来记录异常的堆栈跟踪和其他重要信息。

6)测试

  • 编写单元测试来验证异常处理机制的行为。
  • 确保在出现预期异常时,处理程序能够正确捕获并记录异常,同时返回正确的响应格式。

7)提供友好的用户错误信息

  • 不要在前端界面上直接显示原始异常消息或堆栈跟踪。
  • 提供给用户友好、易于理解的错误信息和建议。

8)处理全局异常

  • 使用@ExceptionHandler(Exception.class)来处理所有未被其他处理器捕获的异常。
  • 这可以作为一个“兜底”处理器,确保所有异常都被处理。

9)考虑国际化

  • 如果你的应用程序需要支持多种语言,确保异常消息是可以国际化的。
  • 使用Spring的消息源(MessageSource)来支持多语言错误消息。

10)优雅地处理资源不足

  • 当应用程序遇到资源不足(如数据库连接池耗尽)等问题时,应确保异常得到妥善处理,避免应用崩溃。
  • 考虑实现资源耗尽时的回退策略,如优雅地降级服务。

11)使用AspectJ进行切面编程

  • 可以使用AspectJ的切面编程来在方法执行前后进行异常处理,从而避免在每个控制器中重复编写异常处理逻辑。

通过遵循这些步骤,你可以设计一个强大而灵活的异常处理机制,提高Spring Boot应用程序的健壮性和用户体验。

二、统一异常处理实现步骤

1、统一数据响应

我们必须为所有的接口定义统一的数据响应格式,创建统一数据响应类

java">@Data
public class AjaxResponse<T> {

    private String message;
    private Integer code;
    private T data;

    public static AjaxResponse<?> error(CustomException e) {
        AjaxResponse<?> response = new AjaxResponse<>();
        response.setCode(e.getCode());
        response.setMessage(e.getMsg());
        return response;
    }

    public static AjaxResponse<?> su***ess() {
        AjaxResponse<?> response = new AjaxResponse<>();
        response.setCode(ErrorCode.SU***ESS.getCode());
        response.setMessage(ErrorCode.SU***ESS.getMsg());
        return response;
    }
    public static <T> AjaxResponse<T> su***ess(T data) {
        AjaxResponse<T> response = new AjaxResponse<>();
        response.setCode(ErrorCode.SU***ESS.getCode());
        response.setMessage(ErrorCode.SU***ESS.getMsg());
        response.setData(data);
        return response;
    }

}

2、定义统一的异常状态码

建议直接使用Http状态码

@Getter
public enum ErrorCode {

    SU***ESS(200, "成功"),
    BAD_REQUEST(400, "请求参数不正确"),
    SERVER_ERROR(500, "系统异常"),
    UNKNOWN(999, "未知错误");

    private Integer code;
    private String msg;

    ErrorCode(Integer code, String message) {
        this.code = code;
        this.msg = message;
    }

}

3、创建自定义异常类

创建自定义异常CustomException,使用统一的异常枚举类ErrorCode作为参数

@Getter
public class CustomException extends RuntimeException {

    private Integer code;
    private String msg;

    public CustomException() {
        super();
    }

    public CustomException(ErrorCode errorCode) {
        this.code = errorCode.getCode();
        this.msg = errorCode.getMsg();
    }

    public CustomException(ErrorCode errorCode, String msg) {
        this.code = errorCode.getCode();
        this.msg = msg;
    }

}

4、创建全局异常处理类

创建全局异常处理类 WebExceptionHandler ,并使用注解 @ControllerAdvice 声明。

拦截异常,并封装成统一的数据返回格式AjaxResponse

@ControllerAdvice
public class WebExceptionHandler {

    @ExceptionHandler(CustomException.class)
    @ResponseBody
    public AjaxResponse<?> customerException(CustomException e) {
        return AjaxResponse.error(e);
    }

    @ExceptionHandler(Exception.class)
    @ResponseBody
    public AjaxResponse<?> exception(Exception e) {
        return AjaxResponse.error(new CustomException(ErrorCode.UNKNOWN));
    }
}

5、创建通用响应处理类

创建通用返回处理类 GlobalResponseHandler ,拦截所有的接口返回数据,将接口请求的HttpCode,设置为AjaxResponse的Code,保证在异常抛出时,前端能够感知到是异常请求。

@ControllerAdvice
public class GlobalResponseHandler implements ResponseBodyAdvice {
    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        if (returnType.getMethod() == null) {
            return false;
        }
        ResponseBody responseBody = returnType.getMethod().getAnnotation(ResponseBody.class);
        if (responseBody != null) {
            return true;
        }
        // 只拦截返回结果为 AjaxResponse 类型
        return returnType.getMethod().getReturnType() == AjaxResponse.class;
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType,
                                  MediaType selectedContentType,
                                  Class selectedConverterType,
                                  ServerHttpRequest request,
                                  ServerHttpResponse response) {

        ResponseBody responseBody = returnType.getMethod().getAnnotation(ResponseBody.class);
        if (responseBody != null || selectedContentType.equalsTypeAndSubtype(MediaType.APPLICATION_JSON)) {
            if (body instanceof AjaxResponse<?> ajaxBody) {
                if (!Objects.equals(ajaxBody.getCode(), ErrorCode.UNKNOWN.getCode())) {
                    response.setStatusCode(HttpStatus.valueOf(ajaxBody.getCode()));
                }
            } else {
                return AjaxResponse.su***ess(body);
            }
        }
        return body;
    }
}

6、创建测试类,模拟抛出异常

1)创建测试异常Service

@Service
public class ExceptionService {

    public void serverError() {
        try {
            Class.forName("***.mysql.jdbc.xxx.Driver");
        } catch (Exception e) {
            throw new CustomException(ErrorCode.SERVER_ERROR, "数据库驱动加载异常,出现ClassNotFoundException,请联系管理员");
        }
    }

    public void badRequest() {
        throw new CustomException(ErrorCode.BAD_REQUEST, "您输入的数据不符合业务逻辑,请确认后重新输入!");
    }
}

2)创建测试Controller方法

@GetMapping("/user")
@ResponseBody
public User user(User user) {
    Assert.isTrue(user.getName().equals("jackson"), "User must be a jackson");
    if (user.getAge() < 18 & user.getAge() >= 0) {
        exceptionService.badRequest();
    } else if (user.getAge() < 0) {
        exceptionService.serverError();
    }
    return user;
}

可以使用postman进行测试,传入不同的参数抛出不同异常

三、 @ControllerAdvice 特别说明

@ControllerAdvice是Spring 3.2及以后版本中引入的一个注解,它用于全局地处理控制器层的异常和其他跨切面的关注点。该注解提供了一种集中的方式,使得开发者可以在单个位置定义并管理多个控制器中可能遇到的通用逻辑。

具体来说,@ControllerAdvice的作用主要体现在以下几个方面:

  1. 全局异常处理:通过结合@ExceptionHandler注解,开发者可以定义全局级别的异常处理逻辑。这避免了在每个控制器中重复编写相同的异常处理代码。当控制器中抛出异常时,Spring MVC会查找带有@ControllerAdvice注解的类,并尝试调用匹配的@ExceptionHandler方法来处理异常。
  2. 数据绑定:通过结合@InitBinder注解,开发者可以在多个控制器之间配置通用的WebDataBinder设置。这对于自定义请求参数的绑定和格式化非常有用。例如,开发者可以定义全局的日期格式、数字格式等。
  3. 模型增强:通过结合@ModelAttribute注解,开发者可以在多个控制器间添加公共的模型属性。这对于添加那些需要在多个控制器或视图中使用的数据非常方便。例如,开发者可以在一个带有@ControllerAdvice注解的类中定义一个方法,该方法会在每个控制器方法执行之前执行,从而向模型中添加一些公共属性。

总的来说,@ControllerAdvice注解提供了一个强大的机制,使得开发者能够以一种集中和模块化的方式处理控制器层的异常和其他跨切面的关注点。这不仅提高了代码的可维护性和可重用性,还使得异常处理和数据绑定等逻辑更加清晰和易于管理。

四、Http状态码

状态码 类别 原因短语 描述
100 信息性响应 Continue 请求已收到,请继续发送
101 信息性响应 Switching Protocols 切换协议
102 信息性响应 Processing 请求正在处理中
200 成功 OK 请求成功
201 成功 Created 请求成功且资源已创建
202 成功 A***epted 请求已接受,处理中
203 成功 Non-Authoritative Information 非授权信息
204 成功 No Content 无内容
205 成功 Reset Content 重置内容
206 成功 Partial Content 部分内容
300 重定向 Multiple Choices 多种选择
301 重定向 Moved Permanently 永久移动
302 重定向 Found 临时移动
303 重定向 See Other 查看其他位置
304 重定向 Not Modified 未修改
307 重定向 Temporary Redirect 临时重定向
400 客户端错误 Bad Request 错误请求
401 客户端错误 Unauthorized 未授权
402 客户端错误 Payment Required 需要付款
403 客户端错误 Forbidden 禁止访问
404 客户端错误 Not Found 未找到资源
405 客户端错误 Method Not Allowed 方法不允许
406 客户端错误 Not A***eptable 不可接受
407 客户端错误 Proxy Authentication Required 需要代理身份验证
408 客户端错误 Request Timeout 请求超时
409 客户端错误 Conflict 冲突
410 客户端错误 Gone 资源已消失
500 服务器错误 Internal Server Error 服务器内部错误
501 服务器错误 Not Implemented 未实现功能
502 服务器错误 Bad Gateway 错误的网关
503 服务器错误 Service Unavailable 服务不可用
504 服务器错误 Gateway Timeout 网关超时
505 服务器错误 HTTP Version Not Supported 不支持的HTTP版本
506 服务器错误 Variant Also Negotiates 协商变体也存在
507 服务器错误 Insufficient Storage 存储不足
508 服务器错误 Loop Detected 检测到循环

参考

  • https://docs.spring.io/spring-boot/docs/3.2.3/reference/htmlsingle/#web.servlet.spring-mvc.error-handling
转载请说明出处内容投诉
CSS教程_站长资源网 » 【SpringBoot3】统一数据响应,全局异常处理以及通用响应处理

发表评论

欢迎 访客 发表评论

一个令你着迷的主题!

查看演示 官网购买