SpringBoot优雅地实现全局异常处理的方法详解
作者:慕歌 发布时间:2023-09-06 09:17:02
标签:SpringBoot,全局,异常,处理
前言
在前一节的学习中,慕歌带大家使用了全局结果集返回,通过使用全局结果集配置,优雅的返回后端数据,为前端的数据拿取提供了非常好的参考。同时通过不同的状态码返回,我们能够清晰的了解报错的位置,排除错误。如果大家有需要,可以使用我提供的的同一结果集以及状态码,并且可以使用全局异常拦截,实现异常的标准返回。接下来,我们一起来了解如何使用全局异常处理吧!
异常工具
先定义一个合适 的异常处理类,在之后的异常都会以这种格式返回前端,前端根据我们的异常进行自己的返回,以一种优雅的方式呈现错误,优化用户体验。
异常结果集:
/**
* 返回结果封装
*/
@Data
public class ResultVo {
// 状态码
private int code;
// 状态信息
private String msg;
// 返回对象
private Object data;
// 手动设置返回vo
public ResultVo(int code, String msg) {
this.code = code;
this.msg = msg;
}
// 手动设置返回vo
public ResultVo(int code, String msg, Object data) {
this.code = code;
this.msg = msg;
this.data = data;
}
// 只返回状态码
public ResultVo(StatusCode statusCode) {
this.code = statusCode.getCode();
this.msg = statusCode.getMsg();
}
// 默认返回成功状态码,数据对象
public ResultVo(Object data) {
this.code = ResultCode.SUCCESS.getCode();
this.msg = ResultCode.SUCCESS.getMsg();
this.data = data;
}
// 返回指定状态码,数据对象
public ResultVo(StatusCode statusCode, Object data) {
this.code = statusCode.getCode();
this.msg = statusCode.getMsg();
this.data = data;
}
public ResultVo(StatusCode statusCode,String msg, Object data) {
this.code = statusCode.getCode();
this.msg = msg;
this.data = data;
}
}
异常状态码,通过返回的状态码,以及状态信息,能够高效反映错误,并且可以全局统一管理,方便快捷:
@Getter
public enum ExceptionCode implements StatusCode {
// 系统级别错误码
ERROR(-1, "操作异常"),
NOT_LOGIN(102, "请先登录!"),
NO_Role(102,"无权限"),
NO_PERMISSION(102,"无权限"),
OUT_TIME(102,"登录信息过期"),
DISABLE_ACCOUNT(102,"帐号已被禁用!"),
EMAIL_DISABLE_LOGIN(102,"该邮箱账号已被管理员禁止登录!"),
IP_REPEAT_SUBMIT(102,"访问次数过多,请稍后重试"),
ERROR_DEFAULT(105,"系统繁忙,请稍后重试");
//异常码
private int code;
//异常信息
private String msg;
//自定义方法
ExceptionCode(int code, String msg) {
this.code = code;
this.msg = msg;
}
}
当我们对异常通过以上工具类进行封装之后,所有异常将以一种固定的格式返回,不会导致错乱:
{3 items
"code":105
"msg":"系统繁忙,请稍后重试"
"data":NULL
}
异常处理
在spring boot中需要使用异常 * ,拦截全局的异常,不直接将异常返回,而是在我们进行处理之后,以一种清晰可读的方式返回。并且前端能够清晰解读我们的异常,呈现给用户。
//捕获校验器异常
@RestControllerAdvice
public class ControllerExceptionAdvice {
@ExceptionHandler({BindException.class})
public ResultVo ValidExceptionHandler(BindException e) {
// 从异常对象中拿到ObjectError对象
ObjectError objectError = e.getBindingResult().getAllErrors().get(0);
return new ResultVo(ResultCode.VALIDATE_ERROR.getCode(),objectError.getDefaultMessage());
}
}
对特定异常进行拦截,并包装异常:
/**
* 对返回结果进行包装
*/
@RestControllerAdvice(basePackages = {"channel.cert"})
public class ControllerResponseAdvice implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
// response是ResultVo类型,或者注释了NotControllerResponseAdvice都不进行包装
return !methodParameter.getParameterType().isAssignableFrom(ResultVo.class);
}
@Override
public Object beforeBodyWrite(Object data, MethodParameter returnType, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest request, ServerHttpResponse response) {
// String类型不能直接包装
if (returnType.getGenericParameterType().equals(String.class)) {
ObjectMapper objectMapper = new ObjectMapper();
try {
// 将数据包装在ResultVo里后转换为json串进行返回
return objectMapper.writeValueAsString(new ResultVo(data));
} catch (JsonProcessingException e) {
throw new APIException(ResultCode.RESPONSE_PACK_ERROR, e.getMessage());
}
}
// 否则直接包装成ResultVo返回
return new ResultVo(data);
}
}
异常捕捉
自定义异常:
@Getter
public class APIException extends RuntimeException {
private int code;
private String msg;
//自定义异枚举
public APIException(StatusCode statusCode){
super(statusCode.getMsg());
this.code = statusCode.getCode();
this.msg = statusCode.getMsg();
}
// 手动设置异常
public APIException(StatusCode statusCode, String message) {
// message用于用户设置抛出错误详情,例如:当前价格-5,小于0
super(message);
// 状态码
this.code = statusCode.getCode();
// 状态码配套的msg
this.msg = statusCode.getMsg();
}
// 默认异常使用APP_ERROR状态码
public APIException(String errorMsg) {
super(errorMsg);
this.code = ExceptionCode.ERROR_DEFAULT.getCode();
this.msg = ExceptionCode.ERROR_DEFAULT.getMsg();
}
//自定义参数 错误码 错误信息
public APIException(int errorCode, String errorMsg) {
super(errorMsg);
this.code = errorCode;
this.msg = ExceptionCode.ERROR_DEFAULT.getMsg();
}
//自定义参数 错误码 错误信息 异常
public APIException(int errorCode, String errorMsg, Throwable cause) {
super(errorMsg);
this.code = errorCode;
this.msg = errorMsg;
}
}
对自定义异常进行捕获,通过定义好的异常的结果集返回。
/**
* 全局异常处理
*/
@Slf4j
@RestControllerAdvice
public class GlobalExceptionAdvice {
// Assert业务异常
@ExceptionHandler(IllegalArgumentException.class)
public ResultVo AssertExceptionHandler(IllegalArgumentException ex) {
log.error( " msg : " + ex.getMessage(), ex);
if(StringUtils.isBlank(ex.getLocalizedMessage())){
return new ResultVo(ExceptionCode.ERROR_DEFAULT);
}
return new ResultVo(ex.getMessage());
}
// 登录失效异常
@ExceptionHandler(SaTokenException.class)
public ResultVo LoginOutExceptionHandler(SaTokenException ex) {
log.error( " msg : " + ex.getMessage(), ex);
return new ResultVo(ExceptionCode.OUT_TIME);
}
// 登录异常
@ExceptionHandler(NotLoginException.class)
public ResultVo NotLoginExceptionHandler(NotLoginException ex) {
log.error( " msg : " + ex.getMessage(), ex);
return new ResultVo(ExceptionCode.NOT_LOGIN);
}
// 权限异常
@ExceptionHandler(NotPermissionException.class)
public ResultVo NotPermissionExceptionHandler(NotPermissionException ex) {
log.error( " msg : " + ex.getMessage(), ex);
return new ResultVo(ExceptionCode.NO_PERMISSION);
}
//角色异常
@ExceptionHandler(NotRoleException.class)
public ResultVo NotRoleExceptionHandler(NotRoleException ex) {
log.error( " msg : " + ex.getMessage(), ex);
return new ResultVo(ExceptionCode.NO_Role);
}
//处理自定义异常
@ExceptionHandler(APIException.class)
public ResultVo APIExceptionHandler(APIException e) {
log.error(e.getMessage(), e);
return new ResultVo(e.getCode(), e.getMsg());
}
//处理运行异常
@ExceptionHandler(RuntimeException.class)
public ResultVo RuntimeExceptionHandler(RuntimeException e) {
log.error(e.getMessage(), e);
return new ResultVo(ExceptionCode.ERROR_DEFAULT);
}
}
通过以上自定义,我们就能在项目中,使用自定义异常,在我们认为可能的报错处插入,当发生错误时,我们更快定位是之前记录的错误点导致。
//查询数字证书
@Override
public GroupUser searchCert(String certCode) {
try {
//查询证书编号
GroupUser user = queryCert(certCode);
if(ObjectUtil.isNotNull(user)){
return user;
}
}catch (Exception e){
throw new APIException("区块链调用失败"+e);
}
return null;
}
来源:https://juejin.cn/post/7132836592368959496


