Home Tags Posts tagged with "异常"

异常

有人说,防止NullPointerException,是程序员的基本修养。

此篇博文,将介绍空指针的相关知识,一起往下看看吧~

 

小杨是一名初入职场的程序员,他很喜欢写代码。每次产品需要他实现的功能,他都能做出来,可当他信心满满的把代码交给组里前辈CR的时候,前辈总会语重心长的告诉他:

小杨, 你这代码用不了啊,可能会报空指针异常,诺,就是这个

此时,小杨还没意识到事情的严重性,他想:这太正常了,谁写个代码还不会出错呢,修复就行了

public class InterToInt{
    public static void main(String[] args) {
        Person person = new Person();
        person.setName("张三");
        Integer count = person.getCount();
        if (count == 0) {
            System.out.println("该变量未填写count");
        }
        // 装箱操作 int -> Integer
        int i = 5;
        Integer integer = new Integer(i);

        // 拆箱操作:Integer -> int
        int num = integer.intValue();
    }
}

上述代码,究竟为什么会出现空指针异常呢?
经过调试,同时结合报错信息可以判断,问题出在if(count == 0)这条语句上,仔细看,我们并没有对person对象的count属性进行赋值,难道是因为count默认值为null,才报的空指针异常吗?

由上图可知,Integer类型的默认值确实为Null,好,那我们给count赋值,这样的话就不会报空指针了吧

果然,没有报空指针错误了,难道问题到这里就解决了吗?未必
在开发中,有时候对于一个属性,我们不需要给其赋值,它也不需要默认值,它就需要用Null来表示。阿里巴巴开发手册规定所有的属性必须用Integer来定义而非int
借助上例,如果我们从库表里面查询一个person对象xiaohuang,xiaohuang的count属性正好是Null,难道我们能给它随便赋一个值吗?显然不能
那我们怎么解决空指针的问题呢?
public class InterToInt{
    public static void main(String[] args) {
        Person person = new Person();
        person.setName("张三");
        Integer count = person.getCount();
        /**拆箱的时候实际上是调用了intValue()方法。
         * Integer类型和int作比较会自动拆箱,由于Interger类型的对象是null,
         * 这时候自动拆箱调用intValue()方法就会报NullPointerException。
         * 所以基本类型和对应包装类作比较时要判断包装类是否有可能为null
         * 不然就会出现这种错误
         */
        if (count != null && 1 == count) { //改写这一句即可
            System.out.println("count == 0");
        }
        // 装箱操作 int -> Integer
        int i = 5;
        Integer integer = new Integer(i);

        // 拆箱操作:Integer -> int
        int num = integer.intValue();
    }
}

到这里,是不是似懂非懂,若有若无?如果你以为你每次在使用count这样的属性前进行非空判断就能避免所有的空指针的话,那就太天真了,空指针要比这狡猾的多,接下来我们一起去揭开空指针的神奇面纱吧~

一、Java中,什么是指针?

Java中究竟有没有指针?你可以就这个问题好好探索下,此处不作深究,本文中的指针可以理解为指针对象

String str = new String(“str是指针对象”);

如上,str就是一个指针,它被存放在Jvm的栈区;留几个问题给你思考吧~ 1.new String(“str是指针对象”)的空间是什么时候分配的? 2.new String(“str是指针对象”)存储在Jvm的哪一块区域?

 

二、什么是空指针?

当指针不指向任何内存地址时,就叫做空指针,例如:int[] array = null

 

三、什么叫空指针异常?

就是一个指针不指向任何内存地址,但是你还调用他了,例如:

int[] array = null;
System.out.println(array[0]);
这个时候原本array数组是个空指针,没有创建新的对象,在调用这个数组的时候就会产生空指针异常的错误!
程序运行会显示Exception in thread “main” java.lang.NullPointerException的异常提示

四、为什么会产生空指针异常呢?

这里我们用上面举的例子进行说明,int[] array = null在内存中的栈内存中创建了一个叫做array的变量,而堆内存中并没有开辟int类型的数组空间,所以在栈内存中的这个array变量没有存放任何内存地址,由此我们可以理解为什么会产生空指针异常,调用没有的东西显然时不可以的。(栈区和堆区之间存放的东西有什么关系呢?详情可在本博文搜索Jvm、栈、堆等关键词)

 

四、NullPointerException以及其产生的场景

