当前位置: 欣欣网 > 码农

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

2024-01-29码农

点击关注公众号,Java干货 及时送达

1 前言

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

2 服务调用异常场景

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

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

3 重写Feign异常处理

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

FeignExceptionConfiguration 自定义异常处理类

@Slf4j
@Configuration
public classFeignExceptionConfiguration{
@Bean
public ErrorDecoder errorDecoder(){
returnnew UserErrorDecoder();
}
/**
* 重新实现feign的异常处理,捕捉restful接口返回的json格式的异常信息
*
*/

public classUserErrorDecoderimplementsErrorDecoder{
@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)) {
returnnull;
}
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 classFeignFaildResult{
private String msg;
privateint code;
}

MyException自定义异常

import lombok.Data;
@Data
public classMyExceptionextendsRuntimeException{
// 自定义异常代码
privateint status = 503;
publicMyException(){
}
// 构造方法
publicMyException(String message, int status){
super(message);
this.status = status;
}
}


FeignClient接口定义

@FeignClient(contextId = "iTestServiceClient",
value = "Lxlxxx-system2",
fallback = TestServiceFallbackFactory. class,
configuration
= FeignExceptionConfiguration. class)
publicinterfaceITestServiceClient
{

/**
* 服务调用测试方法
@return
*/

@GetMapping("/test/method")
public R<String> testRequestMethod()throws Exception;
}

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

被调用方服务

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

调用结果

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

4 Spirng全局异常处理

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

ResultCode异常错误码定义

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

publicenum 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;
privateResultCode(String code, String msg){
this.code = code;
this.msg = msg;
}
public String getCode(){
returnthis.code;
}
publicvoidsetCode(String code){
this.code = code;
}
public String getMsg(){
returnthis.msg;
}
publicvoidsetMsg(String msg){
this.msg = msg;
}
publicstatic 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;
}
}
returnnull;
}
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 classBaseResult<TimplementsSerializable{
privatestaticfinallong serialVersionUID = 621986096326899992L;
private String message;
private String errorCode;
private T data;
publicBaseResult(){
}
publicBaseResult(String message, String errorCode){
this.message = message;
this.errorCode = errorCode;
}
publicstatic <T> BaseResult<T> success(){
BaseResult<T> baseResult = new BaseResult<>();
baseResult.setMessage(ResultCode.SUCCESS.getMsg());
baseResult.setErrorCode(ResultCode.SUCCESS.getCode());
return baseResult;
}
publicstatic <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;
}
publicstatic <T> BaseResult<T> fail(ResultCode error){
BaseResult<T> baseResult = new BaseResult<>();
baseResult.setErrorCode(error.getCode());
baseResult.setMessage(error.getMsg());
return baseResult;
}
publicstatic <T> BaseResult<T> error(ResultCode error,String message){
BaseResult<T> baseResult = new BaseResult<>();
baseResult.setErrorCode(error.getCode());
baseResult.setMessage(message);
return baseResult;
}
publicstatic <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 classCommonExceptionextendsRuntimeException{
private String code;
/**
* 自己临时自定义状态码和状态信息
*
@param code 状态码
@param message 状态信息
*/

publicCommonException(String code, String message){
super(message);
this.code = code;
}
/**
@param resultCode 从枚举对象中获取状态码和状态信息
*/

publicCommonException(ResultCode resultCode){
super(resultCode.getMsg());
this.code = resultCode.getCode();
}
}


ExceptionController全局异常处理控制类

@ControllerAdvice
public classExceptionController{
/**
* CommonException
@param e
@return
*/

@ExceptionHandler(CommonException. class)
@ResponseBody
publicBaseResulthandlerException(CommonExceptione)
{
//异常返回false,Result是上一篇接口返回对象。
returnnew BaseResult(e.getMessage(),e.getCode());
}
}

5 调用结果

@RestController
@Slf4j
public classTestController{
@Autowired
private ITestServiceClient iTestServiceClient;

@GetMapping("/testMethod")
public BaseResult testMethod()throws Exception {
try {
log.info("通过feign调用system2服务~~~~~~~~~");
R<String> stringR = iTestServiceClient.testRequestMethod();
catch (Exception e) {
thrownew CommonException(ResultCode.SYS_EXCEPTION.getCode(), ResultCode.SYS_EXCEPTION.getMsg());
}
return BaseResult.success();
}


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

6 总结

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

来源: juejin.cn/post/7241395887943303226

>>

END

精品资料,超赞福利,免费领

微信扫码/长按识别 添加【技术交流群

群内每天分享精品学习资料

最近开发整理了一个用于速刷面试题的小程序;其中收录了上千道常见面试题及答案(包含基础并发JVMMySQLRedisSpringSpringMVCSpringBootSpringCloud消息队列等多个类型),欢迎您的使用。

👇👇

👇点击"阅读原文",获取更多资料(持续更新中