Spring Boot 使用断言抛出自定义异常,优化异常处理机制

什么是断言?

实际上,断言(Assertion)是在Java 1.4 版本引入的特性,它为开发人员提供了一种简单的机制来进行代码预期条件的验证和调试。

Java 1.4 版本于2002年发布,它引入了许多新功能和改进,其中之一就是断言。断言的目的是在程序中进行可验证的内部检查,以确保代码的正确性和可靠性。

断言最早是在1989年由美国计算机科学家C.A.R. Hoare提出的概念,称为"断言语句"。然后,在Java 1.4版本中,Sun Microsystems将其引入到Java编程语言中,并添加了关键字assert用于表示断言。

使用assert关键字,开发人员可以在代码中编写断言语句,以检查程序的状态或条件是否满足预期。如果断言失败,会抛出一个AssertionError异常,提示开发人员该断言点存在问题。

例如org.springframework.util.Assert是Spring Framework中的一个工具类,用于进行参数校验和断言判断。它是一个包含静态方法的最终类。

org.springframework.util.Assert类中,提供了一系列静态方法用于进行断言操作,例如notNull()isTrue()hasText()等。这些方法主要用于验证方法参数或对象状态是否满足预期条件,如果不满足,则会抛出IllegalArgumentExceptionIllegalStateException等运行时异常。

常用的Assert方法源码如下:

//判断传入的表达式是否为true,如果为false,则抛出`IllegalArgumentException`异常,并使用指定的错误信息
public static void isTrue(boolean expression, String message) {
    
    
    if (!expression) {
    
    
        throw new IllegalArgumentException(message);
    }
}

//判断传入的对象是否为null,如果为null,则抛出`IllegalArgumentException`异常,并使用指定的错误信息
public static void isNull(@Nullable Object object, String message) {
    
    
    if (object != null) {
    
    
        throw new IllegalArgumentException(message);
    }
}

org.springframework.util.Assert类中提供的方法可以帮助我们在编码过程中进行快速的参数校验,避免使用繁琐的if语句或手动抛出异常。同时,这些方法也提供了可自定义的错误信息,有助于更好地理解校验失败的原因。

什么是异常?

异常(Exception)是程序运行时出现的错误或异常情况,它会打断程序正常的执行流程,并且可能导致程序崩溃或产生不可预期的结果。在Java中,异常通常被表示为一个继承自Throwable类的对象,例如RuntimeException、IllegalArgumentException等。

而自定义异常是指开发人员基于Java中的Exception或其子类,定义自己的异常类。通常情况下,自定义异常类用于表示特定的异常情况,以便更好地对程序的异常情况进行分类处理和管理。自定义异常类可以包含自己的属性和方法,以提供更详细的异常信息和处理方式。

需要自定义异常的原因有以下几点:

  1. 更细粒度的异常控制:Java标准库中的异常类型比较全面,但是在实际应用中,我们可能需要更具体的异常类型来描述某种特定场景下的异常情况。例如,我们可以自定义一个PayErrorException来表示支付异常,这样可以更好地区分不同类型的异常情况,并进行不同的处理。

  2. 信息更丰富:Java标准库中的异常类型通常只包含错误信息,如果需要传递更多的相关信息,就需要自定义异常类。例如,我们可以在自定义异常类中添加一些额外的属性,如异常码、请求参数等,以便更好地记录和跟踪异常信息。

  3. 更友好的异常提示:Java标准库中的异常信息通常比较晦涩难懂,如果需要更加友好和易于理解的提示信息,就需要自定义异常类。通过自定义异常类,开发人员可以提供更加直观和易懂的异常提示信息,以便用户或其他开发人员更好地理解异常情况。

在实际业务中,我们往往需要设置一个全局的异常处理器对异常进行统一的处理和管理,并返回相应的错误信息,确保应用程序的稳定性和可靠性。

基于断言实现的异常处理机制

创建自定义异常类

首先,需要创建一个自定义的异常类,继承自RuntimeException或其子类:

/**
 * 自定义通用异常类
 */
public class CommonException extends RuntimeException {
    
    

    /**
     * 无参构造方法
     */
    public CommonException() {
    
    
    }

    /**
     * 构造方法,传入异常信息
     * @param message 异常信息
     */
    public CommonException(String message) {
    
    
        super(message);
    }

    /**
     * 构造方法,传入异常信息和原始异常对象
     * @param message 异常信息
     * @param cause 原始异常对象
     */
    public CommonException(String message, Throwable cause) {
    
    
        super(message, cause);
    }

    /**
     * 构造方法,传入原始异常对象
     * @param cause 原始异常对象
     */
    public CommonException(Throwable cause) {
    
    
        super(cause);
    }
}

在自定义异常类CommonException中我们提供了几个不同的构造方法,方便在不同场景下创建异常对象。每个构造方法都调用了父类的构造方法super,以便初始化异常对象。

