SpringCloud的异常处理体系(一)

前言:

异常处理对程序非常重要,它可以让程序出现错误时,错误能被合理的处理。它也可以帮助程序员排查定位错误的原因。在SpringCloud微服务中,服务之间的调用可以会出现异常,如果不能很好的把异常返回给调用者,则会影响程序的正常运行。

一、统一返回结果:

这里,我定义了一个泛型Result类,统一结果的输出。并且定义了ResultCode枚举类,整理和规定了所有返回结果编码。

Result.java如下:

/**
 * 功能描述: 
* 〈正常返回结果〉 * * @Author:hanxinghua * @Date: 2020/7/21 */ @Data @Builder @NoArgsConstructor @AllArgsConstructor @Accessors(chain = true) public class Result implements Serializable { private static final long serialVersionUID = 874200365941306385L; /** * 编码 */ private Integer code; /** * 信息 */ private String msg; /** * 时间戳 */ private long time; /** * 返回数据 */ private T data; public Result(T data) { this.code = ResultCode.SUCCESS.code(); this.msg = ResultCode.SUCCESS.message(); this.data = data; } public Result(ResultCode resultCode, T data) { this.code = resultCode.code(); this.msg = resultCode.message(); this.data = data; } public static Result success() { Result result = new Result(); result.setResultCode(ResultCode.SUCCESS); return result; } public static Result success(T data) { Result result = new Result(); result.setResultCode(ResultCode.SUCCESS); result.setData(data); return result; } public static Result error(ResultCode resultCode) { Result result = new Result(); result.setResultCode(resultCode); return result; } public static Result error(ResultCode resultCode, T data) { Result result = new Result(); result.setResultCode(resultCode); result.setData(data); return result; } public static Result error(BusinessException e) { BusinessExceptionEnum ee = BusinessExceptionEnum.getByEClass(e.getClass()); if (ee != null) { return Result.error(ee.getResultCode(), e, ee.getHttpStatus(), e.getData()); } Result result = Result.error(e.getResultCode() == null ? ResultCode.NO_DEFINITION : e.getResultCode(), e, HttpStatus.OK, e.getData()); if (StringUtil.isNotBlank(e.getMessage())) { result.setMsg(e.getMessage()); } return result; } public static Result error(ResultCode resultCode, Throwable e, HttpStatus httpStatus) { Map map =new LinkedHashMap<>(8); map.put("status",httpStatus.value()); map.put("error",httpStatus.getReasonPhrase()); map.put("exception",e.getClass().getName()); map.put("exceptionMsg",e.getMessage()); map.put("cause",e.getCause()==null?null:e.getCause().toString()); map.put("path",RequestContextUtil.getRequest()==null?null:RequestContextUtil.getRequest().getRequestURI()); Result> result = new Result<>(); result.setCode(resultCode.code()) .setMsg(resultCode.message()) .setData(map); return result; } public static Result error(ResultCode resultCode, Throwable e, HttpStatus httpStatus, T errorMsg) { Result result = Result.error(resultCode, e, httpStatus); Map map = (Map) result.getData(); map.put("exceptionMsg",errorMsg); return result; } private void setResultCode(ResultCode code) { this.code = code.code(); this.msg = code.message(); } public long getTime() { return time==0?System.currentTimeMillis():time; } }

ResultCode.java如下:

/**
 * 〈一句话功能简述〉
* 〈API 统一返回状态码〉 * * @author hanxinghua * @create 2019/7/10 * @since 1.0.0 */ public enum ResultCode { /* 成功状态码 */ SUCCESS(1, "成功"), /* 参数错误:10001-19999 */ PARAM_IS_INVALID(10001, "参数无效"), PARAM_IS_BLANK(10002, "参数为空"), PARAM_TYPE_BIND_ERROR(10003, "参数类型错误"), PARAM_NOT_COMPLETE(10004, "参数缺失"), ... ... /*其他异常错误: 90001-99999*/ ZUUL_EXCEPTION(90001, "Zuul网关异常"), // todo HYSTRIX_FUSING(90002, "Hystrix熔断"); private Integer code; private String message; ResultCode(Integer code, String message) { this.code = code; this.message = message; } public Integer code() { return this.code; } public String message() { return this.message; } public static String getMessage(String name) { for (ResultCode item : ResultCode.values()) { if (item.name().equals(name)) { return item.message; } } return name; } public static Integer getCode(String name) { for (ResultCode item : ResultCode.values()) { if (item.name().equals(name)) { return item.code; } } return null; } @Override public String toString() { return this.name(); } /*** * 校验重复的code值 */ static void main(String[] args) { ResultCode[] apiResultCodes = ResultCode.values(); List codeList = new ArrayList(); for (ResultCode apiResultCode : apiResultCodes) { if (codeList.contains(apiResultCode.code)) { System.out.println(apiResultCode.code); } else { codeList.add(apiResultCode.code()); } System.out.println(apiResultCode.code() + " " + apiResultCode.message()); } } }

 返回结果JSON样例:

{
  "code": 1,
  "msg": "成功",
  "time": 1601345461720,
  "data": null
}

二、构建系统内部异常体系:

在ResultCode枚举类中,我们已经整理了所有可能出现的结果。有些结果需要我们使用自定义异常类,完成结果输出。下面,我来说一下我的做法:首先创建一个继承RuntimeException类的自定义异常的父类(BusinessException.java),然后所有的自定义异常再继承该父类。此外,我又会创建一个业务异常枚举类,使用这个枚举类维护自定义异常类,HttpStatus和ResultCode三者的对应关系(BusinessExceptionEnum.java)。

BusinessException.java:

/**
 * 〈一句话功能简述〉
* 〈自定义业务异常类〉 * * @author hanxinghua * @create 2019/7/10 * @since 1.0.0 */ @Data public class BusinessException extends RuntimeException { private static final long serialVersionUID = 194906846739586856L; protected String code; protected String message; protected ResultCode resultCode; protected Object data; public BusinessException() { BusinessExceptionEnum exceptionEnum = BusinessExceptionEnum.getByEClass(this.getClass()); if (exceptionEnum != null) { resultCode = exceptionEnum.getResultCode(); code = exceptionEnum.getResultCode().code().toString(); message = exceptionEnum.getResultCode().message(); } } public BusinessException(String message) { this(); if (StringUtil.isNotBlank(message)) { this.message = message; } } public BusinessException(String format, Object... objects) { this(); String message = StringUtil.formatIfArgs(format, "{}", objects); if (StringUtil.isNotBlank(message)) { this.message = message; } } public BusinessException(ResultCode resultCode, Object data) { this(resultCode); this.data = data; } public BusinessException(ResultCode resultCode) { this.resultCode = resultCode; this.code = resultCode.code().toString(); this.message = resultCode.message(); } }

 FeignFailException.java(某一个自定义异常):

/**
 * 〈一句话功能简述〉
* 〈调用Feign失败异常〉 * * @author hanxinghua * @create 2019/9/3 * @since 1.0.0 */ public class FeignFailException extends BusinessException { private static final long serialVersionUID = -832464574020190710L; public FeignFailException() { super(); } public FeignFailException(Object data) { super.data = data; } public FeignFailException(ResultCode resultCode) { super(resultCode); } public FeignFailException(ResultCode resultCode, Object data) { super(resultCode, data); } public FeignFailException(String msg) { super(msg); } public FeignFailException(String formatMsg, Object... objects) { super(formatMsg, objects); } }

 BusinessExceptionEnum.java

/**
 * 〈一句话功能简述〉
* 〈异常、HTTP状态码、默认自定义返回码 映射类〉 * * @author hanxinghua * @create 2019/7/10 * @since 1.0.0 */ public enum BusinessExceptionEnum { /** * Feign失败异常 * HttpStatus 400 */ FEIGN_FAIL(FeignFailException.class, HttpStatus.BAD_REQUEST, ResultCode.INTERFACE_INNER_INVOKE_ERROR), ... ... /** * 未定义的异常 * HttpStatus 500 */ NO_DEFINITION_EXCEPTION(NoDefinitionException.class, HttpStatus.INTERNAL_SERVER_ERROR, ResultCode.NO_DEFINITION); private Class eClass; private HttpStatus httpStatus; private ResultCode resultCode; BusinessExceptionEnum(Class eClass, HttpStatus httpStatus, ResultCode resultCode) { this.eClass = eClass; this.httpStatus = httpStatus; this.resultCode = resultCode; } public Class getEClass() { return eClass; } public HttpStatus getHttpStatus() { return httpStatus; } public ResultCode getResultCode() { return resultCode; } public static boolean isSupportHttpStatus(int httpStatus) { for (BusinessExceptionEnum exceptionEnum : BusinessExceptionEnum.values()) { if (exceptionEnum.httpStatus.value() == httpStatus) { return true; } } return false; } public static boolean isSupportException(Class z) { for (BusinessExceptionEnum exceptionEnum : BusinessExceptionEnum.values()) { if (exceptionEnum.eClass.equals(z)) { return true; } } return false; } public static BusinessExceptionEnum getByHttpStatus(HttpStatus httpStatus) { if (httpStatus == null) { return null; } for (BusinessExceptionEnum exceptionEnum : BusinessExceptionEnum.values()) { if (httpStatus.equals(exceptionEnum.httpStatus)) { return exceptionEnum; } } return null; } public static BusinessExceptionEnum getByEClass(Class eClass) { if (eClass == null) { return null; } for (BusinessExceptionEnum exceptionEnum : BusinessExceptionEnum.values()) { if (eClass.equals(exceptionEnum.eClass)) { return exceptionEnum; } } return null; } }

三、构建全局异常处理注解:

上述已经规范返回结果和异常体系,但是,出现了异常时,如何让异常信息按照统一的返回结果格式输出?我这里采用的是全局异常类的处理方法。为了方便使用,我这里采用注解的形式开启某类中的全局异常处理。

GlobalExceptionHandler.java

/**
 * 功能描述: 
* 〈全局异常处理类〉 * 需要通过注解在Controller上打注解启动{@link EnableGlobalException} * * @Author:hanxinghua * @Date: 2020/7/21 */ @Slf4j @RestController @ControllerAdvice(annotations = EnableGlobalException.class) public class GlobalExceptionHandler{ /** * 处理违反约束异常 * * @param e * @param request * @return */ @ResponseStatus(HttpStatus.BAD_REQUEST) @ExceptionHandler(ConstraintViolationException.class) public Result handleConstraintViolationException(ConstraintViolationException e, HttpServletRequest request) { log.info("handleConstraintViolationException start, uri:[{}], caused by:", request.getRequestURI(), e); List invalidParameterList = HandleInvalidParameter.convertConstraintViolationToInvalidParameterList(e.getConstraintViolations(),true); return Result.error(ResultCode.PARAM_IS_INVALID, e, HttpStatus.BAD_REQUEST, invalidParameterList); } ... ... /** * 处理运行时异常 * * @param e * @param request * @return */ @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) @ExceptionHandler(Throwable.class) public Result handleThrowable(Throwable e, HttpServletRequest request) { log.error("handleThrowable start, uri:[{}], caused by:", request.getRequestURI(), e); return Result.error(ResultCode.SYSTEM_INNER_ERROR, e, HttpStatus.INTERNAL_SERVER_ERROR); } }

EnableGlobalException.java 

/**
 * 功能描述: 
* 〈启动全局异常处理器注解〉 * * @Author:hanxinghua * @Date: 2020/7/22 */ @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface EnableGlobalException { }

你可能感兴趣的:(#SpringCloud,Spring,#Springboot,java,restful)