首先我们需要知道的是,java异常的基础知识和springboot对异常处理是如何支持的。Java异常的基础知识大家可以参考菜鸟教程的java教程。
- springboot对异常处理的支持
在springboot中,我们只需要定义一个全局异常处理类(用@ControllerAdvice),不需要在每个controller类重复定义。在全局异常处理类中,我们使用@ExceptionHandler指定每个方法接收并处理哪种异常。经常我们还会使用@Slf4j和@ResponseBody做日志记录和返回json格式的数据。我定义的全局异常处理类如下:
- 优雅的处理全局异常
在上面定义的全局异常类中,我们可以看到有许多奇奇怪怪的类和方法,如BizException、ResultData等。
这些类是我自己定义的,作用是让全局异常更优雅,更易扩展,下面我将详细介绍如何优雅的处理全局异常。
2.1 总概
我写的全局异常处理包括了一个通用接口CommonException,一个自定义异常枚举类ExceptionEnum、一个公共异常封装类BizException、一个全局异常处理类GlobalExceptionHandler。
2.2 通用接口CommonException
package com.hy.exception;
/**
* Description:
* 作为一个公共的异常
*
* @author houyi
* @version 1.0
* @date 2020/1/21 16:55
* @since JDK 1.8
*/
public interface CommonException {
public int getExceptionCode();
public String getExceptionMsg();
}
只定义了两个方法,目的是实现抽象为扩展提供可能(好吧,我知道你没听懂,不要着急,随着后面的阅读,我相信你会慢慢明白的)
2.3 自定义异常枚举类
package com.hy.exception;
/**
* Description:
*
* @author houyi
* @version 1.0
* @date 2020/1/21 16:57
* @since JDK 1.8
*/
public enum ExceptionEnum implements CommonException {
/**
* 所有的代码内部错误都抛出该异常
*/
INTERNAL_ERROR(500, "内部服务异常"),
/**
* 如参数校验未通过,参数为空等抛出该异常
*/
PARAMS_ERROR(100, "参数异常"),
/**
* 对爬虫等恶意访问抛出该异常
*/
REQUEST_RATE_LIMIT(300, "请求超过速率"),
/**
* token校验未通过抛出该异常
*/
TOKEN_ERROR(101, "token异常"),
/**
* token过期抛出该异常
*/
TOKEN_EXPIRED(102, "token过期");
private int exCode;
private String exMsg;
ExceptionEnum(int exCode, String exMsg) {
this.exCode = exCode;
this.exMsg = exMsg;
}
@Override
public int getExceptionCode() {
return exCode;
}
@Override
public String getExceptionMsg() {
return exMsg;
}
}
可以看到,自定义的异常枚举类包含了exCode和exMsg两个属性,同时实现了CommonException接口,将getExceptionCode和getExceptionMsg两个方法分别实现,返回exCode和exMsg属性。
其中exCode和exMsg是我们根据自己需要完成的业务和可能出现的情况自定义的(值得一提的是,在经过一段时间完整项目Demo的开发后,发现对出现的偶然性错误我们常手工抛出异常,代替以往在单独代码中的输出错误或者提示重输)
这里我们开始了第一个优雅:让枚举类继承CommonException接口。
在这里继承CommonException接口后,原有的功能不多不少,但是却对ExceptionEnum做了抽象处理,在后面的BizException类中定义一个方法要传入ExceptionEnum对象作为参数时,我们对这个参数的类型定义不用ExceptionEnum而用CommonException,这样我们以后如果需要添加行的枚举类,就不用在ExceptionEnum中修改代码,只需要再新建一个类implements CommonException然后定义要添加的异常类型就可以了。
2.4 公共异常封装类BizException
package com.hy.exception;
/**
* Description:
* 公共异常封装类
* 用于在逻辑代码中抛出异常时的封装
*
* @author houyi
* @version 1.0
* @date 2020/1/22 15:44
* @since JDK 1.8
*/
public class BizException extends Exception implements CommonException{
private int exCode;
private String exMsg;
@Override
public int getExceptionCode() {
return exCode;
}
@Override
public String getExceptionMsg() {
return exMsg;
}
/**
* 使用自定义的异常枚举类构造异常
* @param exceptionEnum
*/
public BizException(CommonException exceptionEnum){
this.exCode = exceptionEnum.getExceptionCode();
this.exMsg = exceptionEnum.getExceptionMsg();
}
public BizException(int exCode, String exMsg){
this.exCode = exCode;
this.exMsg = exMsg;
}
public BizException(int exCode){
this.exCode = exCode;
this.exMsg = "未知错误";
}
public BizException(String exMsg){
this.exCode = 10001;
this.exMsg = exMsg;
}
}
我们使用这个类来封装自定义的异常信息再抛出,这样我们在GlobalExceptionHandler类中就可以用一个单独的被@ExceptionHandler(value = BizException.class)注解的方法来处理自定义的异常。
这个类中继承Exception是为了将这个类变为异常类,实现CommonException只是为了复用,少写点代码。
同时,我们应该注意这个类的构造方法:
有一个接收CommonException类型的构造方法可以直接接收一个枚举类型,其他的则是另外零散的自定义异常信息的构造方法。
2.5 全局异常捕获处理类GlobalExceptionHandler
package com.hy.exception;
import com.hy.vo.ResultData;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* Description:
* 全局异常捕获类
* 所有的异常都会在这个类中处理
*
* @author houyi
* @version 1.0
* @date 2020/1/22 20:50
* @since JDK 1.8
*/
@ControllerAdvice
@Slf4j
@ResponseBody
public class GlobalExceptionHandler {
/**
* 捕获自定义的几种异常
*
* @param ex
* @return 异常码和异常信息封装的ResultData对象
*/
@ExceptionHandler(value = BizException.class)
public ResultData handlerBizException(Exception ex){
// 向下转型使BizException的自定义的不同于父类Exception的方法可以使用
BizException bizException = (BizException)ex;
// 获得异常码和异常信息
int exCode = bizException.getExceptionCode();
String exMsg = bizException.getExceptionMsg();
// 将异常码和异常信息封装成通用数据返回类型
ResultData resultData = ResultData.fail(exCode, exMsg);
// 对异常做日志记录,方便项目正式运行时发生异常后寻找异常发生点
log.error(exCode + ":" +exMsg,bizException);
// 向前端返回数据
return resultData;
}
/**
* 系统抛出的异常
* @param ex
* @return 固定异常码10002 和 异常信息封装的ResultData对象
*/
@ExceptionHandler(value = Exception.class)
public ResultData handlerException(Exception ex){
// 对所以其他异常,统一异常码为10002,并封装
ResultData resultData = ResultData.fail(10002,ex.getMessage());
// 日志记录
log.error(10002 + ":" +ex.getMessage(),ex);
// 返回数据
return resultData;
}
}
一路走来,我们的主角终于要上场了。在全局异常捕获处理类中,我只定义了两个类,一个BizException的捕获处理和除BizException的其他所有异常类的捕获处理。可以看到,不管加多少自定义异常枚举类,我们都只要加自定义异常枚举类。
2.6 测试