Spring boot实践之异常处理
回顾
在上一章封装返回体中,已经对请求成功的情况进行了封装,接下来便是处理异常,服务的生产者需要通过状态码此次请求是否成功,出现异常时,错误信息是什么,形如:
1 2 3 4 5
| { "code": 1, "msg": "FAILED", "data": null }
|
异常接口
可以看出只需要code
与msg
, 参考 org.springframework.http.HttpStatus
的实现,我们可以定义一个枚举来封装错误信息,对外暴露getCode
,getMsg
方法即可。由于异常属于一个基础模块,将这两个方法抽象到一个接口中。
1 2 3 4 5 6
| public interface ExceptionEntity {
Integer getCode();
String getMsg(); }
|
异常枚举
以用户模块为例,所有用户相关的业务异常信息封装到UserError
中,例如用户不存在,密码错误
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
| public enum UserError implements ExceptionEntity {
NO_SUCH_USER(1, "用户不存在"), ERROR_PASSWORD(2, "密码错误"), ;
private final Integer MODULE = 10000;
private Integer code;
private String msg;
UserError(Integer code, String msg) { this.code = code; this.msg = msg; }
@Override public Integer getCode() { return MODULE + this.code; }
@Override public String getMsg() { return this.msg; } }
|
模块标识
需要注意的地方是笔者定义了一个MODULE
字段,10000代表用户微服务,这样在拿到错误信息之后,可以很快定位报错的应用
自定义异常
1 2 3 4 5 6
| @Data
@AllArgsConstructor public class ServiceException extends RuntimeException{ ExceptionEntity error; }
|
需要说明的是错误接口与自定义异常属于公共模块,而UserError
属于用户服务
示例
之后,便可以抛出异常
1
| throw new ServiceException(UserError.ERROR_PASSWORD);
|
目前来看,我们只是较为优雅的封装了异常,此时请求接口返回的仍然是Spring boot默认的错误体,没有错误信息
1 2 3 4 5 6 7
| { "timestamp": "2018-10-18T12:28:59.150+0000", "status": 500, "error": "Internal Server Error", "message": "No message available", "path": "/user/error" }
|
统一异常处理
接下来的异常拦截方式,各路神仙都有自己的方法,笔者只说Spring boot项目中比较通用的@ControllerAdvice
,由于是Restful接口,这里使用@RestControllerAdvice
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
| @RestControllerAdvice(basePackages="com.ttyc..controller",annotations={RestController.class})
@Slf4j public class ControllerExceptionAdvisor{
@ExceptionHandler({ServiceException.class}) @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public ResponseModel handleServiceException(ServiceException ex){ Integer code = ex.getError().getCode(); String msg = ex.getError().getMsg(); log.error(msg);
ResponseModel model = new ResponseModel(); model.setCode(code); model.setMsg(msg);
return model; }
@ExceptionHandler({Exception.class}) @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public ResponseModel exception(Exception ex) { int code = HttpStatus.INTERNAL_SERVER_ERROR.value(); String msg = HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase(); log.error(msg);
ResponseModel model = new ResponseModel(); model.setCode(code); model.setMsg(msg);
return model; } }
|
具有争议的一点是捕获ServiceExcption
之后,应该返回200还是500的响应码,有的公司返回200,使用code
字段判断成功失败,这完全没有问题,但是按照Restful的开发风格,这里的@ResponseStatus
笔者返回了500,请读者根据自身情况返回响应码
测试接口与测试用例
测试接口
1 2 3 4 5
| @GetMapping("error") public boolean error(){ throw new ServiceException(UserError.NO_SUCH_USER); }
|
测试用例
1 2 3 4 5 6 7 8
| @Test public void testError() throws Exception { String result = mockMvc.perform(get("/user/error")) .andExpect(status().isInternalServerError()) .andReturn().getResponse().getContentAsString(); System.out.println(result); }
|
结果为:
1 2 3 4 5
| { "data": null, "code": 10001, "msg": "用户不存在" }
|