全局异常,冲!
到了睡觉的时候,还是没有太急着下班,把全局异常讲完!
在程序中我们常常会主动
或者被动
的向外抛出异常,但是如果程序一直向外抛出异常,而不去对其做处理,那用户体验感将会很低,所以我们常常会在代码中使用 try、catch 来对异常进行捕获,我们会大概猜测此处可能会抛出的异常类型,然后使用catch块 对其进行捕获。
但是,还有很多异常我们无法预判到呀!总不能事无巨细的都考虑到。另一方面,当我们在代码中写满 try、catch............那是多么丑陋!为了程序代码的可读性,我们需要将抛出的异常进行一个统一的处理——全局异常处理。
一、异常处理注解
/** * @RestControllerAdvice 捕获@controller 标识的类抛出的异常 * @RestControllerAdvice = @ResponseBody + @ControllerAdvice * 定义在类名头 */ @RestControllerAdvice
@ControllerAdvice
是处理响应为View或者String的控制器的异常,通常用于后端页面渲染的场景。而@RestControllerAdvice
则是处理响应为Json或者Xml等格式的控制器的异常,通常用于前后端分离的场景。此外,
@RestControllerAdvice
注解还有一个特殊的作用:当使用@ExceptionHandler
注解处理异常时,@RestControllerAdvice
注解可以使其返回值直接转换成Json格式的数据,而不需要通过ViewResolver转换成视图。因此,当使用Spring Boot开发Restful API时,通常会使用
@RestControllerAdvice
注解进行全局异常处理。
@ExceptionHandler /** 如果方法上标注了 @ExceptionHandler 注解并指定了对应的异常类型,则 Spring 会自动捕获该异常,并调用标注了 @ExceptionHandler 注解的方法来处理该异常。 */ @ExceptionHandler(HttpRequestMethodNotSupportedException.class) public JSONObject handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e, HttpServletRequest request) { String requestURI = request.getRequestURI(); log.error("请求地址'{}',不支持'{}'请求", requestURI, e.getMethod()); return ResponseResult.failure("请求不支持!"); }
以上的两个注解都是在全局异常类中使用。
二、为什么需要全局异常类
全局异常处理类是为了处理整个系统中的异常情况,而不是仅仅在每个请求或方法内部处理异常。通过全局异常处理类,可以将相同的异常处理方式应用于整个系统,减少代码重复,提高代码的可维护性和可读性。
另外,全局异常处理类还可以帮助我们优雅地处理异常信息,并且可以提供友好的提示信息,避免向用户抛出过于具体的错误信息。同时,全局异常处理类也有助于记录异常信息,方便开发人员进行排查和调试。
三、怎么优雅的抛出异常
既然都有了全局异常处理,那我程序就直接抛出异常就好了,不需要写那么多行的try。
1、Assert断言
使用assert可以将不符合条件的问题都进行抛出,异常类型会随着使用的方法不同而变化。
明确一点,assert断言一般用于在开发环境中使用,在生产环境中应该尽量使用Preconditions来进行抛出异常。
jdk1.8之后assert在默认情况下是开启的。
Java自带的工具类,可以直接使用。
Preconditions 更适合用于公共方法中对参数的检查,而 assert 更适合用于程序内部的调试和测试。
Assert.isTrue(boolean condition):判断条件是否为true。 Assert.isFalse(boolean condition):判断条件是否为false。 Assert.isNull():判断对象是否为null。 Assert.notNull():判断对象是否不为null。 Assert.notEmpty():判断是否为null,长度是否0
2、Preconditions
// 检查 boolean 类型的表达式是否为 true,如果不是,则抛出 IllegalArgumentException 异常,并将指定的错误消息添加到异常信息中。 checkArgument(boolean expression, Object errorMessage): // 检查对象是否为 null,如果为 null,则抛出 NullPointerException 异常,并将指定的错误消息添加到异常信息中。 checkNotNull(T reference, Object errorMessage): // 检查 boolean 类型的表达式是否为 true,如果不是,则抛出 IllegalStateException 异常,并将指定的错误消息添加到异常信息中。 checkState(boolean expression, Object errorMessage): // 检查下标是否在 [0, size) 范围内,如果不是,则抛出 IndexOutOfBoundsException 异常,并将指定的错误消息添加到异常信息中。 checkElementIndex(int index, int size, Object errorMessage): // 检查下标是否在 [0, size] 范围内,如果不是,则抛出 IndexOutOfBoundsException 异常,并将指定的错误消息添加到异常信息中。 checkPositionIndex(int index, int size, Object errorMessage): // 检查下标范围 [start, end] 是否在 [0, size] 范围内,如果不是,则抛出 IndexOutOfBoundsException 异常。 checkPositionIndexes(int start, int end, int size):
四、定义全局异常类
@Slf4j @Component @RestControllerAdvice @ConditionalOnWebApplication @ConditionalOnMissingBean(UnifiedExceptionHandler.class) public class UnifiedExceptionHandler { /** * 生产环境 */ private final static String ENV_PROD = "prod"; @Autowired private UnifiedMessageSource unifiedMessageSource; /** * 当前环境 */ @Value("${spring.profiles.active}") private String profile; /** * 获取国际化消息 * * @param e 异常 * @return */ public String getMessage(BaseException e) { String code = "response." + e.getResponseEnum().toString(); String message = unifiedMessageSource.getMessage(code, e.getArgs()); if (message == null || message.isEmpty()) { return e.getMessage(); } return message; } /** * 业务异常 * * @param e 异常 * @return 异常结果 */ @ExceptionHandler(value = BusinessException.class) public ErrorResponse handleBusinessException(BaseException e) { log.error(e.getMessage(), e); return new ErrorResponse(e.getResponseEnum().getCode(), getMessage(e)); } /** * 自定义异常 * * @param e 异常 * @return 异常结果 */ @ExceptionHandler(value = BaseException.class) public ErrorResponse handleBaseException(BaseException e) { log.error(e.getMessage(), e); return new ErrorResponse(e.getResponseEnum().getCode(), getMessage(e)); } /** * Controller上一层相关异常 * * @param e 异常 * @return 异常结果 */ @ExceptionHandler({ NoHandlerFoundException.class, HttpRequestMethodNotSupportedException.class, HttpMediaTypeNotSupportedException.class, MissingPathVariableException.class, MissingServletRequestParameterException.class, TypeMismatchException.class, HttpMessageNotReadableException.class, HttpMessageNotWritableException.class, // BindException.class, // MethodArgumentNotValidException.class HttpMediaTypeNotAcceptableException.class, ServletRequestBindingException.class, ConversionNotSupportedException.class, MissingServletRequestPartException.class, AsyncRequestTimeoutException.class }) public ErrorResponse handleServletException(Exception e) { log.error(e.getMessage(), e); int code = CommonResponseEnum.SERVER_ERROR.getCode(); try { ServletResponseEnum servletExceptionEnum = ServletResponseEnum.valueOf(e.getClass().getSimpleName()); code = servletExceptionEnum.getCode(); } catch (IllegalArgumentException e1) { log.error("class [{}] not defined in enum {}", e.getClass().getName(), ServletResponseEnum.class.getName()); } if (ENV_PROD.equals(profile)) { // 当为生产环境, 不适合把具体的异常信息展示给用户, 比如404. code = CommonResponseEnum.SERVER_ERROR.getCode(); BaseException baseException = new BaseException(CommonResponseEnum.SERVER_ERROR); String message = getMessage(baseException); return new ErrorResponse(code, message); } return new ErrorResponse(code, e.getMessage()); } /** * 参数绑定异常 * * @param e 异常 * @return 异常结果 */ @ExceptionHandler(value = BindException.class) public ErrorResponse handleBindException(BindException e) { log.error("参数绑定校验异常", e); return wrapperBindingResult(e.getBindingResult()); } /** * 参数校验异常,将校验失败的所有异常组合成一条错误信息 * * @param e 异常 * @return 异常结果 */ @ExceptionHandler(value = MethodArgumentNotValidException.class) public ErrorResponse handleValidException(MethodArgumentNotValidException e) { log.error("参数绑定校验异常", e); return wrapperBindingResult(e.getBindingResult()); } /** * 包装绑定异常结果 * * @param bindingResult 绑定结果 * @return 异常结果 */ private ErrorResponse wrapperBindingResult(BindingResult bindingResult) { StringBuilder msg = new StringBuilder(); for (ObjectError error : bindingResult.getAllErrors()) { msg.append(", "); if (error instanceof FieldError) { msg.append(((FieldError) error).getField()).append(": "); } msg.append(error.getDefaultMessage() == null ? "" : error.getDefaultMessage()); } return new ErrorResponse(ArgumentResponseEnum.VALID_ERROR.getCode(), msg.substring(2)); } /** * 未定义异常 * * @param e 异常 * @return 异常结果 */ @ExceptionHandler(value = Exception.class) public ErrorResponse handleException(Exception e) { log.error(e.getMessage(), e); if (ENV_PROD.equals(profile)) { // 当为生产环境, 不适合把具体的异常信息展示给用户, 比如数据库异常信息. int code = CommonResponseEnum.SERVER_ERROR.getCode(); BaseException baseException = new BaseException(CommonResponseEnum.SERVER_ERROR); String message = getMessage(baseException); return new ErrorResponse(code, message); } return new ErrorResponse(CommonResponseEnum.SERVER_ERROR.getCode(), e.getMessage()); } }
参考: