Spring boot实践之异常处理

Spring boot实践之异常处理

回顾

在上一章封装返回体中,已经对请求成功的情况进行了封装,接下来便是处理异常,服务的生产者需要通过状态码此次请求是否成功,出现异常时,错误信息是什么,形如:

1
2
3
4
5
{
"code": 1,
"msg": "FAILED",
"data": null
}

异常接口

可以看出只需要codemsg, 参考 org.springframework.http.HttpStatus的实现,我们可以定义一个枚举来封装错误信息,对外暴露getCodegetMsg方法即可。由于异常属于一个基础模块,将这两个方法抽象到一个接口中。

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
// lombok自动生成构造方法
@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})
// lombok的日志简写
@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;
}

/**
* 其他错误
* @param ex
* @return
*/
@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": "用户不存在"
}
Author: 紫夜
Link: https://greedypirate.github.io/2018/10/13/Spring-boot实践之异常处理/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.
支付宝打赏
微信打赏