前言
springboot内置的/error错误页面并不一定适用我们的项目,这时候就需要进行自定义统一异常处理,本文记录springboot进行自定义统一异常处理。
1、使用@ControllerAdvice、@RestControllerAdvice捕获运行时异常。
2、重写ErrorController,手动抛出自定义ErrorPageException异常,方便404、403等被统一处理。
官网文档相关介绍:
https://docs.spring.io/spring-boot/docs/2.1.0.RELEASE/reference/htmlsingle/#boot-features-error-handling
代码
项目结构
引入我们父类pom即可,无需引入其他依赖
开始之前,需要先定下统一返回对象、自定义异常枚举类
/** * 自定义异常枚举类 */ public enum ErrorEnum { //自定义系列 USER_NAME_IS_NOT_NULL("10001","【参数校验】用户名不能为空"), PWD_IS_NOT_NULL("10002","【参数校验】密码不能为空"), //400系列 BAD_REQUEST("400","请求的数据格式不符!"), UNAUTHORIZED("401","登录凭证过期!"), FORBIDDEN("403","抱歉,你无权限访问!"), NOT_FOUND("404", "请求的资源找不到!"), //500系列 INTERNAL_SERVER_ERROR("500", "服务器内部错误!"), SERVICE_UNAVAILABLE("503","服务器正忙,请稍后再试!"), //未知异常 UNKNOWN("10000","未知异常!"); /** 错误码 */ private String code; /** 错误描述 */ private String msg; ErrorEnum(String code, String msg) { this.code = code; this.msg = msg; } public String getCode() { return code; } public String getMsg() { return msg; } }
/** * 统一返回对象 */ @Data public class Result<T> implements Serializable { /** * 通信数据 */ private T data; /** * 通信状态 */ private boolean flag = true; /** * 通信描述 */ private String msg = "操作成功"; /** * 通过静态方法获取实例 */ public static <T> Result<T> of(T data) { return new Result<>(data); } public static <T> Result<T> of(T data, boolean flag) { return new Result<>(data, flag); } public static <T> Result<T> of(T data, boolean flag, String msg) { return new Result<>(data, flag, msg); } public static <T> Result<T> error(ErrorEnum errorEnum) { return new Result(errorEnum.getCode(), false, errorEnum.getMsg()); } @Deprecated public Result() { } private Result(T data) { this.data = data; } private Result(T data, boolean flag) { this.data = data; this.flag = flag; } private Result(T data, boolean flag, String msg) { this.data = data; this.flag = flag; this.msg = msg; } }
新增两个自定义异常,便于统一处理时捕获异常
/** * 自定义业务异常 */ public class ServiceException extends RuntimeException { /** * 自定义异常枚举类 */ private ErrorEnum errorEnum; /** * 错误码 */ private String code; /** * 错误信息 */ private String errorMsg; public ServiceException() { super(); } public ServiceException(ErrorEnum errorEnum) { super("{code:" + errorEnum.getCode() + ",errorMsg:" + errorEnum.getMsg() + "}"); this.errorEnum = errorEnum; this.code = errorEnum.getCode(); this.errorMsg = errorEnum.getMsg(); } public ServiceException(String code,String errorMsg) { super("{code:" + code + ",errorMsg:" + errorMsg + "}"); this.code = code; this.errorMsg = errorMsg; } public ErrorEnum getErrorEnum() { return errorEnum; } public String getErrorMsg() { return errorMsg; } public void setErrorMsg(String errorMsg) { this.errorMsg = errorMsg; } public String getCode() { return code; } public void setCode(String code) { this.code = code; } }
/** * 自定义错误页面异常 */ public class ErrorPageException extends ServiceException { public ErrorPageException(ErrorEnum errorEnum) { super(errorEnum); } }
重写ErrorController,不在跳转原生错误页面,而是抛出我们的自定义异常
/** * 自定义errorPage */ @Controller public class ErrorPageConfig implements ErrorController{ private final static String ERROR_PATH = "/error" ; @Override public String getErrorPath() { return ERROR_PATH; } @RequestMapping(ERROR_PATH) public void errorPathHandler(HttpServletResponse response) { //抛出ErrorPageException异常,方便被ExceptionHandlerConfig处理 ErrorEnum errorEnum; switch (response.getStatus()) { case 404: errorEnum = ErrorEnum.NOT_FOUND; break; case 403: errorEnum = ErrorEnum.FORBIDDEN; break; case 401: errorEnum = ErrorEnum.UNAUTHORIZED; break; case 400: errorEnum = ErrorEnum.BAD_REQUEST; break; default: errorEnum = ErrorEnum.UNKNOWN; break; } throw new ErrorPageException(errorEnum); } }
@RestControllerAdvice,统一异常处理,捕获并返回统一返回对象Result,同时把异常信息打印到日志中
/** * 统一异常处理 */ @Slf4j @RestControllerAdvice public class ExceptionHandlerConfig{ /** * 业务异常 统一处理 */ @ExceptionHandler(value = ServiceException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) @ResponseBody public Result exceptionHandler400(ServiceException e){ //把错误信息输入到日志中 log.error(ErrorUtil.errorInfoToString(e)); return Result.error(e.getErrorEnum()); } /** * 错误页面异常 统一处理 */ @ExceptionHandler(value = ErrorPageException.class) @ResponseBody public Result exceptionHandler(ErrorPageException e){ //把错误信息输入到日志中 log.error(ErrorUtil.errorInfoToString(e)); return Result.error(e.getErrorEnum()); } /** * 空指针异常 统一处理 */ @ExceptionHandler(value =NullPointerException.class) @ResponseBody public Result exceptionHandler500(NullPointerException e){ //把错误信息输入到日志中 log.error(ErrorUtil.errorInfoToString(e)); return Result.error(ErrorEnum.INTERNAL_SERVER_ERROR); } /** * 未知异常 统一处理 */ @ExceptionHandler(value =Exception.class) @ResponseBody public Result exceptionHandler(Exception e){ //把错误信息输入到日志中 log.error(ErrorUtil.errorInfoToString(e)); return Result.error(ErrorEnum.UNKNOWN); } }
新建测试controller,新增几个测试接口,模拟多种异常报错的情况
/** * 模拟异常测试 */ @RestController @RequestMapping("/test/") public class TestController { /** * 正常返回数据 */ @GetMapping("index") public Result index(){ return Result.of("正常返回数据"); } /** * 模拟空指针异常 */ @GetMapping("nullPointerException") public Result nullPointerException(){ //故意制造空指针异常 String msg = null; msg.equals("huanzi-qch"); return Result.of("正常返回数据"); } /** * 模拟业务异常,手动抛出业务异常 */ @GetMapping("serviceException") public Result serviceException(){ throw new ServiceException(ErrorEnum.USER_NAME_IS_NOT_NULL); } }
效果
正常数据返回
http://localhost:10010/test/index
模拟空指针异常
http://localhost:10010/test/nullPointerException
模拟业务异常
http://localhost:10010/test/serviceException
调用错误接口,404
http://localhost:10010/test/serviceException111
后记
自定义统一异常处理暂时先记录到这,后续再进行补充。
代码开源
代码已经开源、托管到我的GitHub、码云: