在Spring Boot中设计一个优秀的异常处理机制,可以确保应用程序在遇到错误时提供清晰、一致的响应,同时提高系统的健壮性和可维护性。
以下是一个关于如何设计Spring Boot异常处理机制的步骤和建议:
1)定义自定义异常类
2)创建全局异常处理器
3)定义统一的异常响应格式
4)异常分层
5)日志记录
6)测试
7)提供友好的用户错误信息
8)处理全局异常
9)考虑国际化
10)优雅地处理资源不足
11)使用AspectJ进行切面编程
通过遵循这些步骤,你可以设计一个强大而灵活的异常处理机制,提高Spring Boot应用程序的健壮性和用户体验。
我们必须为所有的接口定义统一的数据响应格式,创建统一数据响应类
@Data public class AjaxResponse{ 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> success() { AjaxResponse> response = new AjaxResponse<>(); response.setCode(ErrorCode.SUCCESS.getCode()); response.setMessage(ErrorCode.SUCCESS.getMsg()); return response; } public static AjaxResponse success(T data) { AjaxResponse response = new AjaxResponse<>(); response.setCode(ErrorCode.SUCCESS.getCode()); response.setMessage(ErrorCode.SUCCESS.getMsg()); response.setData(data); return response; } }
建议直接使用Http状态码
@Getter
public enum ErrorCode {
SUCCESS(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;
}
}
创建自定义异常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;
}
}
创建全局异常处理类 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));
}
}
创建通用返回处理类 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.success(body);
}
}
return body;
}
}
1)创建测试异常Service
@Service
public class ExceptionService {
public void serverError() {
try {
Class.forName("com.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是Spring 3.2及以后版本中引入的一个注解,它用于全局地处理控制器层的异常和其他跨切面的关注点。该注解提供了一种集中的方式,使得开发者可以在单个位置定义并管理多个控制器中可能遇到的通用逻辑。
具体来说,@ControllerAdvice的作用主要体现在以下几个方面:
总的来说,@ControllerAdvice注解提供了一个强大的机制,使得开发者能够以一种集中和模块化的方式处理控制器层的异常和其他跨切面的关注点。这不仅提高了代码的可维护性和可重用性,还使得异常处理和数据绑定等逻辑更加清晰和易于管理。
| 状态码 | 类别 | 原因短语 | 描述 |
|---|---|---|---|
| 100 | 信息性响应 | Continue | 请求已收到,请继续发送 |
| 101 | 信息性响应 | Switching Protocols | 切换协议 |
| 102 | 信息性响应 | Processing | 请求正在处理中 |
| 200 | 成功 | OK | 请求成功 |
| 201 | 成功 | Created | 请求成功且资源已创建 |
| 202 | 成功 | Accepted | 请求已接受,处理中 |
| 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 Acceptable | 不可接受 |
| 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 | 检测到循环 |
上一篇:MySQL的21个SQL经验