异常处理
异常处理问题分析
1、异常如何处理
问题引入
- 针对代码中的异常,常规有两种处理方式,一种throws直接抛出,另一种try…catch捕获。
- 在项目开发中,有可能存在人为逻辑的异常,也可能为取得异常的详情,或是保证程序在异常时继续向下执行,会采用第二种处理方式。
- 问题:代码中每一处异常都来捕获,会使代码冗余且不利于维护
解决思路
- 定义一个全局异常处理类,返回统一规范的异常信息;
- 处理逻辑,先判定是否会出现异常,再执行后续具体的业务。
业务举例
- 增加员工,处理流程:1先根据员工编号查询员工对象,2判断员工对象是否有信息,即是否不为空,3若有信息,则说明已存在,无需再添加,若不是,则直接添加
public class MyService {
// 注入dao层
@Autowired
EmployeeecMapper employeeecMapper;
/**
* 添加员工信息
* @param employee 员工对象
* @return 影响的行数
*/
public int add(Employee employee) {
// 根据id查询员工对象
Employeeec emp = employeeecMapper.selectByPrimaryKey(employee.getId());
// 判断是否已有该员工
if (emp != null){
// 已有,抛出异常,异常信息为已有该员工
throw new RuntimeException("异常代码:1201,错误信息:该员工已存在");
}
// 没有,插入该员工
return employeeecMapper.insert(emp);
}
}
2、异常处理流程
- 业务中存在运行时异常和业务逻辑异常,前者不运行时很难察觉,后者在遍及业务时就可以定义出来,因此异常分为不可预知异常和可知异常。处理流程如下:
- 自定义全局异常类,使用
@ControllerAdvice
,控制器增强 - 自定义错误代码及错误信息,两种异常最终会采用统一的信息格式来表示,错误代码+错误信息。
- 对于可预知的异常由程序员在代码中主动抛出,由
SpringMVC
统一捕获。定义异常信息类,提供错误代码和错误信息,捕获自定义异常时,直接将该对象返回。 - 不可预知异常通常是由于系统出现bug、或一些外界因素(如网络波动、服务器宕机等),异常类型为
RuntimeException
类型(运行时异常)。定义一个map,将常见的异常存入其中,并定义错误代码。对于其他不常见的异常,即map中没有的,同一一个异常对象返回即可。
- 自定义全局异常类,使用
3、异常处理代码流程
-
可知异常
1、定义打印异常信息与返回结果的接口
public interface IErrorCode {
// 操作结果代码
int getCode();
// 提示信息
String getMessage();
}
2、定义打印异常信息的枚举类和返回结果类
@ToString
public enum ResultCode implements IErrorCode {
SUCCESS(200, "操作成功"),
VALIDATE_FAILED(400, "Invalid input(无效的输入)"),
UNAUTHORIZED(401, "暂未登录或token已经过期"),
FORBIDDEN(403, "403 Forbidden(请求被拒绝)没有相关权限"),
NOTFOUND(404, "not found(没有找到相关资源)"),
VALIDATE_METHOD(405, "请求方法被禁止"),
FAILED(500, "服务器内部错误"),
METHOD_ARGUMENT_NOT_VALID(10000,"参数校验失败"),
REPETITIVE_OPERATION(10001, "请勿重复操作"),
ACCESS_LIMIT(10002, "请求太频繁, 请稍后再试"),
EXEC_FAILED(10003, "操作失败");
private int code;
private String message;
private ResultCode(int code, String message) {
this.code = code;
this.message = message;
}
@Override
public int getCode() {
return code;
}
@Override
public String getMessage() {
return message;
}
}
/**
* 通用返回对象
*/
@ApiModel(description = "结果对象")
@Data
@ToString
public class CommonResult<T> {
@ApiModelProperty(value = "状态码",required = true)
private int code;
@ApiModelProperty(value = "消息",required = true)
private String message;
@ApiModelProperty(value = "数据",required = true)
private T data;
protected CommonResult() {
}
protected CommonResult(int code, String message) {
this.code = code;
this.message = message;
}
protected CommonResult(int code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
}
/**
* 成功返回结果
*
*/
public static CommonResult success() {
return new CommonResult(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage());
}
/**
* 成功返回结果
*
* @param data 获取的数据
*/
public static <T> CommonResult<T> success(T data) {
return new CommonResult<T>(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage(), data);
}
/**
* 成功返回结果
*
* @param data 获取的数据
* @param message 提示信息
*/
public static <T> CommonResult<T> success(T data, String message) {
return new CommonResult<T>(ResultCode.SUCCESS.getCode(), message, data);
}
/**
* 失败返回结果
* @param errorCode 错误码
*/
public static <T> CommonResult<T> failed(IErrorCode errorCode) {
return new CommonResult<T>(errorCode.getCode(), errorCode.getMessage());
}
/**
* 失败返回结果
* @param code 错误码
* @param message 错误信息
*/
public static <T> CommonResult<T> failed(int code,String message) {
return new CommonResult<T>(code, message);
}
/**
* 失败返回结果
* @param message 提示信息
*/
public static <T> CommonResult<T> failed(String message) {
return new CommonResult<T>(ResultCode.FAILED.getCode(), message);
}
/**
* 失败返回结果
*/
public static <T> CommonResult<T> failed() {
return failed(ResultCode.FAILED);
}
/**
* 参数验证失败返回结果
*/
public static <T> CommonResult<T> validateFailed() {
return failed(ResultCode.VALIDATE_FAILED);
}
/**
* 参数验证失败返回结果
* @param message 提示信息
*/
public static <T> CommonResult<T> validateFailed(String message) {
return new CommonResult<T>(ResultCode.VALIDATE_FAILED.getCode(), message);
}
/**
* 参数校验失败
* @param data 详细信息
* @param <T>
* @return
*/
public static <T> CommonResult<T> methodArgumentNotValid(T data){
return new CommonResult<T>(ResultCode.METHOD_ARGUMENT_NOT_VALID.getCode(),ResultCode.METHOD_ARGUMENT_NOT_VALID.getMessage(),data);
}
/**
* 未登录返回结果
*/
public static <T> CommonResult<T> unauthorized() {
return new CommonResult<T>(ResultCode.UNAUTHORIZED.getCode(), ResultCode.UNAUTHORIZED.getMessage());
}
/**
* 未授权返回结果
*/
public static <T> CommonResult<T> forbidden(T data) {
return new CommonResult<T>(ResultCode.FORBIDDEN.getCode(), ResultCode.FORBIDDEN.getMessage());
}
/**
* 请求方法被禁止返回结果
*/
public static <T> CommonResult<T> validateMethodFailed() {
return new CommonResult<T>(ResultCode.VALIDATE_METHOD.getCode(), ResultCode.VALIDATE_METHOD.getMessage());
}
}
3、定义错误异常类
/**
* @author Admin
* API异常类
*/
@Data
public class CustomException extends RuntimeException {
private int code;
private String msg;
public CustomException(){}
public CustomException(IErrorCode resultCode){
this.code = resultCode.getCode();
this.msg = resultCode.getMessage();
}
public CustomException(int code, String msg) {
this.code = code;
this.msg = msg;
}
}
4、定义异常抛出类
/**
* @author Admin
* 异常抛出类
*/
public class ExceptionCast {
/**
* 抛出异常
* @param resultCode
*/
public static void cast(ResultCode resultCode){
throw new CustomException(resultCode);
}
/**
* 抛出异常
* @param code 状态码
* @param msg 异常消息
*/
public static void cast(int code,String msg){
throw new CustomException(code,msg);
}
}
5、定义异常捕获类,使用ControllerAdvice
或RestControllerAdvice
控制器增强的注解,并在捕获APIException异常的方法上加ExceptionHandler
注解,即可捕获该类的所有异常,返回json数据。
/**
* @author Admin
* 全局异常处理增强
*/
@RestControllerAdvice
@Slf4j
public class GlobalExceptionAdvice {
/**
* 捕获CustomException类异常
* @param e
* @return 结果信息,json数据
*/
@ExceptionHandler(CustomException.class)
public CommonResult customExceptionHandler(ApiException e) {
log.error("CustomException: ", e);
return CommonResult.failed(e.getCode(),e.getMsg());
}
}
6、在业务中抛出异常
public class MyService {
@Autowired
private EmployeeecMapper employeeecMapper;
public int add(Employee employee) {
Employeeec emp = employeeecMapper.selectByPrimaryKey(employee.getId());
if (emp != null){
ExceptionCast.cast(ResultCode.EXEC_FAILED);
}
return employeeecMapper.insert(emp);
}
}
- 不可知异常处理
1、类似可知异常,先在ResultCode类中添加错误代码,如:
FORBIDDEN(403, "403 Forbidden(请求被拒绝)没有相关权限"),
2、在异常捕获类中添加不可知异常的捕获方法。该方法中,定义一个只读的map存储异常类型的错误代码的映射,map中没有的元素,统一用错误代码9999来定义。
UNKNOWN_ERROR(9999,"未知异常")
// 定义map,存储常见错误信息。该类map不可修改
private static ImmutableMap<Class<? extends Throwable>,ResultCode> EXCEPTIONS;
// 构建ImmutableMap
protected static ImmutableMap.Builder<Class<? extends Throwable>,ResultCode> builder = ImmutableMap.builder();
static {
builder.put(HttpMessageNotReadableException.class,ResultCode.VALIDATE_FAILED);
builder.put(HttpRequestMethodNotSupportedException.class,ResultCode.VALIDATE_METHOD);
builder.put(BindException.class,ResultCode.VALIDATE_FAILED);
}
@ExceptionHandler(Exception.class)
public CommonResult exceptionHandler(Exception e) {
log.error("Exception: ", e);
if(exceptions == null){
exceptions = builder.build();
}
ResultCode resultCode = exceptions.get(e.getClass());
if (resultCode != null) {
return CommonResult.failed(resultCode);
}else{
return CommonResult.failed(ResultCode.UNKNOWN_ERROR);
}
}
/**
* 捕获CustomException类异常
* @param e
* @return 结果信息,json数据
*/
@ExceptionHandler(CustomException.class)
public CommonResult customExceptionHandler(CustomException e) {
log.error("CustomException: ", e);
return CommonResult.failed(e.getCode(),e.getMsg());
}
/**
* 捕获数据校验异常
* @param e
* @return
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public CommonResult methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) {
log.error("MethodArgumentNotValidException: ", e);
ObjectError objectError = e.getBindingResult().getAllErrors().get(0);
return CommonResult.methodArgumentNotValid(objectError.getDefaultMessage());
}