利用feign的重试机制刷新过期的请求Token

feign的重试默认是不开启的,一般再业务中对于重试都非常谨慎,特别是写操作,一定都要保证目标接口实现了幂等才能发起重试,不然就是灾难。但是在一些场景下重试可以简化实现逻辑,比如接口的jwt token过期了,那么在请求响应token过期错误的时候将token刷新后发起重试可以避免请求失败,并且也减少了token有效期的维护逻辑。

因为不是全局开启重试,单独写一个Config类然后作为参数传递给feign client是最好的方式。

流程上大概是从请求结果得知token过期并重置token,然后交给重试器去判断是否需要重置,随后在重试的请求拦截器内刷新token并设置新的token。这里需要自定义feign的RequestInterceptor 、Retryer、ErrorDecoder 一起组合完成判断、重试和token的刷新

public class UserApiClientConfig {

    @Bean
    public RequestInterceptor requestInterceptor() {
        return requestTemplate -> requestTemplate.header("Authorization", TokenProvider.getToken());
    }

    @Bean
    public Retryer retryer() {
        return new Retryer() {
            int tryTimes;

            @Override
            public void continueOrPropagate(RetryableException e) {
                // try only once
                if (this.tryTimes++ >= 1) {
                    throw e;
                }
                if (e instanceof MyRetryException && e.getClass().equals(MyRetryException.class)) {
                    return;
                }
                throw e;
            }

            @Override
            public Retryer clone() {
                return this;
            }
        };
    }

    @Bean
    public ErrorDecoder errorDecoder() {
        return (methodKey, response) -> {
            FeignException exception = FeignException.errorStatus(methodKey, response);
            if (response.status() == HttpStatus.PAYMENT_REQUIRED.value()) {
                // 这里也可以对response.body再做一次判断
                // String bodyString = exception.contentUTF8();
                // 重试之前将token重置,然后在重试请求的interceptor里面通过getToken触发重新获取
                TokenProvider.resetToken();
                return new MyRetryException(response.status(), exception.getMessage(),
                        response.request().httpMethod(), exception, null, response.request());
            }

            return exception;
        };
    }

    static class MyRetryException extends RetryableException {

        public MyRetryException(int status, String message, Request.HttpMethod httpMethod, Throwable cause, Date retryAfter, Request request) {
            super(status, message, httpMethod, cause, retryAfter, request);
        }
    }

}

feign通过调用配置的Retryer.continueOrPropagate判断是否可以重试,如果抛exception那么重试就终止。需要重试的时候直接return,详细可以参考Retryer接口的Default实现,默认情况下开启的话feign会重试5次。我们只需要刷新token,因此重试一次即可,加计数器并在clone方法里传递到下一次重试中用于判断重试次数

为了方便判断我定义了一个MyRetryException并在errorDecoder里面判断http状态码为token过期状态码(我们使用的是402),更为严谨的做法是还要判断响应内容的业务状态码。在读取http response body的时候需要注意,在FeignException.errorStatus方法内已经将body读取并关闭了stream,这时可以通过FeignException的contentUTF8方法读取

如果要看重试的情况,可以开启debug日志,在重试前,feign会打印日志如下

---> RETRYING
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容