项目全局异常处理

起因

1567479454980

这是service层的一个业务方法,业务功能是:查询某些二维码数据。可以看到我们在这个方法里面除了主要的业务逻辑,还有数据非空判断,以及异常处理等等。

如果我们不仅仅是在service层添加try catch异常处理,在controller层调用service方法的地方也添加了try catch这样的异常处理代码,那么代码冗余严重且不易维护

总结-上诉代码存在两个问题

1、上边的代码只要操作不成功仅向用户返回“错误代码:-1,失败信息:操作失败”,无法区别具体的错误信
息。–参见第三处代码
2、service方法在执行过程出现异常在哪捕获?在service中需要都加try/catch,如果在controller也需要添加
try/catch,代码冗余严重且不易维护。

以下项目是 springboot2.0.1 下开发

解决方式

1、在Service方法中的编码顺序是先校验判断,有问题则抛出具体的异常信息,最后执行具体的业务操作,返回成
功信息。

2、在统一异常处理类中去捕获异常,无需controller捕获异常,向用户返回统一规范的响应信息。

异常处理流程

系统对异常的处理使用统一的异常处理流程:

1、自定义异常类型。

2、自定义错误代码及错误信息。

3、对于可预知的异常由程序员在代码中主动抛出,由SpringMVC统一捕获。可预知异常是程序员在代码中手动抛出本系统定义的特定异常类型,由于是程序员抛出的异常,通常异常信息比较齐全,程序员在抛出时会指定错误代码及错误信息,获取异常信息也比较方便。

4、对于不可预知的异常(运行时异常)由SpringMVC统一捕获Exception类型的异常。不可预知异常通常是由于系统出现bug、或一些不要抗拒的错误(比如网络中断、服务器宕机等),异常类型为
RuntimeException类型(运行时异常)。

5、可预知的异常及不可预知的运行时异常最终会采用统一的信息格式(错误代码+错误信息)来表示,最终也会随请求响应给客户端。

1567479831709

1、在controller、service、dao中程序员抛出自定义异常;springMVC框架抛出框架异常类型
2、统一由异常捕获类捕获异常,并进行处理
3、捕获到自定义异常则直接取出错误代码及错误信息,响应给用户。

4、捕获到非自定义异常类型首先从Map中找该异常类型是否对应具体的错误代码,如果有则取出错误代码和错误
信息并响应给用户,如果从Map中找不到异常类型所对应的错误代码则统一为99999错误代码并响应给用户。

5、将错误代码及错误信息以Json格式响应给用户。

特别解释

不可知异常我们在上图中有两个分支,左边的分支表示的是有些不可知异常我们是知道的(这个可能比较拗口),例如SQLException,HttpMessageNotReadableException(消息转化异常),这些异常我们是可以特别处理的,可以给其标注响应的错误信息,而不用走右边的99999默认消息异常处理分支。

实现

定义常用错误代码接口-错误信息格式

public interface ResultCode {
//操作是否成功,true为成功,false操作失败
boolean success();
//操作代码
int code();
//提示信息
String message();
}

根据这个信息格式我们可以自定义我们的错误实现。例如通用的消息模块CommonCode里面包含了常用的错误消息例子、文件上传消息模块FileSystemCode、认证消息模块AuthCode等等。满足我们自己业务的相关提示和异常提示。

定义通用的消息模块CommonCode

public enum CommonCode implements ResultCode{
INVALID_PARAM(false,10003,"非法参数!"),
SUCCESS(true,10000,"操作成功!"),
FAIL(false,11111,"操作失败!"),
UNAUTHENTICATED(false,10001,"此操作需要登陆系统!"),
UNAUTHORISE(false,10002,"权限不足,无权操作!"),
SERVER_ERROR(false,99999,"抱歉,系统繁忙,请稍后重试!");
//操作是否成功
boolean success;
//操作代码
int code;
//提示信息
String message;
private CommonCode(boolean success,int code, String message){
this.success = success;
this.code = code;
this.message = message;
}
@Override
public boolean success() {
return success;
}
@Override
public int code() {
return code;
}
@Override
public String message() {
return message;
}
}

文件上传消息模块FileSystemCode-可选

