Spring Cloud Feign 熔断、异常处理
Spring Cloud Alibaba 用Feign调取服务时,如果服务端发生异常要传递给客户端,或者客户端拦截到异常响应做相应的业务处理,可有几种实现。此例子源码请看GitHub
一、默认熔断策略
我使用的熔断组件是sentinel,其他的熔断组件一样。Feign都是支持的;
- 开启熔断开关
feign:
sentinel:
enabled: true
- 配置熔断策略,也就是在@FeignClient客户端接口中配置 fallback 参数;
@Component
@FeignClient(name = ServiceConstants.FEIGN_SERVICE,fallback = FeignServiceFallback.class,configuration = FeignConfig.class)
public interface FeignService {
@GetMapping(value = ServiceConstants.FEIGN_SERVICE_API_TEST)
TestObject getTestObject();
}
@Slf4j
@Component
public class FeignServiceFallback implements FeignService {
@Override
public TestObject getTestObject() {
log.error("服务调用接口异常");
return null;
}
}
FeignServiceFallback 类就是服务不可用时的返回策略。当然还有一个fallbackFactory 后边会说。
这有个问题,只有服务不可用时才会发生这个熔断,异常是不走这的。
二、异常处理
- Feign获取异常是实现一个ErrorDecoder接口来的,相当于可以自定义异常吧。
public class FeignException implements ErrorDecoder {
@Override
public Exception decode(String s, Response response) {
System.out.println("访问结果码:"+response.status());
return null;
}
}
配置类实例化;@FeignClient(configuration = FeignConfig.class) 上边有
@Component
public class FeignConfig {
@Bean
public ErrorDecoder errorDecoder(){
return new FeignException();
}
}
实现这个接口只能获取到非200状态的异常返回。正常请求是获取不到的。比如在服务端返回自定义status码的异常,这获取不到。只能获取到4xx、5xx。(http 接口项目,一般都会自定义返回结构和状态码。Http的状态码还是200。如果需要传递业务异常那需要另外的办法。)
三、业务异常捕获(1)
Feign定义的异常有几种基本都是继承的FeignException,Feign因为使用HttpClient或okHttp 调用服务接口,在Spring Mvc 中直接用全局异常catch不到。(谁有办法告知一下)所以只能在返回数据处做一下处理。
Feign提供了一个ResponseEntityDecoder类,对数据做编码处理,可以用这个类拦截一下返回数据。
public class FeignConfig {
@Autowired
private ObjectFactory<HttpMessageConverters> messageConverters;
@Bean
public Decoder feignDecoder(){
return new AwardResponseEntityDecoder(new SpringDecoder(this.messageConverters));
}
}
public class AwardResponseEntityDecoder extends ResponseEntityDecoder {
public AwardResponseEntityDecoder(Decoder decoder) {
super(decoder);
}
@Override
public Object decode(Response response, Type type) throws IOException,FeignException {
if (response.body() == null) {
throw new DecodeException(response.status(), "返回数据为空", response.request());
}
String bodyStr = Util.toString(response.body().asReader(Util.UTF_8));
//对结果进行转换
ResultBody resultBody=JSON.parseObject(bodyStr,ResultBody.class);
//此处如果不是正常的200状态,则抛出异常,异常就会进入熔断,走熔断策略
if (resultBody.getCode() != 200) {
throw new DecodeException(resultBody.code, resultBody.message, response.request());
}
return jsonToobj(bodyStr,type);
}
/**
* 反序列化
* @param jsonStr
* @param targetType
* @param <T>
* @return
*/
public static <T> T jsonToobj(String jsonStr, Type targetType) {
try {
JavaType javaType = TypeFactory.defaultInstance().constructType(targetType);
ObjectMapper mapper = new ObjectMapper();
return mapper.readValue(jsonStr, javaType);
} catch (IOException e) {
throw new IllegalArgumentException("Feign JSON转换为对象异常:" + jsonStr, e);
}
}
}
服务端发生异常有统一返回包装类,如下;如果没有异常则返回的是一个正常的对象(本例为TestObject )。所以在上边会先转化为ResultBody 对象,如果code有值则发生了异常。
public class ResultBody implements Serializable {
public boolean success;
public int code;
public String message;
public Object data;
public Long timestamp = System.currentTimeMillis();
///省略.....
}
这样根据返回内容抛出DecodeException会直接进入熔断处理器。二中的 FeignServiceFallback
我的客户端也统一返回ResultBody所以返回结果是这样的:
{
"success": true,
"code": 200,
"message": "",
"data": null,
"timestamp": 1600155600278
}
data中的null就是FeignServiceFallback返回的内容。可以返回对应对象。
进一步处理业务异常问题,当前只是知道了服务端发生了异常,并不知道具体发生了什么。我想直接返回服务端异常内容。
四、业务异常捕获(2)
把(三) 的内容改造一下即可直接返回服务端异常。
fallback 改为 fallbackFactory
@Component
@FeignClient(name = ServiceConstants.FEIGN_SERVICE,fallbackFactory = GeneralFallbackFactory.class,configuration = FeignConfig.class)
public interface FeignService {
@GetMapping(value = ServiceConstants.FEIGN_SERVICE_API_TEST)
TestObject getTestObject();
}
/**
* FeignClient 设置fallbackFactory 返回统一格式异常 与fallback不能共用
* ServiceException 是自定义的异常类,ErrorCode.SERVICE_ERROR是自定义错误码,请自行创建。
*/
@Component
public class GeneralFallbackFactory implements FallbackFactory {
@Override
public Object create(Throwable throwable) {
throw new ServiceException(throwable.getMessage(), ErrorCode.SERVICE_ERROR);
}
}
全局异常处理异常ServiceException
@ExceptionHandler(value = ServiceException.class)
public ResultBody handleServiceException(ServiceException se) {
logger.error("ServiceException:{}", se.getMessage());
return new ResultBody(se.getErrorCode().code, se.getMessage());
}
FallbackFactory 也是熔断处理器,其中可以获取到异常信息,所以使用FallbackFactory 客户端可以看到异常信息,抛出异常捕获。当然FallbackFactory还有其他用法请根据需求自行处理。
现在客户端调用获得的结果可以直接显示服务端异常:
{
"success": false,
"code": 2000,
"message": "未登录",
"data": null,
"timestamp": 1600156631671
}