微服务之间调用的异常应该如何处理

前言

在分布式服务的场景下,业务服务都将进行拆分,不同服务之间都会相互调用,如何做好异常处理是比较关键的,可以让业务人员在页面使用系统报错后,很清楚的看到服务报错的原因,而不是返回代码级别的异常报错,比如NullException、IllegalArgumentException、FeignExecption等异常报错,这样就会让非技术人员看到了一头雾水,从而很降低用户的体验感。

服务调用异常场景

添加图片注释,不超过 140 字(可选)

这是一个很常规的服务链路调用异常,前端用户请求A服务,A服务再去请求B服务,B服务出现了异常,A服务返回的Fallback降级的报错异常,但是显然这个异常并不是很能让人理解。

添加图片注释,不超过 140 字(可选)

这是feign服务之前调用异常的报错,通过FeignException内部的异常处理类进行处理。

重写Feign异常处理

首先我们可以通过实现feign的ErrorDecoder接口重写它的的decode方法,进行自定义异常处理,针对每个feign接口的异常报错,抛出自定义的exception将错误信息和错误码返回。

FeignExceptionConfiguration 自定义异常处理类

 
 

typescript复制代码@Slf4j @Configuration public class FeignExceptionConfiguration { @Bean public ErrorDecoder errorDecoder() { return new UserErrorDecoder(); } /** * 重新实现feign的异常处理,捕捉restful接口返回的json格式的异常信息 * */ public class UserErrorDecoder implements ErrorDecoder { @Override public Exception decode(String methodKey, Response response) { Exception exception = new MyException(); ObjectMapper mapper = new ObjectMapper(); //空属性处理 mapper.setSerializationInclusion(JsonSerialize.Inclusion.NON_EMPTY); //设置输入时忽略在JSON字符串中存在但Java对象实际没有的属性 mapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false); //禁止使用int代表enum的order来反序列化enum mapper.configure(DeserializationConfig.Feature.FAIL_ON_NUMBERS_FOR_ENUMS, true); try { String json = Util.toString(response.body().asReader()); log.info("异常返回结果:"+ JSON.toJSONString(json)); exception = new RuntimeException(json); if (StringUtils.isEmpty(json)) { return null; } FeignFaildResult result = mapper.readValue(json, FeignFaildResult.class); // 业务异常包装成自定义异常类MyException if (result.getCode() != 200) { exception = new MyException(result.getMsg(),result.getCode()); } } catch (IOException ex) { log.error(ex.getMessage(), ex); } return exception; } } }

添加图片注释,不超过 140 字(可选)

这里可以看到,经过处理后的异常返回结果,已经过滤掉feign的一长串异常,只留下code、msg、data等信息,直接映射到结果集对象上,通过自定义异常返回。

FeignFaildResult 异常结果集返回

 
 

arduino复制代码/** * 根据 json 来定义需要的字段 */ @Data public class FeignFaildResult { private String msg; private int code; }

MyException自定义异常

 
 

scala复制代码import lombok.Data; @Data public class MyException extends RuntimeException { // 自定义异常代码 private int status = 503; public MyException() { } // 构造方法 public MyException(String message, int status) { super(message); this.status = status; } }

FeignClient接口定义

 
 

kotlin复制代码@FeignClient(contextId = "iTestServiceClient", value = "Lxlxxx-system2", fallback = TestServiceFallbackFactory.class, configuration = FeignExceptionConfiguration.class) public interface ITestServiceClient { /** * 服务调用测试方法 * @return */ @GetMapping("/test/method") public R<String> testRequestMethod() throws Exception; }

通过@FeignClient注解里面的configuration属性,开启自定义异常处理。

被调用方服务

被调用方服务业务处理直接抛出异常即可

添加图片注释,不超过 140 字(可选)

调用结果

代码中throw的异常message,直接可以返回给前端调用接口,这样报错信息也比较清楚。

添加图片注释,不超过 140 字(可选)

Spirng全局异常处理

当然也可以通过全局异常处理的方式,来处理报错信息,直接在调用方服务的控制层进行切面处理即可,Spring 3.2也提供了相应的的注解类@ControllerAdvice,配合@ExceptionHandler注解,即可实现全局异常处理。

ResultCode异常错误码定义

首先先定义异常错误码枚举

 
 

typescript复制代码public enum ResultCode { /* * 通用错误码 Code约定 * 0表示成功[SUCCESS], 看到0,业务处理成功。 * 10000 - 19999表示业务警告[WARN_], 这种code不是常规武器,能免则免。 * 20000 - 29999表示通用错误代码[ERR_], 各个系统通用的错误代码。 * 30000 - 39999表示业务自定义错误代码[DIY_] * 40000 - 49999表示系统错误[SYS_], 系统错误单独拉出来,作为独立区域。理论上这部分也是通用的,不可以自定义。 */ SUCCESS("0", "操作成功"), ERR_LACK_PARAM("20001", "请求参数不正确"), ERR_NO_LOGIN("20002", "用户未登录"), ERR_NO_RIGHT("20003", "没有权限访问该资源"), ERR_NO_SERVICE("20004", "资源不存在"), ERR_WRONG_STATUS("20005", "资源的当前状态不支持该操作"), ERR_LACK_CONFIG("20006", "缺少必要的配置项"), ERR_PROCESS_FAIL("20007", "业务处理失败"), ERR_THIRD_API_FAIL("20008", "调用第三方接口失败"), ERR_IS_DELETED("20009", "资源已删除"), ERR_UPDATE_FAIL("20010", "更新操作失败"), SYS_MAINTENANCE("40001", "系统维护中"), SYS_BUSY("40002", "系统繁忙"), SYS_EXCEPTION("40003", "系统异常"); private String code; private String msg; private ResultCode(String code, String msg) { this.code = code; this.msg = msg; } public String getCode() { return this.code; } public void setCode(String code) { this.code = code; } public String getMsg() { return this.msg; } public void setMsg(String msg) { this.msg = msg; } public static ResultCode get(String code) { ResultCode[] var1 = values(); int var2 = var1.length; for (int var3 = 0; var3 < var2; ++var3) { ResultCode statusEnum = var1[var3]; if (statusEnum.getCode().equals(code)) { return statusEnum; } } return null; } public String getErrorMsg(Object... params) { String errorMsg = null; if (params != null && params.length != 0) { MessageFormat msgFmt = new MessageFormat(this.msg); errorMsg = msgFmt.format(params); } else { errorMsg = this.msg; } return errorMsg; } }

BaseResult统一返回结果对象

 
 

typescript复制代码@Data public class BaseResult<T> implements Serializable { private static final long serialVersionUID = 621986096326899992L; private String message; private String errorCode; private T data; public BaseResult() { } public BaseResult(String message, String errorCode) { this.message = message; this.errorCode = errorCode; } public static <T> BaseResult<T> success() { BaseResult<T> baseResult = new BaseResult<>(); baseResult.setMessage(ResultCode.SUCCESS.getMsg()); baseResult.setErrorCode(ResultCode.SUCCESS.getCode()); return baseResult; } public static <T> BaseResult<T> success(T result) { BaseResult<T> baseResult = new BaseResult<>(); baseResult.setData(result); baseResult.setMessage(ResultCode.SUCCESS.getMsg()); baseResult.setErrorCode(ResultCode.SUCCESS.getCode()); return baseResult; } public static <T> BaseResult<T> fail(ResultCode error) { BaseResult<T> baseResult = new BaseResult<>(); baseResult.setErrorCode(error.getCode()); baseResult.setMessage(error.getMsg()); return baseResult; } public static <T> BaseResult<T> error(ResultCode error,String message) { BaseResult<T> baseResult = new BaseResult<>(); baseResult.setErrorCode(error.getCode()); baseResult.setMessage(message); return baseResult; } public static <T> BaseResult<T> fail(ResultCode error, Exception e) { BaseResult<T> baseResult = new BaseResult<>(); baseResult.setErrorCode(error.getCode()); baseResult.setMessage(e.getMessage()); return baseResult; } public Boolean isSuccess() { return "0".equals(this.errorCode) ? true : false; } }

CommonException自定义全局异常处理类

 
 

scala复制代码public class CommonException extends RuntimeException { private String code; /** * 自己临时自定义状态码和状态信息 * * @param code 状态码 * @param message 状态信息 */ public CommonException(String code, String message) { super(message); this.code = code; } /** * @param resultCode 从枚举对象中获取状态码和状态信息 */ public CommonException(ResultCode resultCode) { super(resultCode.getMsg()); this.code = resultCode.getCode(); } }

ExceptionController全局异常处理控制类

 
 

less复制代码@ControllerAdvice public class ExceptionController { /** * CommonException * @param e * @return */ @ExceptionHandler(CommonException.class) @ResponseBody public BaseResult handlerException(CommonException e){ //异常返回false,Result是上一篇接口返回对象。 return new BaseResult(e.getMessage(),e.getCode()); } }

调用结果

 
 

less复制代码@RestController @Slf4j public class TestController { @Autowired private ITestServiceClient iTestServiceClient; @GetMapping("/testMethod") public BaseResult testMethod() throws Exception { try { log.info("通过feign调用system2服务~~~~~~~~~"); R<String> stringR = iTestServiceClient.testRequestMethod(); } catch (Exception e) { throw new CommonException(ResultCode.SYS_EXCEPTION.getCode(), ResultCode.SYS_EXCEPTION.getMsg()); } return BaseResult.success(); }

添加图片注释,不超过 140 字(可选)

添加图片注释,不超过 140 字(可选)

我还是模拟上面的场景,A服务去调B服务,B服务中抛出异常,通过定义的ExceptionController进行捕获异常,并且根据自定义的异常,拿到异常code和message进行返回,也是一种不错的选择。

总结

以上两种服务之间调用异常处理的方法,分别在不同服务角度进行捕获处理,关于服务的异常处理,具体还要根据业务需求来进行处理,不同的异常可以分类进行捕获,例如基础异常、参数校验异常、工具类异常、业务检查异常等,都可以分开来进行定义,属于处理异常的一个规范定义。

                                                                                                         资源获取:
大家 点赞、收藏、关注、评论啦 、 查看👇🏻👇🏻👇🏻 微信公众号获取联系方式👇🏻👇🏻👇🏻
精彩专栏推荐订阅:下方专栏👇🏻👇🏻👇🏻👇🏻
每天学四小时:Java+Spring+JVM+分布式高并发,架构师指日可待

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值