Java SpringBoot 异常处理

SpringBoot全局异常处理

开发准备

环境要求

  • JDK:1.8
  • SpringBoot:1.5.17.RELEASE

首先还是Maven的相关依赖:

  1. <properties>
  2. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  3. <java.version>1.8</java.version>
  4. <maven.compiler.source>1.8</maven.compiler.source>
  5. <maven.compiler.target>1.8</maven.compiler.target>
  6. </properties>
  7. <parent>
  8. <groupId>org.springframework.boot</groupId>
  9. <artifactId>spring-boot-starter-parent</artifactId>
  10. <version>1.5.17.RELEASE</version>
  11. <relativePath />
  12. </parent>
  13. <dependencies>
  14. <!-- Spring Boot Web 依赖 核心 -->
  15. <dependency>
  16. <groupId>org.springframework.boot</groupId>
  17. <artifactId>spring-boot-starter-web</artifactId>
  18. </dependency>
  19. <!-- Spring Boot Test 依赖 -->
  20. <dependency>
  21. <groupId>org.springframework.boot</groupId>
  22. <artifactId>spring-boot-starter-test</artifactId>
  23. <scope>test</scope>
  24. </dependency>
  25. <dependency>
  26. <groupId>com.alibaba</groupId>
  27. <artifactId>fastjson</artifactId>
  28. <version>1.2.41</version>
  29. </dependency>
  30. </dependencies>

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

代码编写

SpringBoot的项目已经对有一定的异常处理了,但是对于开发者而言可能就不太合适了,因此需要对这些异常进行统一的捕获并处理。
SpringBoot中有一个ControllerAdvice的注解,使用该注解表示开启了全局异常的捕获,只需在自定义一个方法使用ExceptionHandler注解然后定义捕获异常的类型即可对这些捕获的异常进行统一的处理。
根据下面的这个示例来看该注解是如何使用吧。
示例代码:

  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. }
  8. }

上述的示例中,对捕获的异常进行简单的二次处理,返回异常的信息,虽然这种能够让我们知道异常的原因,但是在很多的情况下来说,可能还是不够人性化,不符合我们的要求。
那么这里可以通过自定义的异常类以及枚举类来实现想要的那种数据。

自定义基础接口类

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

  1. public interface BaseErrorInfoInterface {
  2. /** 错误码*/
  3. String getResultCode();
  4. /** 错误描述*/
  5. String getResultMsg();
  6. }

自定义枚举类

然后这里在自定义一个枚举类,并实现该接口。
代码如下:

  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. }
  26. }

自定义异常类

然后在来自定义一个异常类,用于处理发生的业务异常。
代码如下:

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

自定义数据格式

顺便这里定义一下数据的传输格式。
代码如下:

  1. public class ResultBody {
  2. /**
  3. * 响应代码
  4. */
  5. private String code;
  6. /**
  7. * 响应消息
  8. */
  9. private String message;
  10. /**
  11. * 响应结果
  12. */
  13. private Object result;
  14. public ResultBody() {
  15. }
  16. public ResultBody(BaseErrorInfoInterface errorInfo) {
  17. this.code = errorInfo.getResultCode();
  18. this.message = errorInfo.getResultMsg();
  19. }
  20. public String getCode() {
  21. return code;
  22. }
  23. public void setCode(String code) {
  24. this.code = code;
  25. }
  26. public String getMessage() {
  27. return message;
  28. }
  29. public void setMessage(String message) {
  30. this.message = message;
  31. }
  32. public Object getResult() {
  33. return result;
  34. }
  35. public void setResult(Object result) {
  36. this.result = result;
  37. }
  38. /**
  39. * 成功
  40. *
  41. * @return
  42. */
  43. public static ResultBody success() {
  44. return success(null);
  45. }
  46. /**
  47. * 成功
  48. * @param data
  49. * @return
  50. */
  51. public static ResultBody success(Object data) {
  52. ResultBody rb = new ResultBody();
  53. rb.setCode(CommonEnum.SUCCESS.getResultCode());
  54. rb.setMessage(CommonEnum.SUCCESS.getResultMsg());
  55. rb.setResult(data);
  56. return rb;
  57. }
  58. /**
  59. * 失败
  60. */
  61. public static ResultBody error(BaseErrorInfoInterface errorInfo) {
  62. ResultBody rb = new ResultBody();
  63. rb.setCode(errorInfo.getResultCode());
  64. rb.setMessage(errorInfo.getResultMsg());
  65. rb.setResult(null);
  66. return rb;
  67. }
  68. /**
  69. * 失败
  70. */
  71. public static ResultBody error(String code, String message) {
  72. ResultBody rb = new ResultBody();
  73. rb.setCode(code);
  74. rb.setMessage(message);
  75. rb.setResult(null);
  76. return rb;
  77. }
  78. /**
  79. * 失败
  80. */
  81. public static ResultBody error( String message) {
  82. ResultBody rb = new ResultBody();
  83. rb.setCode("-1");
  84. rb.setMessage(message);
  85. rb.setResult(null);
  86. return rb;
  87. }
  88. @Override
  89. public String toString() {
  90. return JSONObject.toJSONString(this);
  91. }
  92. }