public enum FileSystemCode implements ResultCode {
FS_UPLOADFILE_FILEISNULL(false,25001,"上传文件为空!"),
FS_UPLOADFILE_BUSINESSISNULL(false,25002,"业务Id为空!"),
FS_UPLOADFILE_SERVERFAIL(false,25003,"上传文件服务器失败!"),
FS_DELETEFILE_NOTEXISTS(false,25004,"删除的文件不存在!"),
FS_DELETEFILE_DBFAIL(false,25005,"删除文件信息失败!"),
FS_DELETEFILE_SERVERFAIL(false,25006,"删除文件失败!"),
FS_UPLOADFILE_METAERROR(false,25007,"上传文件的元信息请使用json格式!"),
FS_UPLOADFILE_USERISNULL(false,25008,"上传文件用户为空!");
//操作代码
boolean success;
//操作代码
int code;
//提示信息
String message;
private FileSystemCode(boolean success, int code, String message){
this.success = success;
this.code = code;
this.message = message;
}
@Override
public boolean success() {
return success;
}
@Override
public int code() {
return code;
}
@Override
public String message() {
return message;
}
}

基础的消息格式我们已经做完了,那么接下来就是定义我们的异常

可预知异常和不可预知异常处理

(1)自定义异常类

/**
* 自定义异常类型
**/
public class CustomException extends RuntimeException {
//错误代码
ResultCode resultCode;
public CustomException(ResultCode resultCode){
this.resultCode = resultCode;
}
public ResultCode getResultCode(){
return resultCode;
}
}

(2)异常抛出类

/**
* 异常抛出类,业务端代码直接调用即可
* 避免在service或者controller端书写throw new CustomException(resultCode);这样的重复性代码
*可以直接使用ExceptionCast.cast()的方式直接抛出异常
**/
public class ExceptionCast {
public static void cast(ResultCode resultCode){
throw new CustomException(resultCode);
}
}

(3)异常捕获类

使用 @ControllerAdvice和@ExceptionHandler注解来捕获指定类型的异常

/**
* 统一异常捕获类
**/
@ControllerAdvice//控制器增强
public class ExceptionCatch {
private static final Logger LOGGER = LoggerFactory.getLogger(ExceptionCatch.class);
//定义map,配置异常类型所对应的错误代码 - 这个就是存储我们在不可预知异常的左边分支的异常
private static ImmutableMap<Class<? extends Throwable>,ResultCode> EXCEPTIONS;
//定义map的builder对象,去构建ImmutableMap
protected static ImmutableMap.Builder<Class<? extends Throwable>,ResultCode> builder = ImmutableMap.builder();
//捕获CustomException此类异常 -处理可预知异常关键方法(1)
@ExceptionHandler(CustomException.class)
@ResponseBody//返回给controller的json串格式的异常消息格式
public ResponseResult customException(CustomException customException){
customException.printStackTrace();
//记录日志
LOGGER.error("catch exception:{}",customException.getMessage());
ResultCode resultCode = customException.getResultCode();
return new ResponseResult(resultCode);
}
//捕获Exception此类异常 - 处理不可预知异常关键方法(2)
//里面包含我们在异常处理流程中<处理不可预知异常>的左右两个分支
//根据异常的全类名在我们自定义的异常map中查找是否存在已经自定义的不可预知异常,存在直接返回,不存在则当做99999全局异常处理
@ExceptionHandler(Exception.class)
@ResponseBody
public ResponseResult exception(Exception exception){
exception.printStackTrace();
//记录日志
LOGGER.error("catch exception:{}",exception.getMessage());
if(EXCEPTIONS == null){
EXCEPTIONS = builder.build();//EXCEPTIONS构建成功
}
//从EXCEPTIONS中找异常类型所对应的错误代码,如果找到了将错误代码响应给用户,如果找不到给用户响应99999异常
ResultCode resultCode = EXCEPTIONS.get(exception.getClass());
if(resultCode !=null){
return new ResponseResult(resultCode);
}else{
//返回99999异常
return new ResponseResult(CommonCode.SERVER_ERROR);
}
}
static {
//定义异常类型所对应的错误代码
builder.put(HttpMessageNotReadableException.class,CommonCode.INVALID_PARAM);
}
}

测试使用

改造上诉代码,try catch语句可以去掉,而且可以返回我们精准自定义的错误信息

1567481359314

总结

实现的关键点在于,使用 @ControllerAdvice和@ExceptionHandler注解来捕获指定类型的异常

@ExceptionHandler

//该注解作用对象为方法
@Target({ElementType.METHOD})
//在运行时有效
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExceptionHandler {
//value()可以指定异常类
Class<? extends Throwable>[] value() default {};
}

@ControllerAdvice 增强版控制器

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface ControllerAdvice {
@AliasFor("basePackages")
String[] value() default {};
@AliasFor("value")
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
Class<?>[] assignableTypes() default {};
Class<? extends Annotation>[] annotations() default {};
}
如果你感觉文章对你又些许感悟,你可以支持我!!
-------------本文结束感谢您的阅读-------------