创建全局异常处理器

我们还需要创建一个全局异常处理器GlobalExceptionHandler,用于统一处理我们抛出的自定义异常类:

/**
 * 全局异常处理器
 */
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
    
    

    /**
     * 处理CommonException异常
     * @param e 异常对象
     * @return 异常信息
     */
    @ExceptionHandler(CommonException.class)
    public JSONObject handleCommonException(CommonException e) {
    
    
        log.error("错误信息:{}", e.getMessage());
        return JSON.parseObject(e.getMessage());
    }
}

在全局异常处理器GlobalExceptionHandler类上我们使用了@RestControllerAdvice注解标识该类为全局异常处理器。在该类中,定义了一个方法handleCommonException,用于处理异常捕获后的逻辑,在方法上添加了@ExceptionHandler()注解表示该方法处理指定自定义异常CommonException

创建自定义断言类

对于断言类,我们可以模仿org.springframework.util.Assert类,借鉴其中的方法,自定义一个抽象类MyAssert

/**
 * 断言类
 */
public abstract class MyAssert {
    
    

    /**
     * 判断对象是否为空,如果不为空则抛出CommonException异常
     *
     * @param object 要进行判断的对象
     */
    public void assertIsNull(@Nullable Object object) {
    
    
        // 如果对象不为空,则抛出CommonException异常,并将当前对象序列化为JSON字符串作为异常信息
        if (object != null) {
    
    
            throw new CommonException(JSON.toJSONString(this));
        }
    }

}

如上,我们创建了一个assertIsNull方法,参数object用于接收要进行判断的对象,如果object为空,则抛出CommonException异常,并将当前对象序列化为JSON字符串作为异常信息。

创建响应码类

创建自定义的响应码类,同时继承断言类MyAssert,这样我们可以通过响应码直接调用断言类中的方法进行判断:

/**
 * 响应码类,继承自断言类MyAssert
 */
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class ResCode extends MyAssert {
    
    

    /**
     * 响应码
     */
    private int code;

    /**
     * 响应消息
     */
    private String message;

}

上述代码中,使用了Lombok库的注解来简化代码编写。具体注解的含义如下:

  • @Getter: 自动生成属性的getter方法。
  • @NoArgsConstructor: 自动生成无参构造方法。
  • @AllArgsConstructor: 自动生成包含所有参数的构造方法。

创建工具类

ResCodeUtils工具类用于调用响应码进行校验,抛出自定义异常并返回指定错误信息:

/**
 * 响应码工具类,继承自响应码类ResCode
 */
public class ResCodeUtils extends ResCode {
    
    

    /**
     * 响应码naa,表示错误
     */
    public static ResCode naa = new ResCode(300, "错误");

    /**
     * 响应码naa1,表示naa1错误
     */
    public static ResCode naa1 = new ResCode(4044, "naa1错误");

}

ResCodeUtils类中提供了两个静态属性naanaa1,分别表示响应码为300和4044的错误类型,方便通过类名.的方式直接调用。

测试效果

  1. 编写TestController类,添加@RestController注解表明该类是一个控制器类,用于处理HTTP请求并返回响应:

    @RestController
    public class TestController {
          
          
    
        @RequestMapping("/send1")
        public String send1() {
          
          
            ResCodeUtils.naa.assertIsNull(null);
            return "success";
        }
    
        @RequestMapping("/send2")
        public String send2() {
          
          
            ResCodeUtils.naa1.assertIsNull(new ResCode());
            return "success";
        }
    
    }
    

    TestController类中,定义了两个请求处理方法send1()send2()。这两个方法使用了@RequestMapping注解来指定它们对应的URL路径。

    • send1()方法中调用了ResCodeUtils.naa.assertIsNull(null),这表示对naa响应码进行断言,判断传入的参数是否为null。如果参数为null,断言通过,否则会抛出异常。

    • send2()方法中调用了ResCodeUtils.naa1.assertIsNull(new ResCode()),这表示对naa1响应码进行断言,判断传入的参数是否为null。由于参数new ResCode()不为null,所以这里会抛出异常。

    无论是send1()还是send2()方法,最终都会返回字符串"success"作为处理结果。

  2. 启动项目,使用 ApiFox 访问对应接口。

    • 访问send1(),由于参数为null,断言通过,返回字符串"success":

      image-20231101211357371

    • 访问send2(),由于参数为new ResCode(),不为null,断言未通过,抛出自定义异常之后被全局异常处理器GlobalExceptionHandler捕获,返回对应错误信息:

      image-20231101211431765

      同时,可以在控制台看到打印的日志信息

      image-20231101211511456


本文到此结束,感谢您的阅读,希望对您有所帮助!!!

猜你喜欢

转载自blog.csdn.net/qq_20185737/article/details/134171419