手记

测试开发专题:spring-boot统一异常捕获

java异常介绍

异常时相对于return的一种退出机制可以由系统触发也可由程序通过throw语句触发异常可以通过try/catch语句进行捕获并处理如果没有捕获则会导致程序退出并输出异常栈信息异常有不同的类型所有异常类都有一个共同的父类Throwable下面我们先从Throwable开始介绍。

Throwable

Throwable是所有异常类的父类有四个构造方法

public Throwable(Throwable cause)
public Throwable(String message, Throwable cause) 
public Throwable(String message)
public Throwable()

主要有两个类一个是message表示异常的消息一个是cause表示触发该异常的其他异常异常可以形成一个异常链上层的异常由底层到的异常触发cause表示底层异常。

异常体系

java定义了非常多的异常类来表示各种类型的异常下面的图示是部分的异常类

Throwable是所有异常类的基类它有两个子类Error和Exception。

  • Error错误

操作系统或者虚拟机发生的错误这个时候程序是跑不起来的代码是无法处理的一般表示系统错误或者资源耗尽由java系统自己处理比如图示中给出的虚拟机错误、栈溢出、内存溢出等错误

  • Exception

表示应用程序错误是可以通过代码处理的有两大类一类是受检异常(checked exception)一类是非受检异常(uncheck exception)也就是runtime异常他两的区别就在于java是如何对待他们的受检异常java会要求强制进行处理不然编译时不通过的对于非受检异常则没有这个强制性的要求。

解释

该怎么理解受检异常和非受检异常呢我的理解就是做某件事的时候我们能够顺利的按照我们预期的做完但是实际上呢可能会出现各种各样的情况这种可能出现的情况就把他称之为异常这种异常有的我们能处理有的我们不能处理能处理的就把它称作为受检异常不能处理的称作为非受检异常。

比如说客户端发送一个请求要查询数据库那有可能找到有可能没找到没找到的话就应该抛出runtime异常再比如要去读取文件文件可能是不存在的那就应该抛出checked exception这其实就是bug我们应该去处理的。

怎么理解呢比如读取文件当文件不存在发生异常能处理么当然可以处理啊怎么处理把文件路径改成正确的不就行了。从某种意义上来说checked异常是真正的bug

没有办法处理的情况比如说用户输入ID为2查询记录能找到但是如果输入2000就找不到了这里的找不到就是一种异常情况就需要产生一个运行时异常也就是非受检异常。

已知异常和未知异常

上面我们提到的受检异常 unchecked exception和运行时异常 RuntimeException都是从java语法层面来说的那从程序开发者的角度来说分为两类已知异常和未知异常。

已知异常和未知异常其实就在于我们程序员是否主动的去处理异常。

  • 未知异常当代码中未对一些情况做出处理而引发的异常这就是未知异常一般这种异常都是因为服务端的代码写的有问题的对于前段开发或者用户都是无意义的记录日志供自己查看就行了。
  • 已知异常更多的时候表示的是一种消息用来提示用户的输入是否有问题只不过这里我们用异常的形式来处理当然也可以通过其他方式处理比如当做结果去返回也是可以的但是这种方式不如全部都看做异常通过全局的异常处理器直接处理来的简单。

Spring-Boot全局异常处理器

在使用spring-boot进行开发到的时候有时我们需要对程序中抛出的异常进行统一的处理spring-boot预留了响应的扩展点我们只需要按照要求的方式去自定义自己的实现即可。

@ControllerAdvice
public class GlobalExceptionAdvice {

    @ExceptionHandler(value = Exception.class)
    public void handleHttpException(HttpServletRequest req, Exception ex){
  
    }
}

@ControllerAdvice表明GlobalExceptionAdvice是一个异常的处理类具体的处理方法通过 @ExceptionHandler进行标记通过value来指定能够处理的异常类型。异常的处理方法需要传入两个参数一个是HttpServletRequest请求对象可以从这里获取请求相关的一些信息比如请求的url或者参数等另一个是Exception就是抛出的异常。

模拟异常发生

我们在异常的处理方法中打印一条语句

@ControllerAdvice
public class GlobalExceptionAdvice {

    @ExceptionHandler(value = Exception.class)
    public void handleHttpException(HttpServletRequest req, Exception ex){
        System.out.println("发生异常了");
    }
}

然后再Controller里抛出一个异常

@RestController
public class BannerController {

    @RequestMapping(value = "/v2/banner", method = {RequestMethod.GET})
    public String test() throws Exception {
        throw new Exception("我抛出来的");
    }
}

启动程序后在浏览器中访问路由可以看到在控制台中打印出了预期的信息

当然在实际开发中我们可能要多异常进行区分该抛出什么类型的异常异常处理函数可也不只一个还要对返回的异常信息进行自定义。

自定义异常类

当我们需要自定义异常类的时候是该继承自exception呢还是继承RuntimeException?下面我们就来讨论一下。

当用户访问一个不存在的资源的时候很明显这种情况我们是处理不了的而且这种情况我们是可以判断出来的所以这里应该使用Runtime异常。

HttpException是所有自定义运行时异常类的基类这里定义两个状态码一个code是我们业务层面的定义一个httpStatusCode是http请求的资源不存在的异常定义为 NotFoundException

在controller里面抛出这个异常

那之前定义的 GlobalExceptionAdvice能够监听到这个异常呢

当然能NotFoundExceptionHttpException的子类HttpExceptionRuntimeException的子类RuntimeException又是 Exception的子类@ExceptionHandler(value = Exception.class)全局异常处理器里指定的能够处理的异常类是 Exception自然也能处理 NotFoundException

但是这里也有不同的地方我们自定义的 HttpException是多了两个扩展字段的codehttpStatusCode所以在全局异常处理器里需要对异常的类型进行判断如果是自定义的就需要添加这两个字段。

这里呢有两种方式进行处理一种是在之前的异常处理方法里面进行类型判断另一种呢是可以再添加一个异常处理方法专门处理自定义异常类

这里我们可以考虑一下当我们抛出 NotFoundException异常的时候是会进入哪一个异常处理方法里还是说两个都进呢

我们可以来试验一下启动程序后看看在控制台打印的是什么

在浏览器里访问路由以后控制台显示了handleHttpException异常处理方法里到的打印语句输出的内容。

可以看到多个异常处理方法时可以同时存在的各自处理自己所能处理的异常类。

下篇文章我们来探讨一下如何自定义异常的返回信息敬请关注博客原文

0人推荐
随时随地看视频
慕课网APP