Redian新闻
>
如何优雅的处理异常

如何优雅的处理异常

公众号新闻

来源 | OSCHINA 社区

作者 | 京东云开发者-京东零售 秦浩然

原文链接:https://my.oschina.net/u/4090830/blog/8694561

一、什么是异常

Java 语言按照错误严重性,从 throwale 根类衍生出 Error 和 Exception 两大派系。

Error(错误):

程序在执行过程中所遇到的硬件或操作系统的错误。错误对程序而言是致命的,将导致程序无法运行。常见的错误有内存溢出,jvm 虚拟机自身的非正常运行,calss 文件没有主方法。程序本生是不能处理错误的,只能依靠外界干预。Error 是系统内部的错误,由 jvm 抛出,交给系统来处理。

Exception(异常):

程序正常运行中,可以预料的意外情况。比如数据库连接中断,空指针,数组下标越界。异常出现可以导致程序非正常终止,也可以预先检测,被捕获处理掉,使程序继续运行。Exception(异常)按照性质,又分为编译异常(受检异常)和运行时异常(非受检异常)。

◦ 编译异常:

又叫可检查异常,通常时由语法错和环境因素(外部资源)造成的异常。比如输入输出异常 IOException,数据库操作 SQLException。其特点是,Java 语言强制要求捕获和处理所有非运行时异常。通过行为规范,强化程序的健壮性和安全性。

◦ 运行时异常:

又叫不检查异常 RuntimeException,这些异常一般是由程序逻辑错误引起的,即语义错。比如算术异常,空指针异常 NullPointerException,下标越界 IndexOutOfBoundsException。运行时异常应该在程序测试期间被暴露出来,由程序员去调试,而避免捕获。

二、处理异常方式

代码中,我们最常见到的处理异常的方式就是:try-catch

        try {
// 业务逻辑

} catch (Exception e) {
// 捕获到异常的逻辑
}

或者是再进一步区分下异常类型:

        try {
// 业务逻辑

} catch (IOException ie) {
// 捕获到IO异常的逻辑

} catch (Exception e) {
// 捕获到其他异常的逻辑
}

三、如何抛出异常

我们通常可以用抛出异常的方式来控制代码流程,然后在网关处统一 catch 异常来返回错误 code。这在一定程度上可以简化代码流程控制,如下所示:
    @Override
public UserVO queryUser(Long id) {
UserDO userDO = userMapper.queryUserById(id);
if (Objects.isNull(userDO)) {
throw new RuntimeException("用户不存在"); //用户不存在抛出异常
}
return userDO.toVo();
}

上面这种抛出异常的方式,虽然简化了代码流程,但是在存在多种错误场景时,没有办法细分具体的错误类型。如:用户不存在的错误、用户没有权限的错误;

聪明如你,一定想到了自定义异常,如下:

    @Override
public UserVO queryUser(Long id) {
UserDO userDO = userMapper.queryUserById(id);
if (Objects.isNull(userDO)) {
throw new UserNotFoundException(); //用户不存在抛出对应异常
}
if(!checkLicence(userDO)) {
throw new BadLicenceException(); //用户无权限抛出对应异常
}
return userDO.toVo();
}
确实,自定义异常可以解决错误场景细分的问题。进一步的,我们可以对系统流程不同阶段、不同业务类型分别自定义异常,但这需要自定义大量的异常;

四、如何优雅的抛出异常

上面的方式,可以区分出错误场景了,但是还存在一些缺点。如:可读性差、需要定义大量的自定义异常;
那我们下面就去优化上面的问题;
用断言增加代码的可读性;
    @Override
