Feign 是一个声明式 Web服务客户端,使用它创建一个接口并注解,使得编写 Web服务客户端变得更加容易。
它支持可插拔注解,包括 Feign 注解和 JAX-RS 注解,还支持可插播得编码、解码器。
Cloud 增加了对 Spring MVC 注解的支持,默认使用 httpmessageconverter 的支持。
Cloud 集成了 Ribbon 和 Eureka以及 BalanceLoad,使得在使用 Feign 时支持 Http 客户端的负载均衡。
Feign 自定义的客户端配置不需要使用 @Configuration 配置注解。如果使用了,需要排除在 @ComponentScan 注解扫描之外。否则,它将成为 feign.Encoder、feign.Decoder、feign.Contract等组件的默认实现。如果指定了,可以在 @ComponentScan 中显示排除。
@EnableFeignClients 导入客户端注册类。
通过图解,我们知道它会在容器期间注册一些 bean的定义。
org.springframework.cloud.openfeign.FeignClientsRegistrar 代码片段 @Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { //注册默认的配置 registerDefaultConfiguration(metadata, registry); //注册客户端 registerFeignClients(metadata, registry); } public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { //忽略部分逻辑代码... //@EnableFeignClients 无指定客户端类情况下,设置过滤筛选的注解类和获取扫描基础包路径 if (clients == null || clients.length == 0) { scanner.addIncludeFilter(annotationTypeFilter); basePackages = getBasePackages(metadata); } for (String basePackage : basePackages) { Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage); for (BeanDefinition candidateComponent : candidateComponents) { if (candidateComponent instanceof AnnotatedBeanDefinition) { //加载@FeignClient 注解的属性集 Map<String, Object> attributes = annotationMetadata.getAnnotationAttributes(FeignClient.class.getCanonicalName()); //加载客户端名称,其 contextId 可覆盖 value属性 String name = getClientName(attributes); //注册客户端配置 registerClientConfiguration(registry, name,attributes.get("configuration")); //注册客户端实例Bean(重点) registerFeignClient(registry, annotationMetadata, attributes); } } } } private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map<String, Object> attributes) { //获取被注解的目标类型 String className = annotationMetadata.getClassName(); //生成 FeignClientFactoryBean bean的定义(重点) BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class); validate(attributes); definition.addPropertyValue("url", getUrl(attributes)); definition.addPropertyValue("path", getPath(attributes)); String name = getName(attributes); definition.addPropertyValue("name", name); String contextId = getContextId(attributes); definition.addPropertyValue("contextId", contextId); definition.addPropertyValue("type", className); definition.addPropertyValue("decode404", attributes.get("decode404")); definition.addPropertyValue("fallback", attributes.get("fallback")); definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory")); definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); String alias = contextId + "FeignClient"; AbstractBeanDefinition beanDefinition = definition.getBeanDefinition(); boolean primary = (Boolean) attributes.get("primary"); // has a default, won't be null beanDefinition.setPrimary(primary); String qualifier = getQualifier(attributes); if (StringUtils.hasText(qualifier)) { alias = qualifier; } BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, new String[] { alias }); BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry); }从上述代码可以看到向容器中注册一个 FeignClientFactoryBean 类型的 bean 定义,由它生成最终目标接口的 bean 实例。
通过图解,我们知道 FeignClientFactoryBean 实现了 FactoryBean、InitializingBean,通过 FactoryBean 获取到自定义的Bean实例。
org.springframework.cloud.openfeign.FeignClientFactoryBean 代码片段 <T> T getTarget() { //FeignContext工厂类,能够为每个feign客户端创建一个IOC子容器,并创建相关组件的实例 FeignContext context = this.applicationContext.getBean(FeignContext.class); //创建Builder实例(构造Http API的工厂实例),设置相关组件配置(编码、解码器和拦截器(RequestInterceptor)等) Feign.Builder builder = feign(context); if (!StringUtils.hasText(this.url)) { if (!this.name.startsWith("http")) { this.url = "http://" + this.name; } else { this.url = this.name; } //path 不为空,追加到url后面,作为前缀 this.url += cleanPath(); //负载均衡 return (T) loadBalance(builder, context, new HardCodedTarget<>(this.type, this.name, this.url)); } //忽略部分逻辑代码... } protected <T> T loadBalance(Feign.Builder builder, FeignContext context, HardCodedTarget<T> target) { //加载Feign 负载均衡客户端(LoadBalancerFeignClient) Client client = getOptional(context, Client.class); if (client != null) { builder.client(client); //获取(HystrixTargeter) Targeter targeter = get(context, Targeter.class); return targeter.target(this, builder, context, target); } }feign.Feign 代码片段 public Feign build() { //创建代理方法处理器的工厂实例 SynchronousMethodHandler.Factory synchronousMethodHandlerFactory = new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger, logLevel, decode404, closeAfterDecode, propagationPolicy); //创建请求方法 转 Rest请求的方法执行(MethodHandler) 解析处理实例 ParseHandlersByName handlersByName = new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder, errorDecoder, synchronousMethodHandlerFactory); //创建feign 反射,实例化代理成目标接口实例 return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder); }
feign.ReflectiveFeign 代码片段 public <T> T newInstance(Target<T> target) { //获取目标接口方法的执行实例 Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target); Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>(); List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>(); //遍历目标接口方法 for (Method method : target.type().getMethods()) { if (method.getDeclaringClass() == Object.class) { continue; } else if (Util.isDefault(method)) { DefaultMethodHandler handler = new DefaultMethodHandler(method); defaultMethodHandlers.add(handler); methodToHandler.put(method, handler); } else { //所有方法执行实例,添加到分发器中 methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method))); } } //创建目标方法调用执行实例,对目标接口所有方法的执行都会通过该实例发出调用 InvocationHandler handler = factory.create(target, methodToHandler); //创建目标接口的代理实例 T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[] {target.type()}, handler); for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) { defaultMethodHandler.bindTo(proxy); } return proxy; } feign.ReflectiveFeign.ParseHandlersByName 代码片段 public Map<String, MethodHandler> apply(Target key) { //解析出方法的元数据 List<MethodMetadata> metadata = contract.parseAndValidatateMetadata(key.type()); Map<String, MethodHandler> result = new LinkedHashMap<String, MethodHandler>(); for (MethodMetadata md : metadata) { BuildTemplateByResolvingArgs buildTemplate; //根据传递的参数方式,生成相应的模板构建器 //该构建器在请求调用期间会解析出请求的模板, if (!md.formParams().isEmpty() && md.template().bodyTemplate() == null) { buildTemplate = new BuildFormEncodedTemplateFromArgs(md, encoder, queryMapEncoder); } else if (md.bodyIndex() != null) { buildTemplate = new BuildEncodedTemplateFromArgs(md, encoder, queryMapEncoder); } else { buildTemplate = new BuildTemplateByResolvingArgs(md, queryMapEncoder); } //创建目标接口方法的执行实例(SynchronousMethodHandler) result.put(md.configKey(), factory.create(key, md, buildTemplate, options, decoder, errorDecoder)); } return result; }
参数扩展点:在解析方法的元数据期间,我们可以自定义方法参数的处理器 (实现 AnnotatedParameterProcessor 接口),指定请求参数的位置。在构建请求模板的时候,可以通过 QueryMapEncoder 实现实例完成参数的解析、转换。
示例 @SpringQueryMap 注解结合 FieldQueryMapEncoder,完成 Get Http请求的 复合对象传参的解析、转换。
至此,通过JDK的动态代理机制,返回目标接口的代理实例。
请求扩展点:在构造请求实例期间,可以通过实现 RequestInterceptor 接口,并结合配置文件(可参照FeignClientProperties配置属性)指定客户端实例的请求拦截器。
示例,可参照 BasicAuthRequestInterceptor。
通过上述请求模板的构建,生成请求实例后,进入请求调用阶段。
org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient 代码片段 @Override public Response execute(Request request, Request.Options options) throws IOException { try { URI asUri = URI.create(request.url()); String clientName = asUri.getHost(); URI uriWithoutHost = cleanUrl(request.url(), clientName); FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(this.delegate, request, uriWithoutHost); IClientConfig requestConfig = getClientConfig(options, clientName); return lbClient(clientName) .executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse(); } catch (ClientException e) { throw new RuntimeException(e); } }通过 LoadBalancerFeignClient 实例调用 lbClient 方法,通过 CachingSpringLoadBalancerFactory 创建了 FeignLoadBalancer 负载均衡实例。
org.springframework.cloud.openfeign.ribbon.FeignLoadBalancer 代码片段 public FeignLoadBalancer(ILoadBalancer lb, IClientConfig clientConfig, ServerIntrospector serverIntrospector) { super(lb, clientConfig); this.setRetryHandler(RetryHandler.DEFAULT); this.clientConfig = clientConfig; //获取ribbon的配置,并作为默认的属性 this.ribbon = RibbonProperties.from(clientConfig); RibbonProperties ribbon = this.ribbon; this.connectTimeout = ribbon.getConnectTimeout(); this.readTimeout = ribbon.getReadTimeout(); this.serverIntrospector = serverIntrospector; } @Override public RibbonResponse execute(RibbonRequest request, IClientConfig configOverride) throws IOException { Request.Options options; if (configOverride != null) { //根据覆盖的配置,获取ribbon配置属性 RibbonProperties override = RibbonProperties.from(configOverride); //若覆盖的属性不存在,则使用ribbon的配置属性 options = new Request.Options(override.connectTimeout(this.connectTimeout), override.readTimeout(this.readTimeout)); } else { options = new Request.Options(this.connectTimeout, this.readTimeout); } Response response = request.client().execute(request.toRequest(), options); return new RibbonResponse(request.getUri(), response); } public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException { LoadBalancerCommand<T> command = buildLoadBalancerCommand(request, requestConfig); try { return command.submit( new ServerOperation<T>() { @Override public Observable<T> call(Server server) { URI finalUri = reconstructURIWithServer(server, request.getUri()); S requestForServer = (S) request.replaceUri(finalUri); try { return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig)); } catch (Exception e) { return Observable.error(e); } } }) .toBlocking() .single(); } catch (Exception e) {} }FeignLoadBalancer 实例初始化时,会加载 ribbon 的配置。最后在请求调用期间,若feign配置属性存在,则覆盖ribbon的配置属性。
com.netflix.loadbalancer.reactive.LoadBalancerCommand 代码片段 public Observable<T> submit(final ServerOperation<T> operation) { final ExecutionInfoContext context = new ExecutionInfoContext(); final int maxRetrysSame = retryHandler.getMaxRetriesOnSameServer(); final int maxRetrysNext = retryHandler.getMaxRetriesOnNextServer(); // Use the load balancer Observable<T> o = (server == null ? selectServer() : Observable.just(server)) .concatMap(new Func1<Server, Observable<T>>() { @Override // Called for each server being selected public Observable<T> call(Server server) { // Called for each attempt and retry Observable<T> o = Observable .just(server) .concatMap(new Func1<Server, Observable<T>>() { return operation.call(server).doOnEach(new Observer<T>() { }); } }); if (maxRetrysSame > 0) o = o.retry(retryPolicy(maxRetrysSame, true)); return o; } }); if (maxRetrysNext > 0 && server == null) o = o.retry(retryPolicy(maxRetrysNext, false)); return ... } private Observable<Server> selectServer() { return Observable.create(new OnSubscribe<Server>() { @Override public void call(Subscriber<? super Server> next) { try { Server server = loadBalancerContext.getServerFromLoadBalancer(loadBalancerURI, loadBalancerKey); next.onNext(server); next.onCompleted(); } catch (Exception e) {} } }); } com.netflix.loadbalancer.LoadBalancerContext 代码片段 public Server getServerFromLoadBalancer(@Nullable URI original, @Nullable Object loadBalancerKey) throws ClientException { String host = null; int port = -1; // Various Supported Cases // The loadbalancer to use and the instances it has is based on how it was registered // In each of these cases, the client might come in using Full Url or Partial URL ILoadBalancer lb = getLoadBalancer(); if (host == null) { // Partial URI or no URI Case // well we have to just get the right instances from lb - or we fall back if (lb != null){ Server svc = lb.chooseServer(loadBalancerKey); return svc; } } else { // Full URL Case // This could either be a vipAddress or a hostAndPort or a real DNS // if vipAddress or hostAndPort, we just have to consult the loadbalancer // but if it does not return a server, we should just proceed anyways // and assume its a DNS // For restClients registered using a vipAddress AND executing a request // by passing in the full URL (including host and port), we should only // consult lb IFF the URL passed is registered as vipAddress in Discovery boolean shouldInterpretAsVip = false; if (lb != null) { shouldInterpretAsVip = isVipRecognized(original.getAuthority()); } if (shouldInterpretAsVip) { Server svc = lb.chooseServer(loadBalancerKey); if (svc != null){ return svc; } } } return new Server(host, port); }至此,服务实例负载均衡筛选完毕。
接着进入请求执行,我们返回到 SynchronousMethodHandler ,它调用execute() 方法。
feign.Client 代码片段 @Override public Response execute(Request request, Options options) throws IOException { HttpURLConnection connection = convertAndSend(request, options); return convertResponse(connection, request); }通过 FeignClientProperties 可以配置重试,重试类在 FeignClientFactoryBean 中实例化,实例化的逻辑是 configureFeign 方法中,先从容器中查找 Retryer 中的 bean,如果有则填充到 Feign.Builder,再从 FeignClientProperties 中查找配置,如果有则再次填充 Feign.Builder,简单的讲就是覆盖逻辑。
org.springframework.cloud.openfeign.FeignClientFactoryBean 代码片段 protected void configureFeign(FeignContext context, Feign.Builder builder) { FeignClientProperties properties = this.applicationContext.getBean(FeignClientProperties.class); if (properties != null) { if (properties.isDefaultToProperties()) { //加载全局配置 configureUsingConfiguration(context, builder); //加载默认配置 configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder); //加载指定客户端的配置 configureUsingProperties(properties.getConfig().get(this.contextId), builder); } else { configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder); configureUsingProperties(properties.getConfig().get(this.contextId), builder); configureUsingConfiguration(context, builder); } } else { configureUsingConfiguration(context, builder); } }而feign真正执行重试请求的逻辑在代理类 SynchronousMethodHandler 中,该类是JDK动态代理后,最终执行的方法处理器。
feign.SynchronousMethodHandler 代码片段 public Object invoke(Object[] argv) throws Throwable { RequestTemplate template = buildTemplateFromArgs.create(argv); Options options = findOptions(argv); Retryer retryer = this.retryer.clone(); //循环,若调用无异常则立即返回;有异常,进入重试,计算重试时间间隔,通过线程休眠进行等待重新调用 while (true) { try { return executeAndDecode(template, options); } catch (RetryableException e) { try { //这里重试,无异常则代表可以继续重试 retryer.continueOrPropagate(e); } catch (RetryableException th) { Throwable cause = th.getCause(); if (propagationPolicy == UNWRAP && cause != null) { throw cause; } else { throw th; } } if (logLevel != Logger.Level.NONE) { logger.logRetry(metadata.configKey(), logLevel); } continue; } } }从上述流程看出,若调用异常,就会通过 Retryer 重试处理器进入重试流程。
feign.Retryer 代码片段 //默认实现 class Default implements Retryer { //最大重试次数 private final int maxAttempts; //重试周期 private final long period; //最大重试周期 private final long maxPeriod; //重试次数 int attempt; long sleptForMillis; public Default() { this(100, SECONDS.toMillis(1), 5); } public Default(long period, long maxPeriod, int maxAttempts) { this.period = period; this.maxPeriod = maxPeriod; this.maxAttempts = maxAttempts; this.attempt = 1; } public void continueOrPropagate(RetryableException e) { //重试次数 大于 最大重试次数,则抛出异常 if (attempt++ >= maxAttempts) { throw e; } //计算重试的时间间隔 long interval; if (e.retryAfter() != null) { interval = e.retryAfter().getTime() - currentTimeMillis(); if (interval > maxPeriod) { interval = maxPeriod; } if (interval < 0) { return; } } else { interval = nextMaxInterval(); } try { //线程阻塞、休眠 Thread.sleep(interval); } catch (InterruptedException ignored) { Thread.currentThread().interrupt(); throw e; } sleptForMillis += interval; } long nextMaxInterval() { long interval = (long) (period * Math.pow(1.5, attempt - 1)); return interval > maxPeriod ? maxPeriod : interval; } @Override public Retryer clone() { return new Default(period, maxPeriod, maxAttempts); } } //不重试实现(也是Feign默认的重试机制) Retryer NEVER_RETRY = new Retryer() { @Override public void continueOrPropagate(RetryableException e) { throw e; } @Override public Retryer clone() { return this; } };我们知道 Spring Cloud 为 Feign 集成了 Ribbon ,提供了 Http 客户端的负载均衡支持。不仅自身提供了重试机制,也支持 Ribbon 的 重试机制。
在容器启动期间,根据类路径下是否有 spring-retry jar 包(即是有没 RetryTemplate 类型),创建了 RibbonLoadBalancedRetryFactory 工厂实例,用于在创建 Feign 客户端负载均衡时,创建可重试实例。
org.springframework.cloud.openfeign.ribbon.FeignRibbonClientAutoConfiguration 代码片段 @Bean @Primary @ConditionalOnMissingBean @ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate") public CachingSpringLoadBalancerFactory retryabeCachingLBClientFactory( SpringClientFactory factory, LoadBalancedRetryFactory retryFactory) { // LoadBalancedRetryFactory ,来源于 RibbonAutoConfiguration return new CachingSpringLoadBalancerFactory(factory, retryFactory); }我们直接进入 Feign 执行重试请求的代理类 SynchronousMethodHandler。
feign.SynchronousMethodHandler 代码片段 Object executeAndDecode(RequestTemplate template, Options options) throws Throwable { Request request = targetRequest(template); Response response; try { //客户端执行请求,client 的实例是 LoadBalancerFeignClient 类型 response = client.execute(request, options); } catch (IOException e) { throw errorExecuting(request, e); } boolean shouldClose = true; try { if (Response.class == metadata.returnType()) { if (response.body() == null) { return response; } if (response.body().length() == null || response.body().length() > MAX_RESPONSE_BUFFER_SIZE) { shouldClose = false; return response; } // Ensure the response body is disconnected byte[] bodyData = Util.toByteArray(response.body().asInputStream()); return response.toBuilder().body(bodyData).build(); } if (response.status() >= 200 && response.status() < 300) { if (void.class == metadata.returnType()) { return null; } else { Object result = decode(response); shouldClose = closeAfterDecode; return result; } } else if (decode404 && response.status() == 404 && void.class != metadata.returnType()) { Object result = decode(response); shouldClose = closeAfterDecode; return result; } else { throw errorDecoder.decode(metadata.configKey(), response); } } catch (IOException e) { throw errorReading(request, response, e); } finally { if (shouldClose) { ensureClosed(response.body()); } } }进入 LoadBalancerFeignClient 的 execute 方法查看到会调用 CachingSpringLoadBalancerFactory 创建 Feign 的负载均衡器。
org.springframework.cloud.openfeign.ribbon.CachingSpringLoadBalancerFactory 代码片段 public FeignLoadBalancer create(String clientName) { FeignLoadBalancer client = this.cache.get(clientName); if (client != null) { return client; } IClientConfig config = this.factory.getClientConfig(clientName); ILoadBalancer lb = this.factory.getLoadBalancer(clientName); ServerIntrospector serverIntrospector = this.factory.getInstance(clientName, ServerIntrospector.class); client = this.loadBalancedRetryFactory != null ? new RetryableFeignLoadBalancer(lb, config, serverIntrospector, this.loadBalancedRetryFactory) : new FeignLoadBalancer(lb, config, serverIntrospector); this.cache.put(clientName, client); return client; }RetryableFeignLoadBalancer 是 Feign 可重试负载均衡客户端实例,利用了 spring retry 去重试失败的请求。类图如下:
在创建好实例之后,执行 executeWithLoadBalancer 方法,它在 AbstractLoadBalancerAwareClient 父类中,最终还是调用子类实例的 execute 方法。
org.springframework.cloud.openfeign.ribbon.RetryableFeignLoadBalancer 代码片段 public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException { //构造负载均衡的指令 LoadBalancerCommand<T> command = buildLoadBalancerCommand(request, requestConfig); try { return command.submit( new ServerOperation<T>() { @Override public Observable<T> call(Server server) { URI finalUri = reconstructURIWithServer(server, request.getUri()); S requestForServer = (S) request.replaceUri(finalUri); try { //执行请求 return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig)); } catch (Exception e) {} } }) .toBlocking() .single(); } catch (Exception e) {} } @Override public RibbonResponse execute(final RibbonRequest request, IClientConfig configOverride) throws IOException { final Request.Options options; if (configOverride != null) { //加载、设置ribbon的承诺书配置 RibbonProperties ribbon = RibbonProperties.from(configOverride); options = new Request.Options(ribbon.connectTimeout(this.connectTimeout), ribbon.readTimeout(this.readTimeout)); } else { options = new Request.Options(this.connectTimeout, this.readTimeout); } //创建重试策略 final LoadBalancedRetryPolicy retryPolicy = this.loadBalancedRetryFactory.createRetryPolicy(this.getClientName(), this); //创建重试模板 RetryTemplate retryTemplate = new RetryTemplate(); //创建回退策略 BackOffPolicy backOffPolicy = this.loadBalancedRetryFactory.createBackOffPolicy(this.getClientName()); retryTemplate.setBackOffPolicy(backOffPolicy == null ? new NoBackOffPolicy() : backOffPolicy); //创建重试监听器 RetryListener[] retryListeners = this.loadBalancedRetryFactory.createRetryListeners(this.getClientName()); if (retryListeners != null && retryListeners.length != 0) { retryTemplate.setListeners(retryListeners); } //根据条件是否开启重试策略,或者代理 retryTemplate.setRetryPolicy(retryPolicy == null ? new NeverRetryPolicy() : new FeignRetryPolicy(request.toHttpRequest(), retryPolicy, this,this.getClientName())); return retryTemplate.execute(new RetryCallback<RibbonResponse, IOException>() { //重试执行回调 @Override public RibbonResponse doWithRetry(RetryContext retryContext)throws IOException { Request feignRequest = null; // on retries the policy will choose the server and set it in the context // extract the server and update the request being made if (retryContext instanceof LoadBalancedRetryContext) { ServiceInstance service = ((LoadBalancedRetryContext) retryContext).getServiceInstance(); if (service != null) { feignRequest = ((RibbonRequest) request .replaceUri(reconstructURIWithServer(new Server(service.getHost(), service.getPort()), request.getUri()))).toRequest(); } } if (feignRequest == null) { feignRequest = request.toRequest(); } Response response = request.client().execute(feignRequest, options); if (retryPolicy != null && retryPolicy.retryableStatusCode(response.status())) { byte[] byteArray = response.body() == null ? new byte[] {} : StreamUtils.copyToByteArray(response.body().asInputStream()); response.close(); throw new RibbonResponseStatusCodeException(RetryableFeignLoadBalancer.this.clientName, response,byteArray, request.getUri()); } return new RibbonResponse(request.getUri(), response); } }, new LoadBalancedRecoveryCallback<RibbonResponse, Response>() { //兜底执行回调 @Override protected RibbonResponse createResponse(Response response, URI uri) { return new RibbonResponse(uri, response); } }); }官方文档 Spring Cloud的 Feign和Ribbon重试机制的误区 配置Feign重试机制