springboot全局异常统一处理

异常的定义

异常就是有异于常态,和正常情况不一样,有错误出错。在java中,阻止当前方法或作用域的情况,称之为异常。

异常的继承关系

在这里插入图片描述
Throwable:有两个重要的子类:Exception(异常)和Error(错误),两者都包含了大量的异常处理类。

1、Error(错误):是程序中无法处理的错误,表示运行应用程序中出现了严重的错误。此类错误一般表示代码运行时JVM出现问题。通常有Virtual MachineError(虚拟机运行错误)、NoClassDefFoundError(类定义错误)等。比如说当jvm耗完可用内存时,将出现OutOfMemoryError。此类错误发生时,JVM将终止线程。

这些错误是不可查的,非代码性错误。因此,当此类错误发生时,应用不应该去处理此类错误。

2、Exception(异常):程序本身可以捕获并且可以处理的异常。

Exception这种异常又分为两类:运行时异常和编译异常。

1、运行时异常(不受检异常):RuntimeException类极其子类表示JVM在运行期间可能出现的错误。比如说试图使用空值对象的引用(NullPointerException)、数组下标越界(ArrayIndexOutBoundException)。此类异常属于不可查异常,一般是由程序逻辑错误引起的,在程序中可以选择捕获处理,也可以不处理。

2、编译异常(受检异常):Exception中除RuntimeException极其子类之外的异常。如果程序中出现此类异常,比如说IOException,必须对该异常进行处理,否则编译不通过。在程序中,通常不会自定义该类异常,而是直接使用系统提供的异常类。

可查异常与不可查异常: java的所有异常可以分为可查异常(checked exception)和不可查异常(unchecked exception)。

1、可查异常: 编译器要求必须处理的异常。正确的程序在运行过程中,经常容易出现的、符合预期的异常情况。一旦发生此类异常,就必须采用某种方式进行处理。除RuntimeException及其子类外,其他的Exception异常都属于可查异常。编译器会检查此类异常,也就是说当编译器检查到应用中的某处可能会此类异常时,将会提示你处理本异常——要么使用try-catch捕获,要么使用throws语句抛出,否则编译不通过。

2、不可查异常:编译器不会进行检查并且不要求必须处理的异常,也就说当程序中出现此类异常时,即使我们没有try-catch捕获它,也没有使用throws抛出该异常,编译也会正常通过。该类异常包括运行时异常(RuntimeException极其子类)和错误(Error)。

Java异常的捕获与处理

一、异常处理的使用

Java提供了try(尝试)、catch(捕捉)、finally(最终)这三个关键字来处理异常。在处理各种异常时,需要用到对应的异常类,指的是由程序抛出的对象所属的类。

由于finally块是可以省略的,异常处理格式可以分为三类:try{ }——catch{ }、try{ }——catch{ }——finally{ }、try{ }——finally{ }。

public class DealException
{
    public static void main(String args[])
    {
        try
        //要检查的程序语句
        {
            int a[] = new int[5];
            a[10] = 7;//出现异常
        }
        catch(ArrayIndexOutOfBoundsException ex)
        //异常发生时的处理语句
        {
            System.out.println("超出数组范围!");
        }
        finally
        //这个代码块一定会被执行
        {
            System.out.println("*****");
        }
        System.out.println("异常处理结束!");
    }
}

可以看出,在异常捕捉的过程中要进行两个判断,第一是try程序块是否有异常产生,第二是产生的异常是否和catch()括号内想要捕捉的异常相同。

那么,如果出现的异常和catch()内想要捕捉的异常不相同时怎么办呢?事实上我们可以在一个try语句后跟上多个异常处理catch语句,来处理多种不同类型的异常。

public class DealException
{
    public static void main(String args[])
    {
        try
        //要检查的程序语句
        {
            int a[] = new int[5];
            a[0] = 3;
            a[1] = 1;
            //a[1] = 0;//除数为0异常
            //a[10] = 7;//数组下标越界异常
            int result = a[0]/a[1];
            System.out.println(result);
        }
        catch(ArrayIndexOutOfBoundsException ex)
        //异常发生时的处理语句
        {
            System.out.println("数组越界异常");
            ex.printStackTrace();//显示异常的堆栈跟踪信息
        }
        catch(ArithmeticException ex)
        {
            System.out.println("算术运算异常");
            ex.printStackTrace();
        }
        finally
        //这个代码块一定会被执行
        {
            System.out.println("finally语句不论是否有异常都会被执行。");
        }
        System.out.println("异常处理结束!");
    }
}

上述例子中ex.printStackTrace();就是对异常类对象ex的使用,输出了详细的异常堆栈跟踪信息,包括异常的类型,异常发生在哪个包、哪个类、哪个方法以及异常发生的行号。

注意
异常如果不做处理,那么代码中一旦发生异常错误那么访问接口时会直接报错
如果我们做了try(尝试)、catch(捕捉)那么try块外面的信息还是能正常执行的,也就是说我们访问接口程序会打印错误信息,但是接口不会报错

package com.guojianquan.client;

import com.alibaba.fastjson.JSONObject;
import com.aliyuncs.http.HttpRequest;
import com.aliyuncs.http.HttpResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @ClassName TestController
 * @Description TODO
 * @Author sgq
 * @Date 2021/4/10 15:07
 * @Version 1.0
 **/
@Controller
@RequestMapping("test")
public class TestController {
    @RequestMapping("/go")
    @ResponseBody
    public JSONObject test(HttpServletRequest request, HttpServletResponse response){
        JSONObject json = new JSONObject();
        int b = 10;
        int a=0;
        int c=0;
        try {
            c=b/a;
            a = 10;
        } catch (Exception e) {
            e.printStackTrace();
        }
        a=12;
        json.put("b",b);
        json.put("a",a);
        return json;
    }
}

try catch能捕获任何异常!

二、throws关键字

throws声明的方法表示该方法不处理异常,而由系统自动将所捕获的异常信息抛给上级调用方法。

public class throwsDemo
{
    public static void main(String[] args)
    {
        int[] a = new int[5];
        try
        {
            setZero(a,10);
        }
        catch(ArrayIndexOutOfBoundsException ex)
        {
            System.out.println("数组越界错误!");
            System.out.println("异常:"+ex);
        }
        System.out.println("main()方法结束。");
    }
    private static void setZero(int[] a,int index) throws ArrayIndexOutOfBoundsException
    {
        a[index] = 0;
    }
}

throws关键字抛出异常,“ArrayIndexOutOfBoundsException”表明setZero()方法可能存在的异常类型,一旦方法出现异常,setZero()方法自己并不处理,而是将异常提交给它的上级调用者main()方法。

三、throw关键字

throw的作用是手工抛出异常类的实例化对象。
throw用在方法内,用来抛出一个异常对象,将这个异常对象传递到调用者处,并结 束当前方法的执行。(没有try catch的情况下)
使用的格式:
throw new 异常类名(参数);

public class throwDemo
{
    public static void main(String[] args)
    {
        try
        {
            //抛出异常的实例化对象
            throw new ArrayIndexOutOfBoundsException("\n个性化异常信息:\n数组下标越界");
        }
        catch(ArrayIndexOutOfBoundsException ex)
        {
            System.out.println(ex);
        }
    }
}

我们能发现,throw好像属于没事找事,引发运行期异常,并自定义提示信息。事实上,throw通常和throws联合使用,抛出的是程序中已经产生的异常类实例

1 public class ExceptionDemo
 2 {
 3     public static void main(String[] args) 
 4     {
 5         int[] a = new int[5];
 6         try
 7         {
 8             setZero(a,10);
 9         }
10         catch(ArrayIndexOutOfBoundsException e)
11         {
12             System.out.println("异常:"+e);
13         }
14         System.out.println("main()方法结束!");
15     }
16     public static void setZero(int[] a,int index) throws ArrayIndexOutOfBoundsException
17     {
18         System.out.println("setZero方法开始:");
19         try
20         {
21             a[index] = 0;
22         }
23         catch(ArrayIndexOutOfBoundsException ex)
24         {
25             throw ex;
26         }
27         finally
28         {
29             System.out.println("setZero方法结束。");
30         }
31     }
32 }

输出结果:

setZero方法开始:
setZero方法结束。
异常:java.lang.ArrayIndexOutOfBoundsException: 10
main()方法结束!

四、RuntimeException类

Exception和RuntimeException的区别:
Exception:强制性要求用户必须处理;
RunException:是Exception的子类,由用户选择是否进行处理。

五、自定义异常类

自定义异常类继承自Exception类,可以使用父类的大量的方法,也可自己编写方法来处理特定的事件。Java提供用继承的方式运行用户自己编写的异常类。

class MyException extends Exception
{
    public MyException(String message)
    {
        super(message);
    }
}
public class DefinedException
{
    public static void main(String[] args)
    {
        try
        {
            throw new MyException("\n自定义异常类!");
        }
        catch(MyException e)
        {
            System.out.println(e);
        }
    }
}

结果:
com.guojianquan.DefinedException.MyException: 
自定义异常类!

自定义异常的应用

为什么要使用自定义异常

1.我们在工作的时候,项目是分模块或者分功能开发的,基本不会是一个人开发一整个项目,使用自定义异常类就统一了对外异常展示的方式。

2.有时候我们遇到某些校验或者问题的时候,需要直接结束掉当前的请求,这时便可以通过抛出自定义异常来结束。如果你的项目中使用了SpringMVC比较新的版本的话有控制器增强,可以通过@ControllerAdvice注解写一个控制器增强类来拦截自定义的异常并响应给前端响应的信息。

3.自定义异常可以在我们的项目中某些特殊的业务逻辑时抛出异常。

4.使用自定义异常继承相关的异常来抛出处理后的异常信息,可以隐藏底层的异常。这样更安全,异常信息也会更加直观。自定义异常可以抛出我们自己想要抛出的异常,可以通过抛出的信息区分异常发生的位置,根据异常名我们就可以知道哪里有异常,根据异常提示信息对程序进行修改。比如空指针异常NullPointException,我们可以抛出信息为"XXX为空"的定位异常位置,而不用输出堆栈信息(默认异常抛出的信息)。

自定义异常的缺点

毋庸置疑,我们不能期待JVM自动抛出一个自定义异常,也不能期待JVM会自动处理一个自定义异常。发现异常、抛出异常和处理异常的工作必须靠编程人员在代码中利用异常处理机制自己完成。这样就相应地增加了一些开发成本和工作量,所以项目没必要的话,也不一定非得要用上自定义异常,要能够自己去权衡。

自定义异常的规则

在Java中可以自定义异常,编写自己的异常类时需要记住以下几点:

1.所有异常都必须是Throwable的子类。

2.如果希望写一个检查异常类,则需要继承Exception类。

3.如果希望写一个运行时异常类,则需要继承RuntimeException类。

springboot中捕获全局异常的实现方式

日常开发过程中,难免有的程序会因为某些原因抛出异常,而这些异常一般都是利用try ,catch的方式处理异常或者throw,throws的方式抛出异常不管。这种方法对于程序员来说处理也比较麻烦,对客户来说也不太友好,所以我们希望既能方便程序员编写代码,不用过多的自己去处理各种异常编写重复的代码又能提升用户的体验,这时候全局异常处理就显得很重要也很便捷了,是一种不错的选择。

第一种使用ErrorController类来实现

非springboot项目中错误页面都是定义在web.xml中

<error-page> <!--当系统出现404错误,跳转到页面nopage.html-->
    <error-code>404</error-code>
    <location>/nopage.html</location>
  </error-page>
  <error-page> <!--当系统出现java.lang.NullPointerException,跳转到页面error.html-->
    <exception-type>java.lang.NullPointerException</exception-type>
    <location>/error.html</location>
  </error-page>

springboot中没有xml配置自然也就不能配置404 405 错误相应的页面给用户一个友好的提示,所以它提供一个ErrorController异常捕获接口,我们需要实现ErrorController接口,重写handleError方法。

org.springframework.boot.web.servlet.error.ErrorController  springboot异常捕获处理类
package com.guojianquan.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


/**
 * @ClassName BasicErrorController
 * @Description 404等错误拦截
 * @Author ywl
 * @Date 2020-07-16 16:40
 * @Version 1.0
 **/
@Controller
public class BasicErrorController implements ErrorController {

    private static final Logger log = LoggerFactory.getLogger(BasicErrorController.class);
    private static final String ERROR_PATH = "/error";

