前言
上一篇文章说到,参数校验,往往需要和全局的异常拦截器来配套使用,使得返回的数据结构永远是保持一致的。参数异常springboot默认的返回结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
   | {  "timestamp": "2019-04-25T13:09:02.196+0000",  "status": 400,  "error": "Bad Request",  "errors": [  {  "codes": [  "Pattern.param.birthday",  "Pattern.birthday",  "Pattern.java.lang.String",  "Pattern"  ],  "arguments": [  {  "codes": [  "param.birthday",  "birthday"  ],  "arguments": null,  "defaultMessage": "birthday",  "code": "birthday"  },  [],  {  "defaultMessage": "\d{4}-\d{2}-\d{2}",  "codes": [  "\d{4}-\d{2}-\d{2}"  ],  "arguments": null  }  ],  "defaultMessage": "需要匹配正则表达式"\d{4}-\d{2}-\d{2}"",  "objectName": "param",  "field": "birthday",  "rejectedValue": "apple",  "bindingFailure": false,  "code": "Pattern"  }  ],  "message": "Validation failed for object='param'. Error count: 1",  "path": "/validate/notblank" }
  | 
 
不管是正常的情况,还是异常的情况,对于前端(或者app)来说,最好返回值的结构都是一致的,这样才方便解释。
定义一个BaseResult类,定义返回值的数据结构
1 2 3 4 5 6
   | public class BaseResult {  private int code;  private String message;  private Object data;   }
  | 
 
不管什么接口,都采用这样的数据结构返回给前端。比如约定code为0时是成功,其他错误定义出具体的错误码,message放错误信息,data对象放相应的数据。
定义全局异常处理器GlobalExceptionHandlerAdvice
1 2 3 4
   | @RestControllerAdvice public class GlobalExceptionHandlerAdvice {
  }
   | 
 
使用RestControllerAdvice可以标识一个类为异常捕获类。
捕获异常
通过参数异常的测试,可以知道参数有异常时会抛出org.springframework.web.bind.MethodArgumentNotValidException。我们现在手动捕获 这个异常,并且返回一个BaseResult格式的响应。
1 2 3 4 5 6 7
   | @ExceptionHandler(MethodArgumentNotValidException.class) public BaseResult handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {  BindingResult result = e.getBindingResult();  FieldError fieldError = result.getFieldError();  String defaultMessage = fieldError.getDefaultMessage();  return new BaseResult(11000, defaultMessage, null); }
   | 
 
使用ExceptionHandler可以捕获异常类型。这里捕获了参数错误会抛出的异常,然后返回了自定义的结果。这里错误码为随便填写,真实开发,建议定义一个错误码枚举类。
效果如下:

返回的结果就比较友好了,前端处理起来也方便。
异常流处理业务逻辑
使用异常来处理业务逻辑,会使代码写起来更加流畅。比如说,一个删除用户数据的方法,返回值为void(无返回值),但是当传入的用户id不存在的时候,就应该返回一个用户不存在的结果,这对于void返回值的方法来说,显得无能为力。但是,使用异常流来处理该业务逻辑,会变得非常简单。我们直接抛出一个自定义异常,然后在异常捕获器上捕获该异常,再把结果返回给前端即可。
定义一个WebException
1 2 3 4 5 6 7 8 9 10 11 12 13 14
   | public class WebException extends RuntimeException {  private int code;  private String errorMsg;  public WebException(int code, String errorMsg) {  super(errorMsg);  this.code = code;  this.errorMsg = errorMsg;  }  @Override  public synchronized Throwable fillInStackTrace() {  return this;  }   }
  | 
 
这里定义了一个运行时异常,重写了fillInStackTrace方法,重写该方法是不保留异常信息栈。因为我们使用该异常来处理业务逻辑,都是我们手动抛出的,所以也不需要保存异常信息栈了,这会提升性能。
在异常捕获器添加WebException异常捕获
1 2 3 4
   | @ExceptionHandler(WebException.class) public BaseResult handleWebException(WebException e) {  return new BaseResult(e.getCode(), e.getErrorMsg(), null); }
   | 
 
模拟一段业务逻辑,抛出WebException
在之前的UserController类,修改之前写的deleteUser方法,如下:
1 2 3 4 5 6 7 8
   | @DeleteMapping(value = "/{userId}") public Object deleteUser(@PathVariable(value = "userId") Integer userId) {  if (userId == 0) {  throw new WebException(-1, "用户不存在");  }  return new BaseResult(1, "成功", null); }
 
  | 
 
这里定义一个delete请求的接口,接收一个userId参数,如果userId等于0,则返回该用户不存在。测试结果如下:
当userId为0时,提示用户不存在

当userId为1时,提示成功.

总结
这里实现了全局异常捕获,并且介绍了异常流处理业务逻辑。这里只是一个小demo,还有很多待改进的地方。比如说,我没有定义一个错误码枚举类。在定义了错误码枚举类的前提下,修改构造BaseResult的模式,可以采用静态工厂模式来构造等。这里就不展开讨论了。