微服务之间调用的异常应该如何处理?
1前言
在分布式服务的场景下,业务服务都将进行拆分,不同服务之前都会相互调用,如何做好异常处理是比较关键的,可以让业务人员在页面使用系统报错后,很清楚的看到服务报错的原因,而不是返回代码级别的异常报错,比如NullException、IllegalArgumentException、FeignExecption等异常报错,这样就会让非技术人员看到了一头雾水,从而很降低用户的体验感。
2服务调用异常场景
这是一个很常规的服务链路调用异常,前端用户请求A服务,A服务再去请求B服务,B服务出现了异常,A服务返回的Fallback降级的报错异常,但是显然这个异常并不是很能让人理解。
这是feign服务之前调用异常的报错,通过FeignException内部的异常处理类进行处理。
3重写Feign异常处理
首先我们可以通过实现feign的ErrorDecoder接口重写它的的decode方法,进行自定义异常处理,针对每个feign接口的异常报错,抛出自定义的exception将错误信息和错误码返回。
FeignExceptionConfiguration 自定义异常处理类
@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;
}
}
}
这里可以看到,经过处理后的异常返回结果,已经过滤掉feign的一长串异常,只留下code、msg、data等信息,直接映射到结果集对象上,通过自定义异常返回。
FeignFaildResult 异常结果集返回
/**
* 根据 json 来定义需要的字段
*/
@Data
public class FeignFaildResult {
private String msg;
private int code;
}
MyException自定义异常
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接口定义
@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属性,开启自定义异常处理。
被调用方服务
被调用方服务业务处理直接抛出异常即可
调用结果
代码中throw的异常message,直接可以返回给前端调用接口,这样报错信息也比较清楚。
4Spirng全局异常处理
当然也可以通过全局异常处理的方式,来处理报错信息,直接在调用方服务的控制层进行切面处理即可,Spring 3.2也提供了相应的的注解类@ControllerAdvice,配合@ExceptionHandler注解,即可实现全局异常处理。
ResultCode异常错误码定义
首先先定义异常错误码枚举
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统一返回结果对象
@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自定义全局异常处理类
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全局异常处理控制类
@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());
}
}
5调用结果
@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();
}
我还是模拟上面的场景,A服务去调B服务,B服务中抛出异常,通过定义的ExceptionController进行捕获异常,并且根据自定义的异常,拿到异常code和message进行返回,也是一种不错的选择。
6总结
以上两种服务之间调用异常处理的方法,分别在不同服务角度进行捕获处理,关于服务的异常处理,具体还要根据业务需求来进行处理,不同的异常可以分类进行捕获,例如基础异常、参数校验异常、工具类异常、业务检查异常等,都可以分开来进行定义,属于处理异常的一个规范定义。
来源:juejin.cn/post/7241395887943303226
--完--
ChatGPT 4.0 账号不用特殊网络了 !!! 目前GPT-4.0的功能:支持GPTs、联网功能、插件功能、上传文件、数据分析、AI画图、上传图片自动识别功能等 。这些功能都是3.5不能具备的 ! 不过,这个功能目前只有升级Plus会员,才能使用 。 大家也都知道,官方开通的Plus会员,一个月20美元,相当于人民币180元每月,而且经常有被封号的风险 。 所以,这边一次性买了50多个Plus会员放在一个系统的池子里,共享给大家使用 。每月只需要90元,就可以直接使用 GPT 4.0 ,而且国内网络就可以直接登录 ,不需要额外的上网工具 。 复制购买链接到浏览器打开:https://qeosq.xetlk.com/s/408XZG 购买这个账号,一直有售后,不用担心中途封号或者用不了 或者直接微信付款后,加我微信:itcodexy,备注:90元购买plus账号 我会立马通过微信好友请求 。 扫码可以直接购买
微信扫码关注该文公众号作者