叨叨
- 周末闲来无事,一直觉得当前项目的异常模块的结构设计不合适,经常有人问:这个异常提示放在哪里合适? 这个异常和这个异常是一样的啊! 异常提示放的乱七八糟…来梳理下吧.
- 项目中通常是自定义一个异常类,并继承RuntimeException.通过提供构造方法来传入自定义异常信息.这种设计的结果就是代码中随处可见的
throw new XXException("xxxx");
魔法值,相同的异常提示也没法复用; - 为了异常消息能复用(即字符串异常提示),定义一些接口类,将这些提示信息作为常量.当前项目就是这样做的.不知道什么时候开始,接口类中各种重复的异常提示,可能是数量太多,大家也不再去找有没有现成的,直接重新定义就是了.
项目代码就不贴了,反正就是异常类型的维护是"你中有我,我中有你".总之就是很乱.跟项目管理和开发规范也有关系吧.
设计异常
- 定义系统模块枚举类,功能模块区分出来.
- 异常中最好能体现异常所在的功能模块,防止异常提示信息重复时查找问题不方便
- 取消自定义异常类传入字符串的构造函数,传入指定类型,需要先维护
- 异常信息的维护类加入
模块
属性(强制),作为命名空间,同一模块的异常信息维护在一起,方便查找,一定程度上也能防重复定义,也可以通过类文件进行划分,当然这个属于项目工程和编码规范
定义异常提示枚举
通过接口维护异常提示信息,无法强制已提示信息中添加模块属性,此处使用枚举类
通过定义多个枚举类对模块进行分类,再通过模块
字段分类,比如此处demo定义两个枚举类: 业务处理异常BizProcessErrorEnum
, 请求参数异常RequestParamErrorEnum
(使用validation则忽略),BizProcessErrorEnum
中又有用户模块
,合同模块
,但这些都是在进行业务逻辑处理时发生的异常,所以放在一个类中.
// 业务处理异常枚举类
public enum BizProcessErrorEnum implements BizExceptionType{
// 用户模块
USER_NOT_FOUND(SysModuleEnums.USER,"用户不存在"),
// 合同模块
DATE_FORMAT_ERROR(SysModuleEnums.CONTRACT,"时间格式错误"),
// other
;
private SysModuleEnums module;
private String msg;
BizProcessErrorEnum(SysModuleEnums module, String msg) {
this.module = module;
this.msg = msg;
}
@Override
public String getMessage() {
return msg;
}
@Override
public SysModuleEnums getModuleEnum() {
return module;
}
}
// 请求参数错误枚举类
public enum RequestParamErrorEnum implements BizExceptionType{
DEMO(SysModuleEnums.CONFIG,"字典项重复")
// other
;
private SysModuleEnums module;
private String msg;
RequestParamErrorEnum(SysModuleEnums module, String msg) {
this.module = module;
this.msg = msg;
}
@Override
public String getMessage() {
return msg;
}
@Override
public SysModuleEnums getModuleEnum() {
return module;
}
}
异常归类
定义接口,所有定义的枚举实现该接口,限定自定义异常的传参
public interface BizExceptionType {
/**
* 获取描述信息
* @return
*/
String getMessage();
/**
* 获取模块枚举
* @return
*/
SysModuleEnums getModuleEnum();
}
自定义异常类
public class BaseBizException extends RuntimeException{
// 模块
private SysModuleEnums module;
// 异常信息
private String message;
// 异常提示枚举类
private BizExceptionType errorEnum;
// 构造只能传入BizExceptionType,需要在对应模块提前维护
public BaseBizException(BizExceptionType errorEnum) {
this.module = errorEnum.getModuleEnum();
this.errorEnum = errorEnum;
this.message = errorEnum.getMessage();
}
public SysModuleEnums getModule() {
return module;
}
@Override
public String getMessage() {
return message;
}
public String getErrorEnum() {
return errorEnum.getModuleEnumString();
}
}
全局异常处理
@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
@ExceptionHandler(BaseBizException.class)
public ResponseEntity<BaseResponse<Void>> forBizException(BaseBizException e){
log.error("异常模块:{},异常内容:{}",e.getModule().name(),e.getMessage());
return new ResponseEntity<>(new BaseResponse<>(BizStatus.ERROR,null,e.getMessage()),HttpStatus.OK);
}
@ExceptionHandler(RuntimeException.class)
public ResponseEntity<BaseResponse<Void>> forRuntimeException(RuntimeException e){
log.error("异常内容:{}",e.getMessage());
return new ResponseEntity<>(new BaseResponse<>(BizStatus.ERROR, null, e.getMessage()), HttpStatus.OK);
}
}
测试
@RestController
@RequestMapping("/exception")
public class DemoController{
@PostMapping("/create")
public ResponseEntity<Void> demo(@RequestBody SysUser user){
if (user.getAge() == 0) {
throw new BaseBizException(BizProcessErrorEnum.USER_NOT_FOUND);
}
return new ResponseEntity<>(HttpStatus.OK);
}
@PostMapping("/create1")
public ResponseEntity<Void> demo1(@RequestBody SysUser user){
if (user.getAge() == 0) {
int i = 1/0;
}
return new ResponseEntity<>(HttpStatus.OK);
}
}
@Data
class SysUser{
private String name;
private int age;
}
# 请求
POST http://localhost:8080/exception/create
Content-Type: application/json
{
"name": "wyy",
"age": 0
}
# 日志输出
2021-08-21 19:12:39.423 ERROR 68955 --- [nio-8080-exec-1] demo.wyy.config.GlobalExceptionHandler : 异常模块:USER,异常内容:用户不存在
# 响应
{
"status": "ERROR",
"data": null,
"msg": "用户不存在"
}
-------------------------------------------------------------------------------------------------
# 请求
POST http://localhost:8080/exception/create1
Content-Type: application/json
{
"name": "wyy",
"age": 0
}
# 日志输出
2021-08-21 19:26:09.583 ERROR 68955 --- [nio-8080-exec-3] demo.wyy.config.GlobalExceptionHandler : 异常内容:/ by zero
# 响应
{
"status": "ERROR",
"data": null,
"msg": "/ by zero"
}
小记
按照这种设计,在业务层面对异常提示做大致的划分,从而确定几个枚举类,每个枚举类中通过系统的功能模块进行标识分组,清晰了一些.水平有限,如果哪位大佬项目中有好的实践方案,交流一下,谢谢.