异常处理对程序非常重要,它可以让程序出现错误时,错误能被合理的处理。它也可以帮助程序员排查定位错误的原因。在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
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 extends BusinessException> eClass;
private HttpStatus httpStatus;
private ResultCode resultCode;
BusinessExceptionEnum(Class extends BusinessException> eClass, HttpStatus httpStatus, ResultCode resultCode) {
this.eClass = eClass;
this.httpStatus = httpStatus;
this.resultCode = resultCode;
}
public Class extends BusinessException> 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 extends BusinessException> 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 {
}