自定义全局异常处理类

最后在来编写一个自定义全局异常处理的类。
代码如下:

  1. @ControllerAdvice
  2. public class GlobalExceptionHandler {
  3. private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
  4. /**
  5. * 处理自定义的业务异常
  6. * @param req
  7. * @param e
  8. * @return
  9. */
  10. @ExceptionHandler(value = BizException.class)
  11. @ResponseBody
  12. public ResultBody bizExceptionHandler(HttpServletRequest req, BizException e){
  13. logger.error("发生业务异常!原因是:{}",e.getErrorMsg());
  14. return ResultBody.error(e.getErrorCode(),e.getErrorMsg());
  15. }
  16. /**
  17. * 处理空指针的异常
  18. * @param req
  19. * @param e
  20. * @return
  21. */
  22. @ExceptionHandler(value =NullPointerException.class)
  23. @ResponseBody
  24. public ResultBody exceptionHandler(HttpServletRequest req, NullPointerException e){
  25. logger.error("发生空指针异常!原因是:",e);
  26. return ResultBody.error(CommonEnum.BODY_NOT_MATCH);
  27. }
  28. /**
  29. * 处理其他异常
  30. * @param req
  31. * @param e
  32. * @return
  33. */
  34. @ExceptionHandler(value =Exception.class)
  35. @ResponseBody
  36. public ResultBody exceptionHandler(HttpServletRequest req, Exception e){
  37. logger.error("未知异常!原因是:",e);
  38. return ResultBody.error(CommonEnum.INTERNAL_SERVER_ERROR);
  39. }
  40. }

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

实体类

又是万能的用户表 (▽)
代码如下:

  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. }
  32. }

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. }
  39. }

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. }
  9. }

功能测试

成功启动该程序之后,使用Postman工具来进行接口测试。
首先进行查询,查看程序正常运行是否ok,使用GET 方式进行请求。

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

返回参数为:

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

可以看到程序正常返回,并没有因自定义的全局异常而影响。
然后再来测试下自定义的异常是否能够被正确的捕获并处理。
使用POST方式进行请求

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

Body参数为:

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

返回参数为:

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

可以看到抛出的异常进行数据封装,然后将异常返回出来。
然后再来测试下空指针异常是否能够被正确的捕获并处理。在自定义全局异常中,除了定义空指针的异常处理,也定义最高级别之一的Exception异常,那么这里发生了空指针异常之后,它是回优先使用哪一个呢?这里来测试下。
使用PUT方式进行请求。

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

Body参数为:

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

返回参数为:

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

可以看到这里的的确是返回空指针的异常护理,可以得出全局异常处理优先处理子类的异常。
那么再来试试未指定其异常的处理,看该异常是否能够被捕获。
使用DELETE方式进行请求。

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

Body参数为:

  1. {"id":1}

返回参数为:

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

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