public UserVO queryUser(Long id) {
UserDO userDO = userMapper.queryUserById(id);
Assert.notNull(userDO, "用户不存在"); //用断言进行参数的非空校验
return userDO.toVo();
}
断言虽然代码简洁、可读性好,但是缺乏像上述自定义异常一样可以明确区分错误场景,这就引出我们的究极方案:自定义断言;
自定义断言;
我们用自定义断言的方式,综合上面自定义异常和断言的优点,在断言失败后,抛出我们制定好的异常。代码如下:
・自定义异常基本类
@Getter
@Setter
public class BaseException extends RuntimeException {

// 响应码
private IResponseEnum responseEnum;

// 参数信息
private Object[] objs;

public BaseException(String message, IResponseEnum responseEnum, Object[] objs) {
super(message);
this.responseEnum = responseEnum;
this.objs = objs;
}

public BaseException(String message, Throwable cause, IResponseEnum responseEnum, Object[] objs) {
super(message, cause);
this.responseEnum = responseEnum;
this.objs = objs;
}
}
・自定义断言接口
public interface MyAssert {

/**
* 创建自定义异常
*
* @param objs 参数信息
* @return 自定义异常
*/

BaseException newException(Object... objs);

/**
* 创建自定义异常
*
* @param msg 描述信息
* @param objs 参数信息
* @return 自定义异常
*/

BaseException newException(String msg, Object... objs);

/**
* 创建自定义异常
*
* @param t 接收验证异常
* @param msg 描述信息
* @param objs 参数信息
* @return 自定义异常
*/

BaseException newException(Throwable t, String msg, Object... objs);


/**
* 校验非空
*
* @param obj 被验证对象
*/

default void assertNotNull(Object obj, Object... objs) {
if (obj == null) {
throw newException(objs);
}
}

/**
* 校验非空
*
* @param obj 被验证对象
*/

default void assertNotNull(Object obj, String msg, Object... objs) {
if (obj == null) {
throw newException(msg, objs);
}
}
}
上述代码我们可以看出基本设计,就是在我们自定义断言失败后抛出我们自定义异常。
下面是具体的实现案例:
・自定义业务异常类,继承自异常基本类
public class BusinessException extends BaseException {

public BusinessException(IResponseEnum responseEnum, Object[] args, String msg) {
super(msg, responseEnum, args);
}

public BusinessException(IResponseEnum responseEnum, Object[] args, String msg, Throwable t) {
super(msg, t, responseEnum, args);
}

}

・响应 code 枚举接口定义

public interface IResponseEnum {

/**
* 返回code码
*
* @return code码
*/

String getCode();

/**
* 返回描述信息
*
* @return 描述信息
*/

String getMsg();
}

・自定义业务异常类断言定义,实现自定义断言失败后对应的自定义异常的定义;

public interface BusinessExceptionAssert extends IResponseEnum, MyAssert {

@Override
default BaseException newException(Object... args) {
return new BusinessException(this, args, this.getMsg()); //断言失败后,抛出自定义异常
}

@Override
default BaseException newException(String msg, Object... args) {
return new BusinessException(this, args, msg); //断言失败后,抛出自定义异常
}

@Override
default BaseException newException(Throwable t, String msg, Object... args) {
return new BusinessException(this, args, msg, t); //断言失败后,抛出自定义异常
}
}

・用枚举的方式,代替 BadLicenceException、UserNotFoundException 自定义异常。

public enum ResponseEnum implements IResponseEnum, BusinessExceptionAssert {

BAD_LICENCE("0001", "无权访问"),

USER_NOT_FOUND("1001", "用户不存在"),
;

private final String code, msg;

ResponseEnum(String code, String msg) {
this.code = code;
this.msg = msg;
}

@Override
public String getCode() {
return code;
}

@Override
public String getMsg() {
return msg;
}
}
使用实例
自定义断言失败抛出自定义异常
    @Override
public UserVO queryUser(Long id) {
UserDO userDO = userMapper.queryUserById(id);
ResponseEnum.USER_NOT_FOUND.assertNotNull(userDO); //自定义断言失败抛出自定义异常
return userDO.toVo();
}

网关处统一 catch 异常,识别异常场景

    public static void main(String[] args) {
UserService userService = new UserServiceImpl(new UserMapperImpl());
UserController userController = new UserController(userService);
try {
UserVO vo = userController.queryUser(2L); //执行业务逻辑
} catch (BusinessException e) {
System.out.println(e.getResponseEnum().getCode()); //出现异常,错误code:1001
System.out.println(e.getMessage()); //出现异常,错误msg:用户不存在
}
}