1.产生NullPointerException的场景

Java中定义:在应用程序中尝试使用null时会抛出异常。

其中以下的情况会产生NullPointerException

  1. 调用 null 对象的实例方法。
  2. 访问或修改 null 对象的字段。
  3. 将 null 作为一个数组,获得其长度。
  4. 将 null 作为一个数组,访问或修改其时间片。
  5. 将 null 作为 Throwable 值抛出。

 

2.怎么产生的?

《阿里巴巴开发手册》中提到,

1)返回类型为基本数据类型,return 包装数据类型的对象时,自动拆箱有可能产生 NPE。反例:public int f() { return Integer 对象}, 如果为 null,自动解箱抛 NPE。

2) 数据库的查询结果可能为 null。

3) 集合里的元素即使 isNotEmpty,取出的数据元素也可能为 null。

4) 远程调用返回对象时,一律要求进行空指针判断,防止 NPE。

5) 对于 Session 中获取的数据,建议 NPE 检查,避免空指针。

6) 级联调用 obj.getA().getB().getC();一连串调用,易产生 NPE。正例:使用 JDK8 的 Optional 类来防止 NPE 问题。

 

3.怎么预防NPE?

1) 对象防止,直接!=null

2)集合类判空:一般采用!=null&&判断size(),或者调用isEmpty()方法,或者用Collection工具类判空,java8种Optional类

3)字符串判空:需要判断是否==null&&””.equals(str)来判断,或者StringUtils工具类判断

4)另外项目中要对所有前台参数,对象判空,数据库查询语句判空,JSON对象,JSON数组判空,get()后的值判空

5)对于级联调用 obj.getA().getB().getC(),可以使用Optional类

6)主动进行参数检查,对方法中传入的参数进行检验

7)在已知字符串上使用equals(),equalsIgnoreCase()等方法

8)尽量避免方法中返回null。一些返回数组或者List的方法,如果没有值,尽量返回空集合,避免返回null


总结

记住一句话:避免空指针异常的最好的方法就是总是检查哪些不是自己创建的对象。


补充

针对第一点,我们进行实验

定义Person类

定义测试类

我们对count进行了非空判断,可它还是报了空指针异常,错误信息帮我们定位到第25行

public int getCount(){
    return this.count;
}

这是因为 
返回类型为基本数据类型,return 包装数据类型的对象时,自动拆箱有可能产生 NPE。反例:public int f() { return Integer 对象}, 如果为 null,自动解箱抛 NPE。
拆箱的时候实际上是调用了intValue()方法。Integer类型和int作比较会自动拆箱,由于Interger类型的对象是null,这时候自动拆箱调用intValue()方法就会报NullPointerException。
所以要判断包装类是否有可能为null,不然就会出现这种错误
class VerdictInter{
    public static void main(String[] args) {
        Person person = new Person();
        person.setName("张三");
        person.setCount(null);
        Integer count = person.getCount();
        int res = (count == null) ? 0:count;
        if (res == 0) {
            System.out.println("该变量未填写count");
        }
    }
}


参考资料:
防止NullPointerException,是程序员的基本修养 - 知乎 (zhihu.com)

空指针异常--java.lang.NullPointerException - 云+社区 - 腾讯云 (tencent.com)

(36条消息) java中什么是空指针异常以及为什么会产生空指针异常_imagpie的博客-CSDN博客_为什么会出现空指针异常

0 97

项目中使用到了往Redis中存入消息,格式:<发送者名称,消息内容>

本来做的是每次往Redis中插入 一个消息对象,而这样的后果就是后写入的消息会覆盖前面的消息

 

比如第一条消息是 <橙汁,“你好”>

第二条消息是<橙汁,“嘿嘿嘿”>

这样的话只存入了第二条,第一条消息就丢失了,很明显这样不行

因此准备使用 存入消息列表的方式

例如<橙汁,<消息一,消息二…>>

 

因为每个客户端有一个对应的 readThread,而这个列表是在线程中,与其他的用户线程没用交集,所以我认为是线程安全的

而 Redis 写入也没有问题,但当我准备从 Redis 中取出数据的时候,发现抛出异常了

搜索了一下,大部分说是在删除的时候没有使用迭代器,也就是没有安全删除,详细解释可以搜索另一篇博文,Arraylist的安全删除

而我觉得最贴切的一句描述是

