SpringBoot系列——自定义统一异常处理
前言
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(String code,String msg) { super(code, msg); } }
重写ErrorController,不在跳转原生错误页面,而是抛出我们的自定义异常
/** * 自定义errorPage * 直接继承 BasicErrorController */ @Controller public class ErrorPageConfig extends BasicErrorController { public ErrorPageConfig(){ super(new DefaultErrorAttributes(),new ErrorProperties()); } @Override @RequestMapping( produces = {"text/html"} ) public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) { doError(request); return null; } @Override @RequestMapping public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) { doError(request); return null; } private void doError(HttpServletRequest request) { Map<String, Object> model = this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.ALL)); //抛出ErrorPageException异常,方便被ExceptionHandlerConfig处理 String path = model.get("path").toString(); String status = model.get("status").toString(); //静态资源文件发生404,无需抛出异常 if(!path.contains("/common/") && !path.contains(".")){ throw new ErrorPageException(status, path); } } }
@RestControllerAdvice,统一异常处理,捕获并返回统一返回对象Result,同时把异常信息打印到日志中
/** * 统一异常处理 */ @Slf4j @RestControllerAdvice public class ExceptionHandlerConfig{ /** * 业务异常 统一处理 */ @ExceptionHandler(value = ServiceException.class) @ResponseBody public Object exceptionHandler400(ServiceException e){ return returnResult(e,Result.error(e.getErrorEnum())); } /** * 错误页面异常 统一处理 */ @ExceptionHandler(value = ErrorPageException.class) @ResponseBody public Object exceptionHandler(ErrorPageException e){ ErrorEnum errorEnum; switch (Integer.parseInt(e.getCode())) { 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; } return returnResult(e,Result.error(errorEnum)); } /** * 空指针异常 统一处理 */ @ExceptionHandler(value =NullPointerException.class) @ResponseBody public Object exceptionHandler500(NullPointerException e){ return returnResult(e,Result.error(ErrorEnum.INTERNAL_SERVER_ERROR)); } /** * 其他异常 统一处理 */ @ExceptionHandler(value =Exception.class) @ResponseBody public Object exceptionHandler(Exception e){ return returnResult(e,Result.of(ErrorEnum.UNKNOWN.getCode(), false, "【" + e.getClass().getName() + "】" + e.getMessage())); } /** * 是否为ajax请求 * ajax请求,响应json格式数据,否则应该响应html页面 */ private Object returnResult(Exception e,Result errorResult){ //把错误信息输入到日志中 log.error(ErrorUtil.errorInfoToString(e)); ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = requestAttributes.getRequest(); HttpServletResponse response = requestAttributes.getResponse(); //设置http响应状态 response.setStatus(200); //判断是否为ajax请求 if ("XMLHttpRequest".equalsIgnoreCase(request.getHeader("X-Requested-With"))){ return errorResult; }else{ return new ModelAndView("error","msg",errorResult.getMsg()); } } }
新建测试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
更新
2022-03-22更新:统一异常处理返回格式调整:ajax请求返回json格式数据,其他情况下跳转自定义error页面
后记
自定义统一异常处理暂时先记录到这,后续再进行补充。
代码开源
代码已经开源、托管到我的GitHub、码云:
版权声明
捐献、打赏
支付宝
微信