猜你喜欢
- 初学者,照着书上的抄袭制作,但已经理解了里面的意思和应用,并且进行了稍微改善和异常捕捉。这里记录下,以防以后用到这方面的知识点。窗体设计:c
- 目录1.基于注释声明缓存1.1@EnableCaching1.2@Cacheable1.2.1默认key生成规则1.2.2声明自定义key
- C#实现的鼠标钩子,可以获取鼠标在屏幕中的坐标,记得要以管理员权限运行才行using System;using System.Collect
- 本文实例为大家分享了C# GDI+实现时钟表盘的具体代码,供大家参考,具体内容如下一、设计如下图界面按键“打开时钟&am
- 目录前言:1.委托的声明1.1.delegate1.1.1. 0-23个参数,可以有返回值也可以没有返回值1.1.2.委托的调用1.1.3.
- 对于Android View的测量,我们一句话总结为:
- 前言作为Java开发工程师,相信大家对Spring种事务的使用并不陌生。但是你可能只是停留在基础的使用层面上,在遇到一些比较特殊的场景,事务
- SpringBoot项目中新增脱敏功能项目背景目前正在开发一个SpringBoot项目,此项目有Web端和微信小程序端。web端提供给工作人
- 在网上也没有找到好的解决方案,于是自己研究了下给解决了,分享给大家,希望对大家能有所帮助。一、异常信息这种情况是因为FTP设置的默认目录引发
- 使用官方的刷新控件SwipeRefreshLayout来实现下拉刷新,当RecyclerView滑到底部实现下拉加载(进度条效果用Recyc
- 1.Action中的validate()方法Struts2提供了一个Validateable接口,这个接口中只存在validat
- 1.使用usb口输入的扫描枪,这里实现使用了winform首先创建一个CS文件using System;using System.Colle
- 本文实例为大家分享了RecyclerView实现水平列表的具体代码,供大家参考,具体内容如下1、效果图2、activity_horizont
- Room的三个主要组件:数据库类,用于保存数据库并作为应用持久性数据底层连接的主要访问点。数据实体,@Entity,表示数据库中的表。数据访
- 默认路径在Spring Boot 2.7.2版本中,查看默认静态资源路径,在WebProperties.class中如下private st
- 事件函数的执行顺序先说一下执行顺序吧。 官方给出的脚本中事件函数的执行顺序如下图: 我们可以做一个小实验来测试一下: 在Hierarchy
- 一、前言本篇文章聚焦在“如何使用FragmentStatePagerAdapter来保存Fragment的数据、在内存中
- 一、链表1.1 概述链表是真正动态的数据结构,最简单的动态数据结构,基本用于辅助组成其他数据结构。数据存储在“节点”(Node)中优点:真正
- 此解决方案是针对window的,因为日志默认保存路径在C盘,linux忽略。学习RocketMQ过程中,总是出现com.alibaba.ro
- 就网络和应用程序而言,键盘快捷键很重要,今天我们要谈的便是让这类快捷键得以在Flutter运作的小部件:Focus、Shortcuts和Ac