原因:执行了集合删除元素操作后进行集合元素操作,List,Set,Map迭代的时候是不允许自身长度改变的(无论是新增还是删除都需要修改list的长度)

解决:重新用一个集合处理

 

 

如图,没有对该列表进行输出的时候不报错,应证了上述说法

 

我的read线程在试图往arraylist里面新增数据,而这边又尝试读取,因此报错

 

编程的时候遇到的

 

使用java的对象流出现java.io.StreamCorruptedException: invalid stream header异常

原因是数据发送端发送对象到接收端

接收端对于同一个输入流创建了不同的对象输入流,而后用不同的对象输入流进行接收

 

例子:

也就是说对对象的序列化和反序列化应当由同一组包装流来实现!!

0 128

今天在写代码的时候突然想到

抛出异常后必须要处理,如果不处理程序会往下走吗

原先这里是没有try{}catch()包裹的,我只想抛出一个异常,但是发现如果不捕获的话,程序是无法往下继续的,特此记录

今天遇到一个问题,在下面的代码中,当抛出运行时异常后,后面的代码还会执行吗,是否需要在异常后面加上return语句呢?

答案是:不需要,且编译不通过

   @Override
    public void registerObserver(Observer o) {
        if (o == null){
            throw new NullPointerException("o is a null object");
            return;  //需要么?
        }
        this.mList.add(o);
    }

为了搞清楚这个问题,我编写了几段代码测试了一下,结果如下:

//代码1
public static void test() throws Exception  {

    throw new Exception("参数越界"); 
    System.out.println("异常后"); //编译错误,「无法访问的语句」
}
//代码2
try{
    throw new Exception("参数越界"); 
}catch(Exception e) {
    e.printStackTrace();
}
System.out.println("异常后");//可以执行
//代码3
if(true) {
    throw new Exception("参数越界"); 
}
System.out.println("异常后"); //抛出异常,不会执行

总结:

  1. 若一段代码前有异常抛出,并且这个异常没有被捕获,这段代码将产生编译时错误「无法访问的语句」。如代码1;
  2. 若一段代码前有异常抛出,并且这个异常被try…catch所捕获,若此时catch语句中没有抛出新的异常,则这段代码能够被执行,否则,同第1条。如代码2;
  3. 若在一个条件语句中抛出异常,则程序能被编译,但后面的语句不会被执行。如代码3

另外总结一下运行时异常与非运行时异常的区别:

  • 运行时异常是RuntimeException类及其子类的异常,是非受检异常,如NullPointerException、IndexOutOfBoundsException等。由于这类异常要么是系统异常,无法处理,如网络问题;要么是程序逻辑错误,如空指针异常;JVM必须停止运行以改正这种错误,所以运行时异常可以不进行处理(捕获或向上抛出,当然也可以处理),而由JVM自行处理。Java Runtime会自动catch到程序throw的RuntimeException,然后停止线程,打印异常。
  • 非运行时异常是RuntimeException以外的异常,类型上都属于Exception类及其子类,是受检异常。非运行时异常必须进行处理(捕获或向上抛出),如果不处理,程序将出现编译错误。一般情况下,API中写了throws的Exception都不是RuntimeException。

在项目开发中,异常处理往往是一个容易被忽略的点,我们可能遇到程序异常就进行捕获,这样可能会产生大量的tyr-catch语句,使得项目结构复杂化,并且由于异常出现的位置常常是跨模块的,如果我们没有对异常进行统一管理,那么当程序遇到异常时,能提供给我们调试的信息就不清晰

我们处理了异常,却没有得到更好的效果,因此为了解决这个问题,我查阅了相关资料,并决心根据自己的项目经验进行总结

在此篇文章中你可以看到:

  1. 对枚举类型的使用
  2. Springboot项目中对异常的统一处理
  3. 自定义数据传输格式
  4. Result风格的CRUD
  5. Postman的使用

 

正文:

SpringBoot处理异常的几种方式:

  1. 使用@ControllerAdvice和@ExceptionHandler处理全局异常
  2. 使用@ExceptionHandler处理Controller级别的异常
  3. ResponseStatusException

本文采用第一种,使用@ControllerAdvice开启全局异常的捕获,自定义一个方法使用@ExceptionHandler注解然后定义捕获异常的类型即可对这些捕获的异常进行统一处理

示例:

上述示例中,我们对捕获的异常进行简单的二次处理,返回异常的信息,这种方式虽然能让我们知道异常的原因,但在很多情况下来说还是不够人性化

因此我们可以通过自定义的异常类及枚举类型来实现我们想要的数据;

项目依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
   <modelVersion>4.0.0</modelVersion>
   <parent>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-parent</artifactId>
      <version>2.4.4</version>
      <relativePath/> <!-- lookup parent from repository -->
   </parent>
   <groupId>com.yang</groupId>
   <artifactId>helloworld</artifactId>
   <version>0.0.1-SNAPSHOT</version>
   <name>helloworld</name>
   <description>Demo project for Spring Boot</description>
   <properties>
      <java.version>11</java.version>
   </properties>
   <dependencies>
      <!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
      <dependency>
         <groupId>com.google.guava</groupId>
         <artifactId>guava</artifactId>
         <version>21.0</version>
      </dependency>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-web</artifactId>
      </dependency>

      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-test</artifactId>
         <scope>test</scope>
      </dependency>

      <dependency>
         <groupId>org.thymeleaf.extras</groupId>
         <artifactId>thymeleaf-extras-java8time</artifactId>
      </dependency>

      <dependency>
         <groupId>org.thymeleaf</groupId>
         <artifactId>thymeleaf-spring5</artifactId>
      </dependency>
        <dependency>
            <groupId>org.mortbay.jetty</groupId>
            <artifactId>jetty</artifactId>
            <version>6.1.25</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
      <dependency>
         <groupId>com.alibaba</groupId>
         <artifactId>fastjson</artifactId>
         <version>1.2.58</version>
      </dependency>
   </dependencies>

   <build>
      <plugins>
         <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
         </plugin>
      </plugins>
   </build>

</project>

 

自定义基础接口类

首先定义一个基础的接口类,自定义的错误描述枚举类型需要实现该接口

public interface BaseErrorInfoInterface {
    /** 错误码*/
    String getResultCode();
   
   /** 错误描述*/
    String getResultMsg();
}

自定义枚举类型,实现该接口(用于错误描述)
public enum CommonEnum implements BaseErrorInfoInterface {
   // 数据操作错误定义
   SUCCESS("200", "成功!"), 
   BODY_NOT_MATCH("400","请求的数据格式不符!"),
   SIGNATURE_NOT_MATCH("401","请求的数字签名不匹配!"),
   NOT_FOUND("404", "未找到该资源!"), 
   INTERNAL_SERVER_ERROR("500", "服务器内部错误!"),
   SERVER_BUSY("503","服务器正忙,请稍后再试!")
   ;

   /** 错误码 */
   private String resultCode;

   /** 错误描述 */
   private String resultMsg;

   CommonEnum(String resultCode, String resultMsg) {
      this.resultCode = resultCode;
      this.resultMsg = resultMsg;
   }

   @Override
   public String getResultCode() {
      return resultCode;
   }

   @Override
   public String getResultMsg() {
      return resultMsg;
   }

}

自定义异常类
自定义一个异常类,用于处理我们程序中所出现的异常
public class BizException extends RuntimeException {

   private static final long serialVersionUID = 1L;

   /**
    * 错误码
    */
   protected String errorCode;
   /**
    * 错误信息
    */
   protected String errorMsg;

   public BizException() {
      super();
   }

   public BizException(BaseErrorInfoInterface errorInfoInterface) {
      super(errorInfoInterface.getResultCode());
      this.errorCode = errorInfoInterface.getResultCode();
      this.errorMsg = errorInfoInterface.getResultMsg();
   }
   
   public BizException(BaseErrorInfoInterface errorInfoInterface, Throwable cause) {
      super(errorInfoInterface.getResultCode(), cause);
      this.errorCode = errorInfoInterface.getResultCode();
      this.errorMsg = errorInfoInterface.getResultMsg();
   }
   
   public BizException(String errorMsg) {
      super(errorMsg);
      this.errorMsg = errorMsg;
   }
   
   public BizException(String errorCode, String errorMsg) {
      super(errorCode);
      this.errorCode = errorCode;
      this.errorMsg = errorMsg;
   }

   public BizException(String errorCode, String errorMsg, Throwable cause) {
      super(errorCode, cause);
      this.errorCode = errorCode;
      this.errorMsg = errorMsg;
   }
   

   public String getErrorCode() {
      return errorCode;
   }

