SpringBoot2.3.12.RELEASE优雅的全局异常处理(模板一)
参考:https://www.cnblogs.com/xuwujing/p/10933082.html
1、首先,需要引入maven依赖包,如下所示:
1 <?xml version="1.0" encoding="UTF-8"?> 2 <project xmlns="http://maven.apache.org/POM/4.0.0" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> 5 <modelVersion>4.0.0</modelVersion> 6 <parent> 7 <groupId>org.springframework.boot</groupId> 8 <artifactId>spring-boot-starter-parent</artifactId> 9 <version>2.3.12.RELEASE</version> 10 <relativePath /> <!-- lookup parent from repository --> 11 </parent> 12 <groupId>com.bie</groupId> 13 <artifactId>SpringbootException</artifactId> 14 <version>0.0.1-SNAPSHOT</version> 15 <name>SpringbootException</name> 16 <description>Demo project for Spring Boot</description> 17 18 <properties> 19 <java.version>1.8</java.version> 20 </properties> 21 22 <dependencies> 23 <!-- 引入springboot web模块 --> 24 <dependency> 25 <groupId>org.springframework.boot</groupId> 26 <artifactId>spring-boot-starter-web</artifactId> 27 </dependency> 28 <!-- 引入fastjson的依赖 --> 29 <dependency> 30 <groupId>com.alibaba</groupId> 31 <artifactId>fastjson</artifactId> 32 <version>1.2.41</version> 33 </dependency> 34 35 <dependency> 36 <groupId>org.springframework.boot</groupId> 37 <artifactId>spring-boot-starter-test</artifactId> 38 <scope>test</scope> 39 <exclusions> 40 <exclusion> 41 <groupId>org.junit.vintage</groupId> 42 <artifactId>junit-vintage-engine</artifactId> 43 </exclusion> 44 </exclusions> 45 </dependency> 46 </dependencies> 47 48 <build> 49 <plugins> 50 <plugin> 51 <groupId>org.springframework.boot</groupId> 52 <artifactId>spring-boot-maven-plugin</artifactId> 53 </plugin> 54 </plugins> 55 </build> 56 57 </project>
SpringBoot中有一个ControllerAdvice的注解,使用该注解表示开启了全局异常的捕获,我们只需再自定义一个方法,然后使用ExceptionHandler注解,在该注解的value属性里面,定义捕获异常的类型,即可对这些捕获的异常进行统一的处理。
2、自定义基础接口类。
首先定义一个基础的接口类,自定义的错误描述枚举类需实现该接口。
1 package com.bie.enums; 2 3 public interface BaseErrorInfoInterface { 4 5 // 错误码 6 public String getResultCode(); 7 8 // 错误描述 9 public String getResultMsg(); 10 11 }
3、自定义枚举类。
然后我们这里在自定义一个枚举类,并实现该接口。而使用枚举类的好处是处理异常的时候,可以通过枚举类直接获取到错误码、错误描述,方便调用。
1 package com.bie.enums; 2 3 public enum CommonEnum implements BaseErrorInfoInterface { 4 5 // 数据操作错误定义 6 SUCCESS("200", "接口调用成功!"), 7 8 BODY_NOT_MATCH("400", "请求的数据格式不符!"), 9 10 SIGNATURE_NOT_MATCH("401", "请求的数字签名不匹配!"), 11 12 NOT_FOUND("404", "未找到该资源!"), 13 14 INTERNAL_SERVER_ERROR("500", "服务器内部错误!"), 15 16 SERVER_BUSY("503", "服务器正忙,请稍后再试!"); 17 18 // 错误码 19 private String resultCode; 20 21 // 错误描述 22 private String resultMsg; 23 24 CommonEnum(String resultCode, String resultMsg) { 25 this.resultCode = resultCode; 26 this.resultMsg = resultMsg; 27 } 28 29 @Override 30 public String getResultCode() { 31 return resultCode; 32 } 33 34 @Override 35 public String getResultMsg() { 36 return resultMsg; 37 } 38 39 }
4、自定义异常类。
然后我们在来自定义一个异常类,用于处理我们发生的业务异常。
1 package com.bie.exception; 2 3 import com.bie.enums.BaseErrorInfoInterface; 4 5 public class BizException extends RuntimeException { 6 7 /** 8 * 9 */ 10 private static final long serialVersionUID = -6329783845738305585L; 11 12 // 错误码 13 protected String errorCode; 14 // 错误信息 15 protected String errorMsg; 16 17 public BizException() { 18 super(); 19 } 20 21 public BizException(BaseErrorInfoInterface errorInfoInterface) { 22 super(errorInfoInterface.getResultCode()); 23 this.errorCode = errorInfoInterface.getResultCode(); 24 this.errorMsg = errorInfoInterface.getResultMsg(); 25 } 26 27 public BizException(BaseErrorInfoInterface errorInfoInterface, Throwable cause) { 28 super(errorInfoInterface.getResultCode(), cause); 29 this.errorCode = errorInfoInterface.getResultCode(); 30 this.errorMsg = errorInfoInterface.getResultMsg(); 31 } 32 33 public BizException(String errorMsg) { 34 super(errorMsg); 35 this.errorMsg = errorMsg; 36 } 37 38 public BizException(String errorCode, String errorMsg) { 39 super(errorCode); 40 this.errorCode = errorCode; 41 this.errorMsg = errorMsg; 42 } 43 44 public BizException(String errorCode, String errorMsg, Throwable cause) { 45 super(errorCode, cause); 46 this.errorCode = errorCode; 47 this.errorMsg = errorMsg; 48 } 49 50 public String getErrorCode() { 51 return errorCode; 52 } 53 54 public void setErrorCode(String errorCode) { 55 this.errorCode = errorCode; 56 } 57 58 public String getErrorMsg() { 59 return errorMsg; 60 } 61 62 public void setErrorMsg(String errorMsg) { 63 this.errorMsg = errorMsg; 64 } 65 66 public String getMessage() { 67 return errorMsg; 68 } 69 70 @Override 71 public Throwable fillInStackTrace() { 72 return this; 73 } 74 75 }
5、自定义数据格式。
顺便这里我们定义一下数据的传输格式,作用主要用于返回给前端的数据格式。
1 package com.bie.utils; 2 3 import com.alibaba.fastjson.JSONObject; 4 import com.bie.enums.BaseErrorInfoInterface; 5 import com.bie.enums.CommonEnum; 6 7 public class ResultBody { 8 9 // 响应代码 10 private String code; 11 12 // 响应消息 13 private String message; 14 15 // 响应结果 16 private Object result; 17 18 public ResultBody() { 19 } 20 21 public ResultBody(BaseErrorInfoInterface errorInfo) { 22 this.code = errorInfo.getResultCode(); 23 this.message = errorInfo.getResultMsg(); 24 } 25 26 public String getCode() { 27 return code; 28 } 29 30 public void setCode(String code) { 31 this.code = code; 32 } 33 34 public String getMessage() { 35 return message; 36 } 37 38 public void setMessage(String message) { 39 this.message = message; 40 } 41 42 public Object getResult() { 43 return result; 44 } 45 46 public void setResult(Object result) { 47 this.result = result; 48 } 49 50 /** 51 * 成功 52 * 53 * @return 54 */ 55 public static ResultBody success() { 56 return success(null); 57 } 58 59 /** 60 * 成功 61 * 62 * @param data 63 * @return 64 */ 65 public static ResultBody success(Object data) { 66 ResultBody rb = new ResultBody(); 67 rb.setCode(CommonEnum.SUCCESS.getResultCode()); 68 rb.setMessage(CommonEnum.SUCCESS.getResultMsg()); 69 rb.setResult(data); 70 return rb; 71 } 72 73 /** 74 * 失败 75 */ 76 public static ResultBody error(BaseErrorInfoInterface errorInfo) { 77 ResultBody rb = new ResultBody(); 78 rb.setCode(errorInfo.getResultCode()); 79 rb.setMessage(errorInfo.getResultMsg()); 80 rb.setResult(null); 81 return rb; 82 } 83 84 /** 85 * 失败 86 */ 87 public static ResultBody error(String code, String message) { 88 ResultBody rb = new ResultBody(); 89 rb.setCode(code); 90 rb.setMessage(message); 91 rb.setResult(null); 92 return rb; 93 } 94 95 /** 96 * 失败 97 */ 98 public static ResultBody error(String message) { 99 ResultBody rb = new ResultBody(); 100 rb.setCode("-1"); 101 rb.setMessage(message); 102 rb.setResult(null); 103 return rb; 104 } 105 106 @Override 107 public String toString() { 108 return JSONObject.toJSONString(this); 109 } 110 111 }
6、自定义全局异常处理类。
最后我们再来编写一个自定义全局异常处理的类,可以用于处理各类异常。
1 package com.bie.exception; 2 3 import javax.servlet.http.HttpServletRequest; 4 5 import org.slf4j.Logger; 6 import org.slf4j.LoggerFactory; 7 import org.springframework.web.bind.annotation.ControllerAdvice; 8 import org.springframework.web.bind.annotation.ExceptionHandler; 9 import org.springframework.web.bind.annotation.ResponseBody; 10 11 import com.bie.enums.CommonEnum; 12 import com.bie.utils.ResultBody; 13 14 @ControllerAdvice 15 public class GlobalExceptionHandler { 16 17 private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class); 18 19 /** 20 * 处理自定义的业务异常 21 * 22 * @param req 23 * @param e 24 * @return 25 */ 26 @ExceptionHandler(value = BizException.class) 27 @ResponseBody 28 public ResultBody bizExceptionHandler(HttpServletRequest req, BizException e) { 29 logger.error("发生业务异常!原因是:{}", e.getErrorMsg()); 30 return ResultBody.error(e.getErrorCode(), e.getErrorMsg()); 31 } 32 33 /** 34 * 处理空指针的异常 35 * 36 * @param req 37 * @param e 38 * @return 39 */ 40 @ExceptionHandler(value = NullPointerException.class) 41 @ResponseBody 42 public ResultBody exceptionHandler(HttpServletRequest req, NullPointerException e) { 43 logger.error("发生空指针异常!原因是:", e); 44 return ResultBody.error(CommonEnum.BODY_NOT_MATCH); 45 } 46 47 /** 48 * 处理其他异常 49 * 50 * @param req 51 * @param e 52 * @return 53 */ 54 @ExceptionHandler(value = Exception.class) 55 @ResponseBody 56 public ResultBody exceptionHandler(HttpServletRequest req, Exception e) { 57 logger.error("未知异常!原因是:", e); 58 return ResultBody.error(CommonEnum.INTERNAL_SERVER_ERROR); 59 } 60 61 }
7、创建一个用户的实体类,如下所示:
1 package com.bie.po; 2 3 import java.io.Serializable; 4 5 import com.alibaba.fastjson.JSONObject; 6 7 public class User implements Serializable { 8 9 /** 10 * 11 */ 12 private static final long serialVersionUID = 1360679426784375558L; 13 14 // 编号 15 private int id; 16 // 姓名 17 private String name; 18 // 年龄 19 private int age; 20 21 public User() { 22 } 23 24 public int getId() { 25 return id; 26 } 27 28 public void setId(int id) { 29 this.id = id; 30 } 31 32 public String getName() { 33 return name; 34 } 35 36 public void setName(String name) { 37 this.name = name; 38 } 39 40 public int getAge() { 41 return age; 42 } 43 44 public void setAge(int age) { 45 this.age = age; 46 } 47 48 public String toString() { 49 return JSONObject.toJSONString(this); 50 } 51 52 }
8、Controller 控制层。
控制层这边也比较简单,使用Restful风格实现的CRUD功能,主要是Restful风格的,根据请求方式get、post、put、delete,而请求路径是一个,主要根据请求方式来做区分操作。
1 package com.bie.controller; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 6 import org.springframework.web.bind.annotation.DeleteMapping; 7 import org.springframework.web.bind.annotation.GetMapping; 8 import org.springframework.web.bind.annotation.PostMapping; 9 import org.springframework.web.bind.annotation.PutMapping; 10 import org.springframework.web.bind.annotation.RequestBody; 11 import org.springframework.web.bind.annotation.RequestMapping; 12 import org.springframework.web.bind.annotation.RestController; 13 14 import com.bie.exception.BizException; 15 import com.bie.po.User; 16 17 @RestController 18 @RequestMapping(value = "/api") 19 public class UserRestController { 20 21 @PostMapping("/user") 22 public boolean insert(@RequestBody User user) { 23 System.out.println("开始新增..."); 24 // 如果姓名为空就手动抛出一个自定义的异常! 25 if (user.getName() == null) { 26 throw new BizException("-1", "用户姓名不能为空!"); 27 } 28 return true; 29 } 30 31 @PutMapping("/user") 32 public boolean update(@RequestBody User user) { 33 System.out.println("开始更新..."); 34 // 这里故意造成一个空指针的异常,并且不进行处理 35 String str = null; 36 str.equals("111"); 37 return true; 38 } 39 40 @DeleteMapping("/user") 41 public boolean delete(@RequestBody User user) { 42 System.out.println("开始删除..."); 43 // 这里故意造成一个异常,并且不进行处理 44 Integer.parseInt("abc123"); 45 return true; 46 } 47 48 @GetMapping("/user") 49 public List<User> findByUser(User user) { 50 System.out.println("开始查询..."); 51 List<User> userList = new ArrayList<>(); 52 User user2 = new User(); 53 user2.setId(1); 54 user2.setName("xuwujing"); 55 user2.setAge(18); 56 userList.add(user2); 57 return userList; 58 } 59 60 }
9、接口功能测试,使用postman进行测试,如下所示:
9.1、首先进行查询,查看程序是否正常运行,使用GET 方式进行请求,如下所示:
9.2、进行插入操作,使用POST方式进行请求,如下所示:
9.3、进行修改操作,使用PUT方式进行请求,如下所示:
9.4、进行删除操作,使用DELETE方式进行请求,如下所示:
10、整体思路解析,按照步骤操作,按道理来说,这个思路是很优秀的,那么下面来分析一下这个设计思路。
10.1、在自己的方法中抛出自定义异常,而抛出的自定义异常是被全局异常类进行捕获处理的。
对抛出的自定义异常,在全局异常处理类中进行处理,然后返回的信息,是封装到自定义数据格式类中的,这样返回给前端的数据格式,就可以根据自己的需求进行设计。
10.2、而对于全局异常类中,可以定义捕获其他类型的异常。而在捕获其他异常之后,返回的数据封装到自定义数据格式里面,而对于其他异常而已直接使用定义的枚举类中来选择异常内容。
虽然,这种设计不能说100%完美,但是设计的已经很优秀了,基本可以满足日常需求,赞一个。