当前位置:   article > 正文

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

feign 服务间调用异常

前言

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

服务调用异常场景

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

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

重写Feign异常处理

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

FeignExceptionConfiguration 自定义异常处理类

  1. typescript复制代码@Slf4j
  2. @Configuration
  3. public class FeignExceptionConfiguration {
  4. @Bean
  5. public ErrorDecoder errorDecoder() {
  6. return new UserErrorDecoder();
  7. }
  8. /**
  9. * 重新实现feign的异常处理,捕捉restful接口返回的json格式的异常信息
  10. *
  11. */
  12. public class UserErrorDecoder implements ErrorDecoder {
  13. @Override
  14. public Exception decode(String methodKey, Response response) {
  15. Exception exception = new MyException();
  16. ObjectMapper mapper = new ObjectMapper();
  17. //空属性处理
  18. mapper.setSerializationInclusion(JsonSerialize.Inclusion.NON_EMPTY);
  19. //设置输入时忽略在JSON字符串中存在但Java对象实际没有的属性
  20. mapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
  21. //禁止使用int代表enum的order来反序列化enum
  22. mapper.configure(DeserializationConfig.Feature.FAIL_ON_NUMBERS_FOR_ENUMS, true);
  23. try {
  24. String json = Util.toString(response.body().asReader());
  25. log.info("异常返回结果:"+ JSON.toJSONString(json));
  26. exception = new RuntimeException(json);
  27. if (StringUtils.isEmpty(json)) {
  28. return null;
  29. }
  30. FeignFaildResult result = mapper.readValue(json, FeignFaildResult.class);
  31. // 业务异常包装成自定义异常类MyException
  32. if (result.getCode() != 200) {
  33. exception = new MyException(result.getMsg(),result.getCode());
  34. }
  35. } catch (IOException ex) {
  36. log.error(ex.getMessage(), ex);
  37. }
  38. return exception;
  39. }
  40. }
  41. }

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

FeignFaildResult 异常结果集返回

  1. arduino复制代码/**
  2. * 根据 json 来定义需要的字段
  3. */
  4. @Data
  5. public class FeignFaildResult {
  6. private String msg;
  7. private int code;
  8. }

MyException自定义异常

  1. scala复制代码import lombok.Data;
  2. @Data
  3. public class MyException extends RuntimeException {
  4. // 自定义异常代码
  5. private int status = 503;
  6. public MyException() {
  7. }
  8. // 构造方法
  9. public MyException(String message, int status) {
  10. super(message);
  11. this.status = status;
  12. }
  13. }

FeignClient接口定义

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

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

被调用方服务

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

调用结果

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

Spirng全局异常处理

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

ResultCode异常错误码定义

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

  1. typescript复制代码public enum ResultCode {
  2. /*
  3. * 通用错误码 Code约定
  4. * 0表示成功[SUCCESS], 看到0,业务处理成功。
  5. * 10000 - 19999表示业务警告[WARN_], 这种code不是常规武器,能免则免。
  6. * 20000 - 29999表示通用错误代码[ERR_], 各个系统通用的错误代码。
  7. * 30000 - 39999表示业务自定义错误代码[DIY_]
  8. * 40000 - 49999表示系统错误[SYS_], 系统错误单独拉出来,作为独立区域。理论上这部分也是通用的,不可以自定义。
  9. */
  10. SUCCESS("0", "操作成功"),
  11. ERR_LACK_PARAM("20001", "请求参数不正确"),
  12. ERR_NO_LOGIN("20002", "用户未登录"),
  13. ERR_NO_RIGHT("20003", "没有权限访问该资源"),
  14. ERR_NO_SERVICE("20004", "资源不存在"),
  15. ERR_WRONG_STATUS("20005", "资源的当前状态不支持该操作"),
  16. ERR_LACK_CONFIG("20006", "缺少必要的配置项"),
  17. ERR_PROCESS_FAIL("20007", "业务处理失败"),
  18. ERR_THIRD_API_FAIL("20008", "调用第三方接口失败"),
  19. ERR_IS_DELETED("20009", "资源已删除"),
  20. ERR_UPDATE_FAIL("20010", "更新操作失败"),
  21. SYS_MAINTENANCE("40001", "系统维护中"),
  22. SYS_BUSY("40002", "系统繁忙"),
  23. SYS_EXCEPTION("40003", "系统异常");
  24. private String code;
  25. private String msg;
  26. private ResultCode(String code, String msg) {
  27. this.code = code;
  28. this.msg = msg;
  29. }
  30. public String getCode() {
  31. return this.code;
  32. }
  33. public void setCode(String code) {
  34. this.code = code;
  35. }
  36. public String getMsg() {
  37. return this.msg;
  38. }
  39. public void setMsg(String msg) {
  40. this.msg = msg;
  41. }
  42. public static ResultCode get(String code) {
  43. ResultCode[] var1 = values();
  44. int var2 = var1.length;
  45. for (int var3 = 0; var3 < var2; ++var3) {
  46. ResultCode statusEnum = var1[var3];
  47. if (statusEnum.getCode().equals(code)) {
  48. return statusEnum;
  49. }
  50. }
  51. return null;
  52. }
  53. public String getErrorMsg(Object... params) {
  54. String errorMsg = null;
  55. if (params != null && params.length != 0) {
  56. MessageFormat msgFmt = new MessageFormat(this.msg);
  57. errorMsg = msgFmt.format(params);
  58. } else {
  59. errorMsg = this.msg;
  60. }
  61. return errorMsg;
  62. }
  63. }