   public void setErrorCode(String errorCode) {
      this.errorCode = errorCode;
   }

   public String getErrorMsg() {
      return errorMsg;
   }

   public void setErrorMsg(String errorMsg) {
      this.errorMsg = errorMsg;
   }

   public String getMessage() {
      return errorMsg;
   }

   /**
    */
   @Override
   public Throwable fillInStackTrace() {
      return this;
   }
}

自定义数据格式
定义数据的传输格式(返回给前端的格式)
public class ResultBody {
   /**
    * 响应代码
    */
   private String code;

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

   /**
    * 响应结果
    */
   private Object result;

   public ResultBody() {
   }

   public ResultBody(BaseErrorInfoInterface errorInfo) {
      this.code = errorInfo.getResultCode();
      this.message = errorInfo.getResultMsg();
   }

   public String getCode() {
      return code;
   }

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

   public String getMessage() {
      return message;
   }

   public void setMessage(String message) {
      this.message = message;
   }

   public Object getResult() {
      return result;
   }

   public void setResult(Object result) {
      this.result = result;
   }

   /**
    * 成功
    * 
    * @return
    */
   public static ResultBody success() {
      return success(null);
   }

   /**
    * 成功
    * @param data
    * @return
    */
   public static ResultBody success(Object data) {
      ResultBody rb = new ResultBody();
      rb.setCode(CommonEnum.SUCCESS.getResultCode());
      rb.setMessage(CommonEnum.SUCCESS.getResultMsg());
      rb.setResult(data);
      return rb;
   }

   /**
    * 失败
    */
   public static ResultBody error(BaseErrorInfoInterface errorInfo) {
      ResultBody rb = new ResultBody();
      rb.setCode(errorInfo.getResultCode());
      rb.setMessage(errorInfo.getResultMsg());
      rb.setResult(null);
      return rb;
   }

   /**
    * 失败
    */
   public static ResultBody error(String code, String message) {
      ResultBody rb = new ResultBody();
      rb.setCode(code);
      rb.setMessage(message);
      rb.setResult(null);
      return rb;
   }

   /**
    * 失败
    */
   public static ResultBody error( String message) {
      ResultBody rb = new ResultBody();
      rb.setCode("-1");
      rb.setMessage(message);
      rb.setResult(null);
      return rb;
   }

   @Override
   public String toString() {
      return JSONObject.toJSONString(this);
   }

}

自定义全局异常处理类
编写自定义全局异常处理类,程序中遇到异常了进行抛出,抛出之后总要捕获吧,此类即为捕获全局抛出的异常

/**
 * 
* @Title: GlobalExceptionHandler
* @Description: 全局异常处理
如果使用@RestControllerAdvice 注解
则会将返回的数据类型转换成JSON格式
 */
@ControllerAdvice
public class GlobalExceptionHandler {
   private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
   
   /**
    * 处理自定义的业务异常
    * @param req
    * @param e
    * @return
    */
    @ExceptionHandler(value = BizException.class)  
    @ResponseBody  
   public  ResultBody bizExceptionHandler(HttpServletRequest req, BizException e){
       logger.error("发生业务异常!原因是:{}",e.getErrorMsg());
       return ResultBody.error(e.getErrorCode(),e.getErrorMsg());
    }

   /**
    * 处理空指针的异常
    * @param req
    * @param e
    * @return
    */
   @ExceptionHandler(value =NullPointerException.class)
   @ResponseBody
   public ResultBody exceptionHandler(HttpServletRequest req, NullPointerException e){
      logger.error("发生空指针异常!原因是:",e);
      return ResultBody.error(CommonEnum.BODY_NOT_MATCH);
   }


    /**
        * 处理其他异常
     * @param req
     * @param e
     * @return
     */
    @ExceptionHandler(value =Exception.class)
   @ResponseBody
   public ResultBody exceptionHandler(HttpServletRequest req, Exception e){
       logger.error("未知异常!原因是:",e);
           return ResultBody.error(CommonEnum.INTERNAL_SERVER_ERROR);
    }
}

自定义一个实体类
User实体类,目的是供Controller操作并制造异常,模拟程序出现的异常

/**
 * 
* Title: User
* Description:用户pojo类
 */