    @RequestMapping(value=ERROR_PATH)
    public String handleError(HttpServletRequest request, HttpServletResponse response){
        int status = response.getStatus();
        log.info("拦截错误码为:"+status);
        if(status == 400){
            return "system/400error";
        }
        if(status == 404){
            return "system/404error";
        }
        if(status == 500){
            return "system/500error";
        }
        return "system/error";
    }

    @Override
    public String getErrorPath() {
        return ERROR_PATH;
    }
}

注意: getErrorPath()返回的路径,服务器将会重定向到该路径对应的处理类,本例中为error方法。

第二种使用@RestControllerAdvice和@ExceptionHandler注解

Spring在3.2版本增加了一个注解@ControllerAdvice或者@RestControllerAdvice,可以与@ExceptionHandler
注解@ControllerAdvice出现了,简单的说,该注解可以把异常处理器应用到所有控制器,而不是单个控制器。借助该注解,我们可以实现:在独立的某个地方,比如单独一个类,定义一套对各种异常的处理机制,然后在类的签名加上注解@ControllerAdvice,统一对 不同阶段的、不同异常 进行处理。这就是统一异常处理的原理。
注意到上面对异常按阶段进行分类,大体可以分成:进入Controller前的异常 和 Service层异常,具体可以参考下图:
在这里插入图片描述
目标
消灭95%以上的 try catch 代码块,以优雅的 Assert(断言) 方式来校验业务的异常情况,只关注业务逻辑,而不用花费大量精力写冗余的 try catch 代码块。
== 注解@ControllerAdvice表示这是一个控制器增强类,当控制器发生异常且符合类中定义的拦截异常类,将会被拦截。
@ControllerAdvice注解以及 @ExceptionHandler注解前者是用来开启全局的异常捕获,后者则是说明捕获哪些异常,对那些异常进行处理。==


------------------------全局捕获异常控制器增强类----------------------------------
package com.guojianquanapp.config;
import com.guojianquanapp.pojo.CommonResponse;
import com.guojianquanapp.pojo.CustomException;
import com.guojianquanapp.pojo.ReturnCode;
import org.springframework.validation.BindException;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.validation.ConstraintViolationException;
import java.util.ArrayList;
import java.util.List;

/**
 * @ClassName ExceptionControllerAdvice
 * @Description 统一异常捕获
 * @Author 
 * @Date 2021-04-06 11:06
 * @Version 1.0
 **/
@RestControllerAdvice
public class ExceptionControllerAdvice {

    /**
     * @Author 
     * @Description 全局异常处理捕获参数不合法异常
     * @Date 2021-04-07 15:59
     * @Param [e]
     * @return com.guojianquanapp.pojo.CommonResponse
    **/
    @ExceptionHandler(BindException.class)
    public CommonResponse BindException(BindException e) {
        List<String> list = new ArrayList<>();
        List<ObjectError> allErrors = e.getBindingResult().getAllErrors();
        for (ObjectError error: allErrors){
            list.add(error.getDefaultMessage());
        }
        return new CommonResponse<>(ReturnCode.CHECKED_PARAM_FAILED, list);
    }


    /**
     * @MethodName: ExceptionHandler
     * @Params: [e]
     * @return: show.mrkay.response.CommonResponse<java.lang.String>
     * @Description: 空参异常捕获
     * @Author: MrKay
     * @Date: 2020/7/19
     */
    @ExceptionHandler(ConstraintViolationException.class)
    public CommonResponse<String> ExceptionHandler(ConstraintViolationException e) {
        return new CommonResponse<>(ReturnCode.CHECKED_PARAM_FAILED, e.getMessage());
    }


    /**
     * @MethodName: ExceptionHandler
     * @Params: [e]
     * @return: show.mrkay.response.CommonResponse
     * @Description: 其他未知异常
     * @Author: MrKay
     * @Date: 2020/7/19
     */
    @ExceptionHandler(Exception.class)
    public CommonResponse ExceptionHandler(Exception e) {
        return new CommonResponse<>(ReturnCode.INTERNAL_SERVER_ERROR, e.getMessage());
    }

