Feign请求拦截、失败重试与全局异常捕获
feign注册
spring加载的时候通过@EnableFeignClients的FeignClientsRegistrar注册扫描所以得FeignClient以及Configuration,最终注册为ReflectiveFeign,最终通过代理类FeignInvocationHandler实现方法的调用,在
FeignInvocationHandler中通过SynchronousMethodHandler方法执行实际逻辑
当调用Feignclient里面的方法的时候最终会知道SynchronousMethodHandler的invoke方法
这里主要总结下feign的失败重试逻辑与异常捕获情况
feign异常
在SynchronousMethodHandler方法中会首先调用executeAndDecode方法,可以看到调用client.execute请求方法之后如果调用失败了则会执行errorExecuting直接返回RetryableException,
还有一个重要的方法是asyncResponseHandler.handleResponse,到这一步表示HTTP请求成功了,这里面将会根据响应码以及FeignClient中的方法返回类型反序列化来处理响应结果
首先判断是否为内定的Response类型,如果不是会根据项目配置的Decoder,可以根据自己需要配置fastjson或者jackjson以及其他的序列号工具
@Bean
public Decoder feignDecoder() {
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
List<MediaType> supportedMediaTypes = new ArrayList<>();
supportedMediaTypes.add(MediaType.ALL);
converter.setSupportedMediaTypes(supportedMediaTypes);
ObjectFactory<HttpMessageConverters> objectFactory = () -> new HttpMessageConverters(converter);
return new ResponseEntityDecoder(new SpringDecoder(objectFactory));
}
如果是其他响应码会直接调用ErrorDecoder接口的decode()方法,默认的errorDecoder为ErrorDecoder.Default,默认返回FeignExcpetion,如果响应头当中含有Retry-After,则会返回RetryableException异常信息,进行后续逻辑来判断是否需要重试,但是这个默认的比较鸡肋,需要响应端提前在响应头加这个字段,调用第三方的接口时无法完成。
所以可以自己定义ErrorDecoder实现定制化的功能,比如只有当响应码为500的时候返回RetryableException后续进行重试
@Bean
public ErrorDecoder errorDecoder() {
return (methodKey, response) -> {
FeignException exception = errorStatus(methodKey, response);
if (response.status() >= 500 && response.status() <= 599) {
exception = new RetryableException(
response.status(),
exception.getMessage(),
response.request().httpMethod(),
exception,
null,
response.request());
}
return exception;
};
}
也可以自己根据响应码指定异常信息:
public class CustomErrorDecoder implements ErrorDecoder {
@Override
public Exception decode(String methodKey, Response response) {
int status = response.status();
switch (status) {
case 400:
return new CustomFeignException(status, "Bad Request");
case 401:
return new CustomFeignException(status, "Unauthorized");
case 403:
return new CustomFeignException(status, "Forbidden");
case 404:
return new CustomFeignException(status, "Not Found");
case 500:
return new CustomFeignException(status, "Internal Server Error");
default:
return new Exception(response.reason());
}
}
}
feign重试
如果前面步骤返回了RetryableException,则会调用Rtryer的continueOrPropagate方法,
默认为不重试,Reteyer里面内部类定义了一个Default重试方法,通过最大次数以及重试间隔来让线程休眠一段时间达到重试,在这期间调用FeignClient的线程一直处于阻塞中,所以重试不能间隔太长时间,这期间如果服务重启重试的线程则会直接断掉,所以如果不是页面操作调用接口的可以直接异步线程调用FeignClient的方法,或者自定义重试逻辑,存入延迟队列或者借助mq来处理调用失败的接口,
@Bean
public Retryer retryer() {
// 参数分别为:最大重试次数、初始等待时间(毫秒)、最大等待时间(毫秒)
return new Retryer.Default(3, 1000, 5000);
}
从invoke方法中可以看出feign还不支持全局的异常信息捕获,只能在调用feignClent的地方手动进行try catch
feign请求拦截
在SynchronousMethodHandler#executeAndDecode第一行首先会调用targetRequest
会依次调用注入的RequestInterceptor,所以可以在这里干很多事,比如请求头加入Authorization或者请求参数添加签名等等操作,像经常使用的Basic验证(即用户名密码验证),feign内部实现了基础的BasicAuthRequestInterceptor,我们用的时候只需要在FeignConfiguration注入bean
@Bean
public RequestInterceptor requestInterceptor(Config config) {
return new BasicAuthRequestInterceptor(config.getUsername(), config.getPassword(), Charset.defaultCharset());
}
ErrorDecode只有在请求成功的时候才会调用,如果连接超时或者网络错误,直接会抛出RetryableException进行重试,不会调用decode,所以不适合做全局异常处理
feignClient的configuration请不要加@Configuration注解,否则会全局生效,因为在FeignClientsRegistrar#registerFeignClients会执行registerClientConfiguration根据BeanDefinition自动注入这个Configuration