public class User {
   /** 编号*/
   private Long id;
   /** 姓名*/
   private String name;
   /** 年龄*/
   private Integer age;
   /**  
    * 获取编号  
    * @return  id  
    */
   public Long getId() {
      return id;
   }
   /**  
    * 设置编号  
    * @param Long id  
    */
   public void setId(Long id) {
      this.id = id;
   }
   /**  
    * 获取姓名  
    * @return  name  
    */
   public String getName() {
      return name;
   }
   /**  
    * 设置姓名  
    * @param String name  
    */
   public void setName(String name) {
      this.name = name;
   }
   /**  
    * 获取年龄  
    * @return  age  
    */
   public Integer getAge() {
      return age;
   }
   /**  
    * 设置年龄  
    * @param Integer age  
    */
   public void setAge(Integer age) {
      this.age = age;
   }
}

Controller控制层
该层采用Result风格实现CRUD功能,在该类我们故意制造异常不捕获,让其抛出被我们的全局异常捕获类GlobalExceptionHandler捕获进行处理
这些异常有自定义的异常抛出,也有空指针的异常抛出,也有不可预知的异常抛出

* @Title: UserRestController
* @Description:
* 用户控制层
 */
@RestController
@RequestMapping(value = "/api")
public class UserRestController {

   @PostMapping("/user")
    public boolean insert(@RequestBody(required = false) User user) {
       System.out.println("开始新增...");
       //如果姓名为空就手动抛出一个自定义的异常!
        if(user.getName()==null){
            throw  new BizException("-1","用户姓名不能为空!");
        }
        return true;
    }

    @PutMapping("/user")
    public boolean update(@RequestBody(required = false) User user) {
       System.out.println("开始更新...");
       //这里故意造成一个空指针的异常,并且不进行处理
        String str=null;
        str.equals("111");
        return true;
    }

    @DeleteMapping("/user")
    public boolean delete(@RequestBody(required = false) User user)  {
        System.out.println("开始删除...");
        //这里故意造成一个异常,并且不进行处理
        Integer.parseInt("abc123");
        return true;
    }

    @GetMapping("/user")
    public List<User> findByUser(User user) {
       System.out.println("开始查询123...");
        List<User> userList =new ArrayList<>();
        User user2=new User();
        user2.setId(1L);
        user2.setName("xuwujing");
        user2.setAge(18);
        userList.add(user2);
        return userList;
    } 
}

主程序入口
/**
 * 
* @Title: App
* @Description:
* 主程序入口
 */

@SpringBootApplication
public class App 
{
    public static void main( String[] args )
    {
      SpringApplication.run(App.class, args);
      System.out.println("程序正在运行...");
    }
}

接下来使用Postman进行接口测试

首先进行查询,使用GET 方式进行请求。

然后我们再来测试下自定义的异常是否能够被正确的捕获并处理。

使用POST方式进行请求

POST http://localhost:8080/api/user

Body参数为:

{“id”:1,”age”:18}

返回参数为:

{“code”:”-1″,”message”:”用户姓名不能为空!”,”result”:null}

示例图:
在这里插入图片描述
可以看出将我们抛出的异常进行数据封装,然后将异常返回出来。

然后我们再来测试下空指针异常是否能够被正确的捕获并处理。在自定义全局异常中,我们除了定义空指针的异常处理,也定义最高级别之一的Exception异常,那么这里发生了空指针异常之后,它是回优先使用哪一个呢?这里我们来测试下。

使用PUT方式进行请求。

PUT http://localhost:8080/api/user

Body参数为:

{“id”:1,”age”:18}

返回参数为:

{“code”:”400″,”message”:”请求的数据格式不符!”,”result”:null}

示例图:

我们可以看到这里的的确是返回空指针的异常护理,可以得出全局异常处理优先处理子类的异常。

那么我们在来试试未指定其异常的处理,看该异常是否能够被捕获。

使用DELETE方式进行请求

DELETE http://localhost:8080/api/user

Body参数为:

{“id”:1}

返回参数为:

{“code”:”500″,”message”:”服务器内部错误!”,”result”:null}

在这里插入图片描述

这里可以看到它使用了我们在自定义全局异常处理类中的Exception异常处理的方法。
到这里,测试就结束了。顺便再说一下,自义定全局异常处理除了可以处理上述的数据格式之外,也可以处理页面的跳转,只需在新增的异常方法的返回处理上填写该跳转的路径并不使用ResponseBody 注解即可。 细心的同学也许发现了在GlobalExceptionHandler类中使用的是ControllerAdvice注解,而非RestControllerAdvice注解,如果是用的RestControllerAdvice注解,它会将数据自动转换成JSON格式,这种于ControllerRestController类似,所以我们在使用全局异常处理的之后可以进行灵活的选择处理。

 

 

