小白的springboot之路(十)、全局异常处理
0、前言
任何系统,我们不会傻傻的在每一个地方进行异常捕获和处理,整个系统一般我们会在一个的地方统一进行异常处理,spring boot全局异常处理很简单;
介绍前先说点题外话,我们现在开发系统,都是前后端完全分离的,后端只提供RESTfull API,禁止涉及任何界面,什么thymeleaf、JSP那些后端模板,是绝对禁止使用的,那些东西请扔垃圾箱,不要浪费大好青春去研究,那是堕落;前端则负责界面相关,常用Vue;如果公司还没前后端分离,还在thymeleaf还在前后端一起写,那你还是早做跳槽打算吧,他们养不起你,更养不起你的家人;
前后端分离,后端API,一般对于异常处理,要做得无非两件事,
1是记录日志及相应通知处理,这是对内的,
2是给出返回结果给API调用者,这是对外的;
对API调用者来说,他只需要一个返回结果(包含错误代码、提示信息),其他的他不关心
对后端来说,他只需要记录日志,通知或者给发布相应消息给其他队列处理相关事项;
所以:看到过不少人封装了很多个自定义异常类,其实,完全没有必要,只需要一个异常处理来处理所有异常即可,然后封装一个错误识别码和提示消息的枚举,用于返回给API调用者;然后后端的处理,直接在一个异常处理方法中全部处理就行了,完全没必要封装N多个自定义异常,那没有任何意义;
0-1、关于异常的思想认识
我们应该认识到,一切异常,对系统来说,都是不正常的表现,都是属于缺陷,都属于BUG,尽管有些异常是我们主动抛出的;
我们要做的,是应该尽量提高系统可用性,最大限度避免任何异常的出现,而不是去指望完善异常处理来完善系统;
异常处理,是异常无法避免的出现了而采取的一种应急措施,主要目的是对外增加友好性,对内提供补救线索;
不要认为完善的异常处理是系统核心,他不是,不要指望异常处理尽善尽美,不要指望异常处理来给系统缺陷擦屁股;
如果系统异常过多,那么你要做的不是去完善异常处理机制,而是要好好去反思:系统架构设计是否合理,系统逻辑设计是否合理;
1、全局异常处理
使用@ControllerAdvice、@ExceptionHandler注解封装一个异常处理类即可
package com.anson.common.exception; import com.anson.common.result.ResultBody; import com.anson.common.result.ResultCode; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; /** * @description: 全局异常处理类 * @author: anson * @Date: 2019/12/17 20:56 */ @ControllerAdvice public class GlobalExceptionHandler { private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class); /** * 所有异常处理 * @param e * @return */ @ExceptionHandler(value =Exception.class) @ResponseBody public ResultBody exceptionHandler(Exception e) { //1、写日志及其他处理,对内 logger.error("未知异常!原因是:",e); System.out.println("未知异常!原因是:"+e); //2、返回错误识别码和提示给API调用者、对外 return ResultBody.failed(ResultCode.FAILED); } }
这样就可以了,
ResultBody、ResultCode这个我们下节会说到;
-------------------华丽丽的分割线--------------------------
2、自定义异常
上面的异常处理可以处理所有异常了,但是有时候,比如在拦截器、AOP、侦听器中,我们要主动抛出特定异常,比如没有权限、登录过期等,这个时候,我们可以增加一个自动以异常来统一处理,如下:
2.1、增加一个自定义异常类:
package com.anson.common.exception; import com.anson.common.result.IErrorCode; /** * @description: 自定义异常类 * @author: anson * @Date: 2019/12/30 9:14 */ public class BizException extends RuntimeException { private static final long serialVersionUID = 1L; protected long errorCode; //错误码 protected String errorMsg; //错误信息 //构造1 public BizException() { super(); } //构造2 public BizException(IErrorCode errorInfoInterface) { super(String.valueOf(errorInfoInterface.getCode())); this.errorCode = errorInfoInterface.getCode(); this.errorMsg = errorInfoInterface.getMessage(); } public BizException(IErrorCode errorInfoInterface, Throwable cause) { super(String.valueOf(errorInfoInterface.getCode()), cause); this.errorCode = errorInfoInterface.getCode(); this.errorMsg = errorInfoInterface.getMessage(); } public BizException(String errorMsg) { super(errorMsg); this.errorMsg = errorMsg; } public BizException(long errorCode, String errorMsg) { super(String.valueOf(errorCode)); this.errorCode = errorCode; this.errorMsg = errorMsg; } public BizException(long errorCode, String errorMsg, Throwable cause) { super(String.valueOf(errorCode), cause); this.errorCode = errorCode; this.errorMsg = errorMsg; } public long getErrorCode() { return errorCode; } public void setErrorCode(long errorCode) { this.errorCode = errorCode; } public String getErrorMsg() { return errorMsg; } public void setErrorMsg(String errorMsg) { this.errorMsg = errorMsg; } public String getMessage() { return errorMsg; } @Override public Throwable fillInStackTrace() { return this; } }
2.2、在全局异常处理中增加处理自定义异常:
package com.anson.common.exception; import com.anson.common.result.ResultBody; import com.anson.common.result.ResultCode; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import javax.servlet.http.HttpServletRequest; /** * @description: 全局异常处理类 * @author: anson * @Date: 2019/12/10 20:56 */ @ControllerAdvice public class GlobalExceptionHandler { private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class); /** * 1、处理自定义的业务异常 * @param req * @param e * @return */ @ExceptionHandler(value = BizException.class) @ResponseBody public ResultBody bizExceptionHandler(HttpServletRequest req, BizException e){ logger.error("发生业务异常!原因是:{}",e.getErrorMsg()); return ResultBody.failed(e.getErrorCode(),e.getErrorMsg()); } /** * 2、处理其他异常 * @param e * @return */ @ExceptionHandler(value =Exception.class) @ResponseBody public ResultBody exceptionHandler(Exception e) { //1、写日志及其他处理,对内 logger.error("未知异常!原因是:",e); System.out.println("未知异常!原因是:"+e); logger.trace("trace level"); logger.debug("debug level"); logger.info("info level"); logger.warn("warn level"); logger.error("error level"); long beginTime = System.currentTimeMillis(); logger.info("请求处理结束,耗时:{}毫秒", (System.currentTimeMillis() - beginTime)); //第一种用法 logger.info("请求处理结束,耗时:" + (System.currentTimeMillis() - beginTime) + "毫秒"); //第二种用法 //------------------------------ //2、返回错误识别码和提示给API调用者、对外 return ResultBody.failed(ResultCode.FAILED); } }
2.3、然后,在需要的地方上抛异常即可
//主动抛出自定义异常 throw new BizException(ResultCode.UNAUTHORIZED.getCode(),ResultCode.UNAUTHORIZED.getMessage());
运行后即可看到上抛了自定义异常: