一、设计一个优秀的异常处理机制
在spring Boot中设计一个优秀的异常处理机制,可以确保应用程序在遇到错误时提供清晰、一致的响应,同时提高系统的健壮性和可维护性。
以下是一个关于如何设计Spring Boot异常处理机制的步骤和建议:
1)定义自定义异常类
- 创建自定义异常类来代表应用程序中可能发生的特定错误情况。
- 自定义异常类应该扩展标准的
RuntimeException
或Exception
类,并提供一个明确的消息来描述错误。
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的作用主要体现在以下几个方面:
-
全局异常处理:通过结合
@ExceptionHandler
注解,开发者可以定义全局级别的异常处理逻辑。这避免了在每个控制器中重复编写相同的异常处理代码。当控制器中抛出异常时,Spring MVC会查找带有@ControllerAdvice
注解的类,并尝试调用匹配的@ExceptionHandler
方法来处理异常。 -
数据绑定:通过结合
@InitBinder
注解,开发者可以在多个控制器之间配置通用的WebDataBinder设置。这对于自定义请求参数的绑定和格式化非常有用。例如,开发者可以定义全局的日期格式、数字格式等。 -
模型增强:通过结合
@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