    /**
     * @MethodName: CustomExceptionHandler
     * @Params: [ce]
     * @return: show.mrkay.response.CommonResponse<java.lang.String>
     * @Description: 自定义异常处理
     * @Author: MrKay
     * @Date: 2020/7/12
     */
    @ExceptionHandler(CustomException.class)
    public CommonResponse<String> CustomExceptionHandler(CustomException ce) {
        return new CommonResponse<>(ce.getErrorCode(), ce.getErrorMsg());
    }

}
说明:在这里我随便创建一个类加上@RestControllerAdvice注解 代表全局捕获
我使用的是@RestControllerAdvice而非@ControllerAdvice目的是为了给接口返回json数据,springboot集成thymeleaf只要是没有@ResponseBody注解返回都默认是模板页面
@ExceptionHandler(BindException.class)表示异常的捕获类型,只要出现异常的类型和BindException.class匹配那么就会执行里面的方法
------------------------全局捕获异常控制器增强类----------------------------------
---------------------------统一结果返回 定义错误代码与提示的枚举类-------------------------------
public enum ReturnCode {
    SUCCESS(200, "操作成功"),
    CHECKED_PARAM_FAILED(400, "请求参数校验失败"),
    INTERNAL_SERVER_ERROR(500, "操作失败");

    private int code;
    private String msg;

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

    public int getCode() {
        return code;
    }

    public String getMsg() {
        return msg;
    }
}
说明:用枚举类型主要是方便定义维护起来方便.新创建一种错误码只需添加一个成员变量即可
---------------------------定义错误代码与提示的枚举类-------------------------------
---------------------------统一放回数据格式泛型类-------------------------------
public class CommonResponse<T> {
    @ApiModelProperty(value = "响应码")
    private int code;
    @ApiModelProperty(value = "响应信息")
    private String msg;
    @ApiModelProperty(value = "数据体")
    private T data;

    //成功响应构造
    public CommonResponse(T data) {
        this(ReturnCode.SUCCESS, data);
    }

    //全参构造
    public CommonResponse(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    //全参构造
    public CommonResponse(ReturnCode returnCode, T data) {
        this.code = returnCode.getCode();
        this.msg = returnCode.getMsg();
        this.data = data;
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }
}
---------------------------统一放回数据格式泛型类-------------------------------
说明: 一般接口返回数据都有code msg 与data 这里我们直接写成一个公共的泛型类型来接收返回数据

以上程序是凡是通过控制器的请求的方式出现的各种异常按照控制器增强异常类型的处理捕获。
@RestControllerAdvice 方式表示我获取异常处理后统一返回一个json格式的数据,也就是给接口或者ajax访问是应用的
想要做全局统一的异常捕获机制需要我们创建一个公共数据返回泛型类 一个写各种错误响应码的枚举类 一个单独的添加@RestControllerAdvice @ExceptionHandler 注解的全局统捕获异常的处理类 一个自定义运行时异常类

两种方法的区别

1.注解@ControllerAdvice方式只能处理访问控制器抛出的异常。此时请求已经进入控制器中。
2.类ErrorController方式可以处理所有的异常,包括未进入控制器的错误,比如404,401等错误
3.如果应用中两者共同存在,则@ControllerAdvice方式处理控制器抛出的异常,类ErrorController方式未进入控制器的异常。
4二者可以兼容,凡是404不存在的路径访问都能被ErrorController获取到,如果访问的是控制器存在的路径即使请求方式get post不对,也算是访问控制器异常,会被@ControllerAdvice方式捕捉
5.@ControllerAdvice方式可以定义多个拦截方法,拦截不同的异常类,并且可以获取抛出的异常信息,自由度更大。
6 程序中tey catch 捕捉到的异常算是抛出异常所以不能被全局异常捕获到,而自定义异常的出现就是为了在catch中抛出异常让@ControllerAdvice方式捕捉
7控制器通知还有一个兄弟,@RestControllerAdvice,如果用了它,错误处理方法的返回值不会表示用的哪个视图,而是会作为HTTP body处理,即相当于错误处理方法加了@ResponseBody注解。
8使用注解方式全局统一在接口或者前后端分离的项目中应用较广可以返回统一的数据格式,项目中带模板h5页面的不是太友好,当然我们可以设着全局统一处理器返回各种页面只不过很少这样去做
9如果项目中既有接口数据又有大量的为前后端分类的后台页面,那么就容易出现混乱,在通过控制器返回一个页面的过程中出现错误可能会被全局异常拦截而返回json数据

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值