Spring Cloud 关于feign远程调用异常处理的问题
问题描述
现在有两个service分别是A和B,服务A调用服务B来注册新用户,如果该用户已经存在则抛出一个系统自定义异常,表示用户已经存在。但是 当A通过feign远程调用B的时候,如果B出现了异常:如下
那么服务A会获得一个FeignException(因为feign会将服务A的异常封装成FeignException):如下
怎么将服务b抛出的自定义异常给提取出来
为什么要将服务b的异常提取出来呢?因为在服务b可能会针对此类异常做一个处理亦或者返回给错误信息,但是如果被feign封装成FeignException之后是看不到错误消息的,并且前端的http status是500
其中response是的内容是
{"error":"[600 600] during [POST] to [http://hxds-cst/customer/registerNewCustomer] [CstServiceApi#registerNewCustomer(RegisterNewCustomerForm)]: [{\"error\":\"该微信无法注册\"}]"}
其中服务b有一个全局异常处理的配置如下:
当然有同学可能会说我给else if(e instanceof HxdsException)再加一个 || FeignException不就可以了吗?答案是肯定不可以的,因为feign会将下游服务即服务A的所有异常封装成FeignException,那么你怎么判断是否系统内部定义的Exception呢,如果不是自定义的异常那么我们是不能够去处理的
解决方案
我们现在的问题就是如何区分自定义异常和系统的异常从而来对不同类型的异常做一个处理。我们可以在发生异常的时候给我们的自定义异常做一个标志位,这里我采用的是status,给服务A返回的response添加一个status
服务A
在服务A中有一个全局异常处理的一个配置类,这个配置类会针对HxdsException(自定义异常类)做一个处理
@Slf4j
@RestControllerAdvice
public class ExceptionAdvice {
/**
* 处理业务异常
* @param response
* @param e
*/
@ExceptionHandler(HxdsException.class)
public String handleHxdsException(HttpServletResponse response, HxdsException e){
log.error(e.getMessage(), e);
//上游服务可以通过status来判断是否是自定义异常从而来进行处理
response.setStatus(FeignHttpStatus.HXDS_ERROR.getVal());
if (ObjectUtil.isNotEmpty(e.getCode())) {
response.addHeader("code",e.getCode() + "");
}
// post请求头中的数据如果有中文必须进行编码,接收端要进行编码,例如:URLDecoder.decode(msg,"uft-8")
String msg = "";
String data = "";
try {
if (StrUtil.isNotBlank(e.getMsg())) {
msg = URLEncoder.encode(e.getMsg(), "utf-8");
}
// if (ObjectUtil.isNotEmpty(e.getData())) {
// data = URLEncoder.encode(JSON.toJSONString(e.getData()),"utf-8");
// }
} catch (Exception e2) {
}
response.addHeader("msg",msg); //并且将HxdsException中的内容也放到response的header中
JSONObject json = new JSONObject();
json.set("error", e.getMsg());
//这里返回的json可以被前端获取到而不会被spring servlet包装因为是string 类型
return json.toString();
}
其中的FeignHttpsStatus是自定义的一个枚举类
/**
* 服务内部调用Feign Status
*/
public enum FeignHttpStatus {
HXDS_ERROR(600,"Feign errors"); //代表了系统自定义异常的https status
private final Integer val;
private final String res;
private FeignHttpStatus(Integer val,String res) {
this.val = val;
this.res = res;
}
public Integer getVal() {
return this.val;
}
public String getRes() {
return this.res;
}
}
这样我就给我们的自定义异常加上了status,那么上游服务就可以通过status 来判断是否是自定义异常
服务b
我们不得不提到feign提供的一个接口叫做ErrorDecoder, 是用来处理feign异常的,有一个方法需要实现
public Exception decode(String s, Response response),这个接口就是feign提供给我们当远程调用的时候出现异常做处理的接口
public class FeignErrorDecoder implements ErrorDecoder {
private final ErrorDecoder defaultErrorDecoder = new Default();
@Override
public Exception decode(String methodKey, Response response) {
// 获取Feign返回的原始异常信息
int status = response.status();
// 将 status == FeignHttpStatus.OKD_ERROR 的 Response 转化
if (FeignHttpStatus.HXDS_ERROR.getVal() == status) {
Map<String, Collection<String>> headers = response.headers();
String msg = headers.get("msg").iterator().next();
try {
msg = URLDecoder.decode(msg, "utf-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
String code = headers.get("code").iterator().next();
//抛出业务异常
return new HxdsException(msg, Integer.parseInt(code));
}
return defaultErrorDecoder.decode(methodKey, response);
}
}
在decode方法里面我们可以通过判断response的status来做处理,在这里我只针对自定义异常类做了处理(将response中code,msg提取出来然后封装成HxdsException)。
要想使FeignErrorDecoder生效,需要在feignclient中指明
@FeignClient(value = "hxds-cst", configuration = FeignErrorDecoder.class)
public interface CstServiceApi {
@PostMapping("/customer/registerNewCustomer")
public R registerNewCustomer(RegisterNewCustomerForm form) throws HxdsException;
@PostMapping("/customer/login")
public R login(LoginForm form);
@PostMapping("/customer/car/insertCustomerCar")
public R insertCustomerCar(InsertCustomerCarForm form);
@PostMapping("/customer/car/searchCustomerCarList")
public R searchCustomerCarList(SearchCustomerCarListForm form);
@PostMapping("/customer/car/deleteCustomerCarById")
public R deleteCustomerCarById(DeleteCustomerCarByIdForm form);
@PostMapping("/customer/searchCustomerOpenId")
public R searchCustomerOpenId(SearchCustomerOpenIdForm form);
}
验证结果
先看前端的响应状态
是我们自定义的状态码
再来看响应内容
大功告成!!!!!
2024年的第一篇博客