五、如何优雅的处理异常

网关处统一处理异常,这属于常规操作,这里不再赘述,简单举例如下:
@ControllerAdvice
public class BusinessExceptionHandler {

@ExceptionHandler(value = BusinessException.class)
@ResponseBody
public Response handBusinessException(BaseException e) {
return new Response(e.getResponseEnum().getCode(), e.getResponseEnum().getMsg()); //统一处理异常
}
}
综上,我们采用自定义断言的方式,结合了断言的可读性高的优势和自定义异常区分错误场景的优势。并且,有新增的错误场景,我们只需要在错误码枚举中新增对应枚举即可。


END



“游戏发行版” Manjaro Linux用户正在迅速流失


🌟 活动推荐


2023 年 5 月 27-28 日,GOTC 2023 全球开源技术峰会将在上海张江科学会堂隆重举行。

为期 2 天的开源行业盛会,将以行业展览、主题发言、特别论坛、分论坛、快闪演讲的形式来诠释此次大会主题 ——“Open Source, Into the Future”。与会者将一起探讨元宇宙、3D 与游戏、eBPF、Web3.0、区块链等热门技术主题,以及 OSPO、汽车软件、AIGC、开源教育培训、云原生、信创等热门话题,探讨开源未来,助力开源发展。

长按识别下方二维码立即查看 GOTC 2023 详情/报名。

微信扫码关注该文公众号作者

戳这里提交新闻线索和高质量文章给我们。
相关阅读
造车,地产商最优雅的死法五个步骤,助你优雅的写好 Controller 层代码!如何健康优雅的活到100岁?|润米读书周,全场书籍,限时5折斯里兰卡实用锦囊 | 在网红餐厅“螃蟹部”如何优雅点餐?医疗|One Medical 招人优雅的接口防刷处理方案!遇事最有水平的处理逻辑,请看景甜女教师穿清凉孕装开家长会被举报,学校的处理意见却极其夸张!聊一款优雅的粉色轻薄本Vision Pro 电池更优雅的充电方式,我们找到了在初春的雨天SpringBoot 统一功能处理:用户登录权限校验-拦截器、异常处理、数据格式返回遇到烂人烂事,最聪明的处理方式:3个字两性交往,男人这3个方面越厉害,越让女人念念不忘遇事最有水平的处理方式:心态置顶,情绪置后易中天:“上海人和非上海人,几乎一眼就能区分开来”低调优雅的悉尼现代住宅【居住榜样】冰箱终于到货了当别人嘲讽Quant时,该如何优雅的怼回去?川普律师:希望在曼哈顿过堂将是“无痛和优雅的”宋慧乔教我如何优雅变老深度好文|当别人嘲讽Quant时,该如何优雅的怼回去?欧洲共识:TAVI患者冠状动脉疾病的处理热搜第一!梅西为缺席训练道歉:等待“大巴黎”的处理!或不再续约?去向成谜迫不及待想要穿上这条浪漫优雅的小裙子了!对于“毕节殴打记者事件”的处理,媒体不要高兴得太早优雅的接口防刷处理方案读完 RocketMQ 源码,我学会了如何优雅的创建线程一个有钱人才能想到的细节,生活就是要优雅的享受!欧盟官员:美国政府对硅谷银行破产事件的处理堪称“灾难”!30岁如何优雅地留在北上广?遇事最有水平的处理方式:快,准,稳遇到烂人烂事,最有水平的处理方式:三个字如何高效优雅的使用java枚举这3种优雅的嵌入式软件架构,你值得拥有!
logo
联系我们隐私协议©2024 redian.news
Redian新闻
Redian.news刊载任何文章,不代表同意其说法或描述,仅为提供更多信息,也不构成任何建议。文章信息的合法性及真实性由其作者负责,与Redian.news及其运营公司无关。欢迎投稿,如发现稿件侵权,或作者不愿在本网发表文章,请版权拥有者通知本网处理。