如何优雅的处理异常

作者:京东零售  秦浩然

一、什么是异常

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());    //统一处理异常
    }
}

综上,我们采用自定义断言的方式,结合了断言的可读性高的优势和自定义异常区分错误场景的优势。并且,有新增的错误场景,我们只需要在错误码枚举中新增对应枚举即可。

原文链接:https://www.cnblogs.com/Jcloud/p/17351770.html

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:如何优雅的处理异常 - Python技术站

(0)
上一篇 2023年4月25日
下一篇 2023年4月25日

相关文章

  • 怎么运行用记事本写的java程序

    当我们使用记事本来编写Java程序时,需要注意一些细节,例如设置环境变量、保存文件格式、编译运行指令等等。下面是详细讲解如何在Windows操作系统中使用记事本运行Java程序的攻略: 环境变量配置 为了让操作系统知道我们的Java程序在哪里,我们需要设置Java环境变量。可以按照如下步骤进行设置: 在桌面上用鼠标右键点击“此电脑”图标,然后选择“属性”选项…

    Java 2023年5月19日
    00
  • 详解Java中如何正确书写单例模式

    单例模式是一种常见的设计模式,常被用来保证一个类在系统中只有一个实例存在,以达到节省系统资源、避免重复实例化等效果。Java中正确书写单例模式有多种方式,在此给出一些详细的攻略和示例供大家参考。 1. 饿汉式单例模式 饿汉式单例模式就是在类加载的时候就创建它的唯一实例,并且所有的请求都会返回同一个实例。这种实现方式的优点是线程安全,缺点是类加载的时候就初始化…

    Java 2023年5月26日
    00
  • Spark SQL常见4种数据源详解

    Spark SQL常见4种数据源详解 Spark SQL是一个强大的分布式数据处理引擎,可以对多种数据源进行处理。本文将重点讲解Spark SQL常见的4种数据源,包括Hive、JSON、Parquet和JDBC,并附带示例说明。 1. Hive Hive是Hadoop的数据仓库,Spark可以使用Hive的数据进行处理。为了使用Hive,请按照以下步骤: …

    Java 2023年6月16日
    00
  • JSON 与对象、集合之间的转换的示例

    JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,常用于前后端数据传输。在JavaScript中,可以轻松将JSON格式的数据存储在对象或集合中,也可以将对象或者集合转换为JSON格式的数据。下面,我们通过两个示例来详细讲解JSON与对象、集合之间的转换攻略。 示例一:JSON字符串转对象 我们假设有如下JSON字符…

    Java 2023年5月26日
    00
  • Spring @DateTimeFormat日期格式化时注解场景分析

    当我们在Spring中使用日期类型的时候,通常需要对日期进行格式化,否则就会出现无法解析的错误。而Spring提供的@DateTimeFormat注解可以帮我们在响应请求时对日期进行格式化,是一个非常方便的工具。 什么是@DateTimeFormat @DateTimeFormat是Spring的一个注解,用于序列化和反序列化日期类型。它可以指定日期格式,并…

    Java 2023年6月1日
    00
  • SpringSecurity+JWT实现前后端分离的使用详解

    实现前后端分离的一个重要问题是如何进行身份验证和授权。Spring Security提供了一个非常方便的方法来处理这个问题,即使用JSON Web Token(JWT)。 JWT是一种用于身份验证和授权的开放标准,它定义了一种紧凑的、自包含的、可自校验的JSON格式来传递信息,通常用于在安全领域的传输而被广泛使用。 下面是SpringSecurity+JWT…

    Java 2023年5月20日
    00
  • Java中关键字synchronized的使用方法详解

    下面我将详细讲解 Java 中关键字 synchronized 的使用方法。 什么是 synchronized synchronized 是 Java 中的关键字,用来保证线程安全。 在 Java 中,当多个线程访问同一资源时,如果没有对这个资源加锁,可能会发生数据不一致的情况。而使用 synchronized 关键字可以保证在同一时刻只有一个线程可以访问一…

    Java 2023年5月26日
    00
  • 深入学习JavaScript执行上下文

    下面我将给出一份完整的攻略,帮助大家深入学习 JavaScript 执行上下文。 什么是执行上下文 在讲解如何深入学习 JavaScript 执行上下文之前,我们先来介绍一下什么是执行上下文。 在 JavaScript 中,每当代码运行到一个函数或者全局代码块的时候,都会创建一个执行上下文,用于存储当前代码执行的状态。执行上下文包含了当前环境中的变量、函数、…

    Java 2023年5月26日
    00
合作推广
合作推广
分享本页
返回顶部