0 111

为了优化Springboot项目的全局异常处理,回顾了一下异常知识,特此记录

 

首先了解一下异常体系结构

那么什么是异常呢?

出现场景:我们编写的模块让用户输入数字,而用户却输入了字符,我们的程序要打开某个文档,但这个文档却不存在或者格式不对,读取数据库的数据,数据可能是空的,网络连接失败,非法参数等;这些异常发生在程序运行期间,它影响了正常的程序执行流程;

这些场景我们称之为异常(Exception)

为什么要处理异常?

为了使程序对异常做出合理的处理,不让程序崩溃或无法运行;

 

异常分类:

1.检查性异常:最具代表的检查性异常是用户错误或问题引起的异常,程序员无法预见。例如要打开一个不存在的文件时,一个异常就发生了,这些异常在编译时不能被简单的忽略;

2.运行时异常:运行时异常是可能被程序员避免的异常,与检查性异常相反,运行时异常可以在编译时被忽略;

错误(Error):错误不是异常,而是脱离程序员控制的问题,非常严重,错误在代码中通常被忽略。例如,当栈溢出时,一个错误就发生了,它们在编译也检查不到;

Exception

 

 

异常处理机制

五大关键字:

try:尝试去处理某代码块

catch:捕获try中代码块出现的异常

finally:一定会执行(可用于关闭资源)

throw、throws:抛出异常

 

1.制造异常

2.捕获异常输出信息

3.接下来我们更改上述异常为错误,观察ArithmeticException这个异常类能不能捕获Error

4.由异常体系图可知,Error和Exception都是Throwable类的子类,那么Throwable是否可以捕获Error呢?

可知:catch()里面的参数为想要捕获的异常类型,Throwable最高级

5.Java支持多catch捕获(从小到大),但是需要注意,子类异常在上父类异常在下才可以,如果父类异常在上那么子类异常catch就不起作用了,因为父类先捕获了异常
补充:

IDEA添加Try()catch()快捷键:ctrl+alt+t

e.printStackTrace();打印错误的栈信息

异常抛出:

@Test
void Exception02(){

   try {
      test(1,0);
   } catch (ArithmeticException e) {
      e.printStackTrace();
   }
}
//假设这个方法处理不了异常,则方法抛出异常
public void test(int a, int b)throws ArithmeticException{
   if(b == 0)throw new ArithmeticException();//主动抛出异常,一般在方法中使用
}

算数异常属于运行时异常,不需要我们程序抛出,Java会自己抛出,如果不用try()catch()程序会停止,但如果我们用了,程序就会继续往下运行;

 

实战:自定义异常

使用Java内置的异常类可以描述编程时出现的大部分异常情况,除此之外,用户还可以自定义异常。用户自定义异常类,只需要继承Exception类即可

步骤:

1.创建自定义异常类

2.在方法中通过throw关键字抛出异常对象

3.如果在当前抛出异常的方法中处理异常,使用Try-catch语句捕获并处理;否则在方法的声明处使用throws关键字指明要抛出给方法调用者的异常,继续进行下一步操作

4.在出现一场方法的调用者中捕获并处理异常

 

代码:

package Exception;


/**自定义异常类
 *1.自定义异常类,继承Exception
 *2.处理可能出现的异常问题
 *3.输出异常信息
 *
 */
public class MyException extends Exception{


    //传递数字>10则抛出异常
    private int detail;

    public MyException(int detaild) {
        this.detail = detail;
    }


    //异常的打印信息
    @Override
    public String toString() {
        return "你好,MyException{" +
                "detail=" + detail +
                '}';
    }
}
public class Test {

    //可能存在异常的方法,通过throws进行抛出,try-catch则为捕获
   static public void test(int a) throws MyException {
        System.out.println("传递的参数为:"+a);
        if(a>10)throw new MyException(a);//抛出,使外面调用者捕获
       System.out.println("Ok");
   }

    public static void main(String[] args) {
        try {
            test(11);
        } catch (MyException e) {
            System.out.println("MyException->"+e);
        }
    }
}

使用经常总结: