文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Spring Boot全局异常处理,这样写才优雅...

2024-12-03 06:52

关注

开发准备

环境要求:

JDK:1.8

SpringBoot:1.5.17.RELEASE

首先还是Maven的相关依赖: 

  1. <properties>  
  2.       <project.build.sourceEncoding>UTF-8project.build.sourceEncoding>  
  3.       <java.version>1.8java.version>  
  4.       <maven.compiler.source>1.8maven.compiler.source>  
  5.       <maven.compiler.target>1.8maven.compiler.target>  
  6. properties>  
  7. <parent>  
  8.     <groupId>org.springframework.bootgroupId>  
  9.     <artifactId>spring-boot-starter-parentartifactId>  
  10.     <version>1.5.17.RELEASEversion>  
  11.     <relativePath />  
  12. parent>  
  13. <dependencies>  
  14.       
  15.     <dependency>  
  16.         <groupId>org.springframework.bootgroupId>  
  17.         <artifactId>spring-boot-starter-webartifactId>  
  18.     dependency>  
  19.       
  20.     <dependency>  
  21.         <groupId>org.springframework.bootgroupId>  
  22.         <artifactId>spring-boot-starter-testartifactId>  
  23.         <scope>testscope>  
  24.     dependency>  
  25.     <dependency>  
  26.         <groupId>com.alibabagroupId>  
  27.         <artifactId>fastjsonartifactId>  
  28.         <version>1.2.41version>  
  29.     dependency>  
  30. dependencies> 

配置文件这块基本不需要更改,全局异常的处理只需在代码中实现即可。

代码编写

SpringBoot的项目已经对有一定的异常处理了,但是对于我们开发者而言可能就不太合适了,因此我们需要对这些异常进行统一的捕获并处理。

SpringBoot中有一个ControllerAdvice的注解,使用该注解表示开启了全局异常的捕获,我们只需在自定义一个方法使用ExceptionHandler注解然后定义捕获异常的类型即可对这些捕获的异常进行统一的处理。

我们根据下面的这个示例来看该注解是如何使用吧。Spring Boot 基础就不介绍了,看睛这个:https://github.com/javastacks/spring-boot-best-practice

示例代码: 

  1. @ControllerAdvice  
  2. public class MyExceptionHandler {  
  3.  @ExceptionHandler(value =Exception.class)  
  4.  public String exceptionHandler(Exception e){  
  5.   System.out.println("未知异常!原因是:"+e);  
  6.         return e.getMessage();  
  7.     }  

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

那么我们这里可以通过自定义的异常类以及枚举类来实现我们想要的那种数据吧。

自定义基础接口类

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

代码如下: 

  1. public interface BaseErrorInfoInterface {  
  2.     
  3.   String getResultCode();  
  4.    
  5.   String getResultMsg();  

自定义枚举类

然后我们这里在自定义一个枚举类,并实现该接口。

代码如下: 

  1. public enum CommonEnum implements BaseErrorInfoInterface {  
  2.  // 数据操作错误定义  
  3.  SUCCESS("200", "成功!"),   
  4.  BODY_NOT_MATCH("400","请求的数据格式不符!"),  
  5.  SIGNATURE_NOT_MATCH("401","请求的数字签名不匹配!"),  
  6.  NOT_FOUND("404", "未找到该资源!"),   
  7.  INTERNAL_SERVER_ERROR("500", "服务器内部错误!"),  
  8.  SERVER_BUSY("503","服务器正忙,请稍后再试!")  
  9.  ;  
  10.    
  11.  private String resultCode;  
  12.    
  13.  private String resultMsg;  
  14.  CommonEnum(String resultCode, String resultMsg) {  
  15.   this.resultCode = resultCode;  
  16.   this.resultMsg = resultMsg;  
  17.  }  
  18.  @Override  
  19.  public String getResultCode() {  
  20.   return resultCode;  
  21.  }  
  22.  @Override  
  23.  public String getResultMsg() {  
  24.   return resultMsg; 
  25.   

自定义异常类

然后我们在来自定义一个异常类,用于处理我们发生的业务异常。

代码如下: 

  1. public class BizException extends RuntimeException {  
  2.  private static final long serialVersionUID = 1L 
  3.    
  4.  protected String errorCode;  
  5.    
  6.  protected String errorMsg;  
  7.  public BizException() {  
  8.   super();  
  9.  }  
  10.  public BizException(BaseErrorInfoInterface errorInfoInterface) {  
  11.   super(errorInfoInterface.getResultCode());  
  12.   this.errorCode = errorInfoInterface.getResultCode();  
  13.   this.errorMsg = errorInfoInterface.getResultMsg();  
  14.  }  
  15.  public BizException(BaseErrorInfoInterface errorInfoInterface, Throwable cause) {  
  16.   super(errorInfoInterface.getResultCode(), cause);  
  17.   this.errorCode = errorInfoInterface.getResultCode();  
  18.   this.errorMsg = errorInfoInterface.getResultMsg();  
  19.  }  
  20.  public BizException(String errorMsg) {  
  21.   super(errorMsg);  
  22.   this.errorMsg = errorMsg;  
  23.  }  
  24.  public BizException(String errorCode, String errorMsg) {  
  25.   super(errorCode);  
  26.   this.errorCode = errorCode;  
  27.   this.errorMsg = errorMsg;  
  28.  }  
  29.  public BizException(String errorCode, String errorMsg, Throwable cause) {  
  30.   super(errorCode, cause);  
  31.   this.errorCode = errorCode;  
  32.   this.errorMsg = errorMsg;  
  33.  }   
  34.  public String getErrorCode() {  
  35.   return errorCode;  
  36.  }  
  37.  public void setErrorCode(String errorCode) {  
  38.   this.errorCode = errorCode;  
  39.  }  
  40.  public String getErrorMsg() {  
  41.   return errorMsg;  
  42.  }  
  43.  public void setErrorMsg(String errorMsg) {  
  44.   this.errorMsg = errorMsg;  
  45.  }  
  46.  public String getMessage() {  
  47.   return errorMsg;  
  48.  }  
  49.  @Override  
  50.  public Throwable fillInStackTrace() {  
  51.   return this; 
  52.  }  

自定义数据格式

顺便这里我们定义一下数据的传输格式。

代码如下: 

  1. public class ResultBody {  
  2.    
  3.  private String code;  
  4.    
  5.  private String message;  
  6.    
  7.  private Object result;  
  8.  public ResultBody() {  
  9.  }  
  10.  public ResultBody(BaseErrorInfoInterface errorInfo) {  
  11.   this.code = errorInfo.getResultCode();  
  12.   this.message = errorInfo.getResultMsg();  
  13.  }  
  14.  public String getCode() {  
  15.   return code; 
  16.  }  
  17.  public void setCode(String code) {  
  18.   this.code = code;  
  19.  }  
  20.  public String getMessage() {  
  21.   return message; 
  22.  }  
  23.  public void setMessage(String message) {  
  24.   this.message = message;  
  25.  }  
  26.  public Object getResult() { 
  27.   return result; 
  28.  }  
  29.  public void setResult(Object result) {  
  30.   this.result = result;  
  31.  }  
  32.    
  33.  public static ResultBody success() {  
  34.   return success(null);  
  35.  }  
  36.    
  37.  public static ResultBody success(Object data) {  
  38.   ResultBody rb = new ResultBody();  
  39.   rb.setCode(CommonEnum.SUCCESS.getResultCode());  
  40.   rb.setMessage(CommonEnum.SUCCESS.getResultMsg());  
  41.   rb.setResult(data);  
  42.   return rb;  
  43.  }  
  44.    
  45.  public static ResultBody error(BaseErrorInfoInterface errorInfo) {  
  46.   ResultBody rb = new ResultBody();  
  47.   rb.setCode(errorInfo.getResultCode());  
  48.   rb.setMessage(errorInfo.getResultMsg());  
  49.   rb.setResult(null);  
  50.   return rb;  
  51.  }  
  52.    
  53.  public static ResultBody error(String code, String message) {  
  54.   ResultBody rb = new ResultBody();  
  55.   rb.setCode(code);  
  56.   rb.setMessage(message);  
  57.   rb.setResult(null);  
  58.   return rb;  
  59.  }  
  60.    
  61.  public static ResultBody error( String message) {  
  62.   ResultBody rb = new ResultBody();  
  63.   rb.setCode("-1");  
  64.   rb.setMessage(message);  
  65.   rb.setResult(null);  
  66.   return rb;  
  67.  }  
  68.  @Override  
  69.  public String toString() {  
  70.   return JSONObject.toJSONString(this);  
  71.  }  

自定义全局异常处理类

最后我们在来编写一个自定义全局异常处理的类。

代码如下: 

  1. @ControllerAdvice  
  2. public class GlobalExceptionHandler {  
  3.  private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);   
  4.    
  5.     @ExceptionHandler(value = BizException.class)   
  6.     @ResponseBody    
  7.  public  ResultBody bizExceptionHandler(HttpServletRequest req, BizException e){  
  8.      logger.error("发生业务异常!原因是:{}",e.getErrorMsg());  
  9.      return ResultBody.error(e.getErrorCode(),e.getErrorMsg());  
  10.     }  
  11.    
  12.  @ExceptionHandler(value =NullPointerException.class)  
  13.  @ResponseBody 
  14.  public ResultBody exceptionHandler(HttpServletRequest req, NullPointerException e){  
  15.   logger.error("发生空指针异常!原因是:",e); 
  16.   return ResultBody.error(CommonEnum.BODY_NOT_MATCH);  
  17.  }  
  18.       
  19.     @ExceptionHandler(value =Exception.class)  
  20.  @ResponseBody  
  21.  public ResultBody exceptionHandler(HttpServletRequest req, Exception e){  
  22.      logger.error("未知异常!原因是:",e);  
  23.         return ResultBody.error(CommonEnum.INTERNAL_SERVER_ERROR);  
  24.     }  

因为这里我们只是用于做全局异常处理的功能实现以及测试,所以这里我们只需在添加一个实体类和一个控制层类即可。

实体类

又是万能的用户表 (▽)

代码如下: 

  1. public class User implements Serializable{  
  2.  private static final long serialVersionUID = 1L 
  3.    
  4.   private int id;  
  5.     
  6.   private String name;  
  7.     
  8.   private int age;   
  9.   public User(){  
  10.   }  
  11.  public int getId() {  
  12.   return id;  
  13.  }   
  14.  public void setId(int id) {  
  15.   this.id = id;  
  16.  }  
  17.  public String getName() {  
  18.   return name;  
  19.  } 
  20.  public void setName(String name) {  
  21.   this.name = name;  
  22.  } 
  23.  public int getAge() {  
  24.   return age;  
  25.  } 
  26.  public void setAge(int age) {  
  27.   this.age = age;  
  28.  }  
  29.  public String toString() {  
  30.   return JSONObject.toJSONString(this);  
  31.  }  

Controller 控制层

控制层这边也比较简单,使用Restful风格实现的CRUD功能,不同的是这里我故意弄出了一些异常,好让这些异常被捕获到然后处理。这些异常中,有自定义的异常抛出,也有空指针的异常抛出,当然也有不可预知的异常抛出(这里我用类型转换异常代替),那么我们在完成代码编写之后,看看这些异常是否能够被捕获处理成功吧!

代码如下: 

  1. @RestController  
  2. @RequestMapping(value = "/api" 
  3. public class UserRestController {  
  4.  @PostMapping("/user")  
  5.     public boolean insert(@RequestBody User user) {  
  6.      System.out.println("开始新增...");  
  7.      //如果姓名为空就手动抛出一个自定义的异常!  
  8.         if(user.getName()==null){  
  9.             throw  new BizException("-1","用户姓名不能为空!");  
  10.         }  
  11.         return true;  
  12.     }  
  13.     @PutMapping("/user")  
  14.     public boolean update(@RequestBody User user) {  
  15.      System.out.println("开始更新...");  
  16.        //这里故意造成一个空指针的异常,并且不进行处理  
  17.         String str=null 
  18.         str.equals("111");  
  19.         return true;  
  20.     }  
  21.     @DeleteMapping("/user")  
  22.     public boolean delete(@RequestBody User user)  {  
  23.         System.out.println("开始删除...");  
  24.         //这里故意造成一个异常,并且不进行处理  
  25.         Integer.parseInt("abc123");  
  26.         return true;  
  27.     }  
  28.     @GetMapping("/user")  
  29.     public List<User> findByUser(User user) {  
  30.      System.out.println("开始查询...");  
  31.         List<User> userList =new ArrayList<>();  
  32.         User user2=new User();  
  33.         user2.setId(1L);  
  34.         user2.setName("xuwujing"); 
  35.         user2.setAge(18);  
  36.         userList.add(user2);  
  37.         return userList;  
  38.     }  

App 入口

和普通的SpringBoot项目基本一样。

代码如下: 

  1. @SpringBootApplication  
  2. public class App  
  3.  
  4.     public static void main( String[] args )  
  5.     {  
  6.       SpringApplication.run(App.class, args);  
  7.       System.out.println("程序正在运行...");  
  8.     }  

功能测试

我们成功启动该程序之后,使用Postman工具来进行接口测试。

首先进行查询,查看程序正常运行是否ok,使用GET 方式进行请求。

GET http://localhost:8181/api/user

返回参数为:

{"id":1,"name":"xuwujing","age":18}

示例图:

可以看到程序正常返回,并没有因自定义的全局异常而影响。

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

使用POST方式进行请求

POST http://localhost:8181/api/user

Body参数为:

{"id":1,"age":18}

返回参数为:

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

示例图:

可以看出将我们抛出的异常进行数据封装,然后将异常返回出来。

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

使用PUT方式进行请求。

PUT http://localhost:8181/api/user

Body参数为:

{"id":1,"age":18}

返回参数为:

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

示例图:

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

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

使用DELETE方式进行请求。

DELETE http://localhost:8181/api/user

Body参数为:

{"id":1}

返回参数为:

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

这里可以看到它使用了我们在自定义全局异常处理类中的Exception异常处理的方法。

到这里,测试就结束了。

顺便再说一下,自义定全局异常处理除了可以处理上述的数据格式之外,也可以处理页面的跳转,只需在新增的异常方法的返回处理上填写该跳转的路径并不使用ResponseBody 注解即可。

细心的同学也许发现了在GlobalExceptionHandler类中使用的是ControllerAdvice注解,而非RestControllerAdvice注解,如果是用的RestControllerAdvice注解,它会将数据自动转换成JSON格式,这种于Controller和RestController类似,所以我们在使用全局异常处理的之后可以进行灵活的选择处理。

 

来源:Java技术栈内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