错误码及全局异常处理 发表于 2022-04-07 更新于 2022-04-07
字数总计: 2.7k 阅读时长: 11分钟 阅读量: 上海
写在前面 在软件开发过程中,不可避免的是需要处理各种异常,在 Java 中,处理异常的方式一般就是采用try{...}catch{...}finally{...}
代码块。在业务系统中,可能会有大量的异常处理代码块,这样不仅有大量的冗余代码,而且还影响代码的可读性。比较下面两张图:
可以看到,明显第二种的代码简洁,可读性高!此处的代码是在 Controller 层中的,在 Service 层中会有更多的异常处理代码块。
那么我们应该如何优雅的进行异常处理呢?
什么是统一异常处理 在 Spring 里,我们可以使用@ControllerAdvice 来处理一些全局性的东西,最常见的是结合@ExceptionHandler 注解用于全局异常的处理。
@ControllerAdvice 是在类上声明的注解,其用法主要有三点:
@ExceptionHandler
注解标注的方法:用于捕获 Controller 中抛出的不同类型的异常,从而达到异常全局处理的目的
@InitBinder
注解标注的方法:用于请求中注册自定义参数的解析,从而达到自定义请求参数格式的目的
@ModelAttribute
注解标注的方法:表示此方法会在执行目标 Controller 方法之前执行
跟异常处理有关的只有@ExceptionHandler
注解,从字面意思上理解,就是异常处理器
的意思,其实际作用也正是如此:若在某个Controller
类定义一个异常处理方法,并在方法上添加该注解,那么当出现指定的异常时,会执行该处理异常的方法,其可以使用SpringMVC
提供的数据绑定,比如接受一个当前抛出的Throwable
对象。
但是,这样一来,就必须在每一个Controller
类都定义一套这样的异常处理方法,因为异常可以是各种各样。这样一来,就会造成大量的冗余代码,而且若需要新增一种异常的处理逻辑,就必须修改所有Controller
类了,很不优雅。
当然你可能会说,那就定义个类似BaseController
的基类,这样总行了吧。
这种做法虽然没错,但仍不尽善尽美,因为这样的代码有一定的侵入性和耦合性。简简单单的Controller
,我为啥非得继承这样一个类呢,万一已经继承其他基类了呢。大家都知道Java
只能继承一个类。
那有没有一种方案,既不需要跟Controller
耦合,也可以将定义的 异常处理器 应用到所有控制器呢?所以注解@ControllerAdvice
出现了,简单的说,该注解可以把异常处理器应用到所有控制器,而不是单个控制器。借助该注解,我们可以实现:在独立的某个地方,比如单独一个类,定义一套对各种异常的处理机制,然后在类的签名加上注解@ControllerAdvice
,统一对 不同阶段的
、不同异常
进行处理。这就是统一异常处理的原理。
注意到上面对异常按阶段进行分类,大体可以分成:进入Controller
前的异常 和 Service
层异常,具体可以参考下图:
统一异常处理实战 通过全局统一的异常处理将自定义的错误码以 json 的格式返回给前端。
统一返回结果类 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 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 package org.jeecg.common.api.vo;import com.fasterxml.jackson.annotation .JsonIgnore;import io.swagger.annotations.ApiModel;import io.swagger.annotations.ApiModelProperty;import lombok.Data;import org.jeecg.common.constant.CommonConstant;import org.jeecg.common.constant.enums.ErrorCodeEnum;import java.io.Serializable;@Data @ApiModel(value="接口返回对象" , description="接口返回对象" ) public class Result <T > implements Serializable { private static final long serialVersionUID = 1L ; @ApiModelProperty(value = "成功标志" ) private boolean success = true ; @ApiModelProperty(value = "返回处理消息" ) private String message = "" ; @ApiModelProperty(value = "返回代码" ) private String code = "000000" ; @ApiModelProperty(value = "返回数据对象" ) private T result; @ApiModelProperty(value = "时间戳" ) private long timestamp = System.currentTimeMillis(); public Result() { } public Result(String code,String message) { this .code = code; this .message = message; } public Result<T> success(String message) { this .message = message; this .code = CommonConstant.SC_OK_200; this .success = true ; return this ; } public static<T> Result<T> OK() { Result<T> r = new Result<T>(); r.setSuccess(true ); r.setCode(CommonConstant.SC_OK_200); return r; } public static<T> Result<T> OK(T data ) { Result<T> r = new Result<T>(); r.setSuccess(true ); r.setCode(CommonConstant.SC_OK_200); r.setResult(data ); return r; } public static<T> Result<T> OK(String msg, T data ) { Result<T> r = new Result<T>(); r.setSuccess(true ); r.setCode(CommonConstant.SC_OK_200); r.setMessage(msg); r.setResult(data ); return r; } public static<T> Result<T> error(String msg, T data ) { Result<T> r = new Result<T>(); r.setSuccess(false ); r.setCode(CommonConstant.SC_INTERNAL_SERVER_ERROR_500); r.setMessage(msg); r.setResult(data ); return r; } public static<T> Result<T> error(String msg) { return error(CommonConstant.SC_INTERNAL_SERVER_ERROR_500, msg); } public static<T> Result<T> error(ErrorCodeEnum errorCodeEnum) { return error(errorCodeEnum.getCode(), errorCodeEnum.getMessage()); } public static<T> Result<T> error(String code, String msg) { Result<T> r = new Result<T>(); r.setCode(code); r.setMessage(msg); r.setSuccess(false ); return r; } public Result<T> error500(String message) { this .message = message; this .code = CommonConstant.SC_INTERNAL_SERVER_ERROR_500; this .success = false ; return this ; } }
错误码枚举类 需要定义一个枚举类,包含所有的自定义的结果码:
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 43 44 45 46 47 package org.jeecg .common .constant .enums ; import lombok.AllArgsConstructor ;import lombok.Getter ;import lombok.NoArgsConstructor ;@Getter @AllArgsConstructor @NoArgsConstructor public enum ResultCodeEnum { SUCCESS_ERROR ("000000" ,"成功" ), CLIENT_ERROR ("A0001" ,"用户端错误" ), SYSTEM_ERROR ("B0001" ,"系统执行出错" ), TPA_ERROR ("C0001" ,"调用第三方服务出错" ); private String code; private String message; }
自定义业务异常类 自定义一个业务异常类,以后和业务有关的异常通通抛出这个异常类,只需将定义好的错误枚举传入即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package org.jeecg.common.exception;import lombok.Getter ;import lombok.Setter ;import org.jeecg.common.constant.enums.ResultCodeEnum ;@Getter @Setter public class VanxSoftException extends RuntimeException { private static final long serialVersionUID = 1 L; private ResultCodeEnum resultCodeEnum; public VanxSoftException (ResultCodeEnum resultCodeEnum){ super (resultCodeEnum.getMessage()); this .resultCodeEnum = resultCodeEnum; } }
全局异常处理类 定义一个全局异常处理类
通过 @RestControllerAdvice
指定该类为 Controller
增强类并返回 json
到前端
通过 @ExceptionHandler
自定义捕获的异常类型
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 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 package org.jeecg.common.exception ; import lombok.extern.slf4j.Slf4j; import org.apache.shiro.authz.AuthorizationException; import org.apache.shiro.authz.UnauthorizedException; import org.jeecg.common.api.vo.Result; import org.springframework.beans.factory.annotation.Value; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.dao.DuplicateKeyException; import org.springframework.data.redis.connection.PoolException; import org.springframework.http.HttpStatus; import org.springframework.web.HttpRequestMethodNotSupportedException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.multipart.MaxUploadSizeExceededException; import org.springframework.web.servlet.NoHandlerFoundException; @RestControllerAdvice @Slf4j public class JeecgBootExceptionHandler { @Value("${spring.servlet.multipart.max-file-size}" ) private String maxFileSize; @ExceptionHandler(JeecgBootException.class ) public Result<?> handleJeecgBootException(JeecgBootException e ) { log.error(e.getMessage() , e); return Result . error(e.getMessage() ); } @ExceptionHandler(JeecgBoot401Exception.class ) @ResponseStatus(HttpStatus.UNAUTHORIZED) public Result<?> handleJeecgBoot401Exception(JeecgBoot401Exception e ) { log.error(e.getMessage() , e); return new Result("401" , e .getMessage () ); } @ExceptionHandler(VanxSoftException.class ) public Result<?> handleVanxSoftException(VanxSoftException e ) { log.error(e.getMessage() , e); return Result . error(e.getResultCodeEnum() ); } @ExceptionHandler(NoHandlerFoundException.class ) public Result<?> handlerNoFoundException(Exception e ) { log.error(e.getMessage() , e); return Result . error("404" , "路径不存在,请检查路径是否正确" ); } @ExceptionHandler(DuplicateKeyException.class ) public Result<?> handleDuplicateKeyException(DuplicateKeyException e ) { log.error(e.getMessage() , e); return Result . error("数据库中已存在该记录" ); } @ExceptionHandler({UnauthorizedException.class , AuthorizationException.class }) public Result<?> handleAuthorizationException(AuthorizationException e ) { log.error(e.getMessage() , e); return Result . noauth("没有权限,请联系管理员授权" ); } @ExceptionHandler(Exception.class ) public Result<?> handleException(Exception e ) { log.error(e.getMessage() , e); return Result . error("操作失败," + e.getMessage() ); } @ExceptionHandler(HttpRequestMethodNotSupportedException.class ) public Result<?> handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e ) { StringBuffer sb = new StringBuffer() ; sb.append("不支持" ); sb.append(e.getMethod() ); sb.append("请求方法," ); sb.append("支持以下" ); String[] methods = e.getSupportedMethods() ; if (methods != null) { for (String str : methods) { sb.append(str); sb.append("、" ); } } log.error(sb.to String() , e); return Result . error("405" , sb.to String() ); } @ExceptionHandler(MaxUploadSizeExceededException.class ) public Result<?> handleMaxUploadSizeExceededException(MaxUploadSizeExceededException e ) { log.error(e.getMessage() , e); return Result . error(String . format("文件大小超出%s限制, 请压缩或降低文件质量! " , maxFileSize)); } @ExceptionHandler(DataIntegrityViolationException.class ) public Result<?> handleDataIntegrityViolationException(DataIntegrityViolationException e ) { log.error(e.getMessage() , e); return Result . error("字段太长,超出数据库字段的长度" ); } @ExceptionHandler(PoolException.class ) public Result<?> handlePoolException(PoolException e ) { log.error(e.getMessage() , e); return Result . error("Redis 连接异常!" ); } }
测试 编写 TestController 测试 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 package org.jeecg.modules.exception.controller;import com.alibaba.fastjson.JSONObject;import io.swagger.annotations.Api;import io.swagger.annotations.ApiOperation;import lombok.extern.slf4j.Slf4j;import org.jeecg.common.api.vo.Result;import org.jeecg.common.constant.enums.ResultCodeEnum;import org.jeecg.common.exception.VanxSoftException;import org.springframework.web.bind.annotation .RequestMapping;import org.springframework.web.bind.annotation .RequestMethod;import org.springframework.web.bind.annotation .RestController;@RestController @RequestMapping("/exception" ) @Api(tags="全局异常处理" ) @Slf4j public class ExceptionController { @ApiOperation("测试请求" ) @RequestMapping(value = "/test" , method = RequestMethod.POST) public Result<JSONObject> testClientError(){ throw new VanxSoftException(ResultCodeEnum.SYSTEM_ERROR); } }