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

posted @ 2024-10-30 18:39  木马不是马  阅读(287)  评论(0编辑  收藏  举报