BaseResult统一返回结果对象

  1. typescript复制代码@Data
  2. public class BaseResult<T> implements Serializable {
  3. private static final long serialVersionUID = 621986096326899992L;
  4. private String message;
  5. private String errorCode;
  6. private T data;
  7. public BaseResult() {
  8. }
  9. public BaseResult(String message, String errorCode) {
  10. this.message = message;
  11. this.errorCode = errorCode;
  12. }
  13. public static <T> BaseResult<T> success() {
  14. BaseResult<T> baseResult = new BaseResult<>();
  15. baseResult.setMessage(ResultCode.SUCCESS.getMsg());
  16. baseResult.setErrorCode(ResultCode.SUCCESS.getCode());
  17. return baseResult;
  18. }
  19. public static <T> BaseResult<T> success(T result) {
  20. BaseResult<T> baseResult = new BaseResult<>();
  21. baseResult.setData(result);
  22. baseResult.setMessage(ResultCode.SUCCESS.getMsg());
  23. baseResult.setErrorCode(ResultCode.SUCCESS.getCode());
  24. return baseResult;
  25. }
  26. public static <T> BaseResult<T> fail(ResultCode error) {
  27. BaseResult<T> baseResult = new BaseResult<>();
  28. baseResult.setErrorCode(error.getCode());
  29. baseResult.setMessage(error.getMsg());
  30. return baseResult;
  31. }
  32. public static <T> BaseResult<T> error(ResultCode error,String message) {
  33. BaseResult<T> baseResult = new BaseResult<>();
  34. baseResult.setErrorCode(error.getCode());
  35. baseResult.setMessage(message);
  36. return baseResult;
  37. }
  38. public static <T> BaseResult<T> fail(ResultCode error, Exception e) {
  39. BaseResult<T> baseResult = new BaseResult<>();
  40. baseResult.setErrorCode(error.getCode());
  41. baseResult.setMessage(e.getMessage());
  42. return baseResult;
  43. }
  44. public Boolean isSuccess() {
  45. return "0".equals(this.errorCode) ? true : false;
  46. }
  47. }

CommonException自定义全局异常处理类

  1. scala复制代码public class CommonException extends RuntimeException {
  2. private String code;
  3. /**
  4. * 自己临时自定义状态码和状态信息
  5. *
  6. * @param code 状态码
  7. * @param message 状态信息
  8. */
  9. public CommonException(String code, String message) {
  10. super(message);
  11. this.code = code;
  12. }
  13. /**
  14. * @param resultCode 从枚举对象中获取状态码和状态信息
  15. */
  16. public CommonException(ResultCode resultCode) {
  17. super(resultCode.getMsg());
  18. this.code = resultCode.getCode();
  19. }
  20. }

ExceptionController全局异常处理控制类

  1. less复制代码@ControllerAdvice
  2. public class ExceptionController {
  3. /**
  4. * CommonException
  5. * @param e
  6. * @return
  7. */
  8. @ExceptionHandler(CommonException.class)
  9. @ResponseBody
  10. public BaseResult handlerException(CommonException e){
  11. //异常返回false,Result是上一篇接口返回对象。
  12. return new BaseResult(e.getMessage(),e.getCode());
  13. }
  14. }

调用结果

  1. less复制代码@RestController
  2. @Slf4j
  3. public class TestController {
  4. @Autowired
  5. private ITestServiceClient iTestServiceClient;
  6. @GetMapping("/testMethod")
  7. public BaseResult testMethod() throws Exception {
  8. try {
  9. log.info("通过feign调用system2服务~~~~~~~~~");
  10. R<String> stringR = iTestServiceClient.testRequestMethod();
  11. } catch (Exception e) {
  12. throw new CommonException(ResultCode.SYS_EXCEPTION.getCode(), ResultCode.SYS_EXCEPTION.getMsg());
  13. }
  14. return BaseResult.success();
  15. }

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

总结

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/84651
推荐阅读
  

闽ICP备14008679号