概述
SpringCloud feign 对Netflix feign进行了包装和增强,本文从源码角度梳理一个请求链路中参与的各个组件及其它们如何配合交互完成请求。组件包括feign、ribbon、hystrix、sleuth等
核心类及流程
上图为部分流程图,大致可以分为以下3个步骤
- 元数据的注册
FeignClient配置及BeanDefinition的注册等 - Feign初始化
Feign的上下文信息、负载均衡代理、Feign代理的实例化等 - 请求调用
接受请求,进行负载均衡,调用http client客户端完成请求调用等
源码分析
从上文中的几个方面来解读源码
元数据的注册
从EnableXXX开始 -> @EnableFeignClients:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
...
}
//通过import方式,引入FeignClientsRegistrar,处理逻辑在这
class FeignClientsRegistrar
implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
//1. 先注册默认配置
registerDefaultConfiguration(metadata, registry);
//2. 注册所有的feignClient BeanDefinition
registerFeignClients(metadata, registry);
}
}
实现了ImportBeanDefinitionRegistrar接口,开始进行BeanDefinition的注入,分别看下registerBeanDefinitions中的两个方法
//注册默认配置
private void registerDefaultConfiguration(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
Map<String, Object> defaultAttrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName(), true);
if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
String name;
if (metadata.hasEnclosingClass()) {
name = "default." + metadata.getEnclosingClassName();
}
else {
name = "default." + metadata.getClassName();
}
//以default为前缀,注册feignClient configuration 第一处
registerClientConfiguration(registry, name,
defaultAttrs.get("defaultConfiguration"));
}
}
上述方法读取@EnableFeignClients注解中声明的defaultConfiguration配置,name以default为前缀,一般情况不需要配置。
//注册所有的FeignClient 的BeanDefinition
public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
...
//获取扫描目录下面所有的bean deanDefinition
for (String basePackage : basePackages) {
Set<BeanDefinition> candidateComponents = scanner
.findCandidateComponents(basePackage);
for (BeanDefinition candidateComponent : candidateComponents) {
if (candidateComponent instanceof AnnotatedBeanDefinition) {
// verify annotated class is an interface
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
Assert.isTrue(annotationMetadata.isInterface(),
"@FeignClient can only be specified on an interface");
Map<String, Object> attributes = annotationMetadata
.getAnnotationAttributes(
FeignClient.class.getCanonicalName());
String name = getClientName(attributes);
//注册feignClient configuration 第二处
registerClientConfiguration(registry, name,
attributes.get("configuration"));
//注册feignClient
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
}
上述两个代码片段中都调用了registerClientConfiguration,注册了FeignClientSpecification,这个类很重要,是feign client配置的载体,包含FeignClient的核心配置重试策略、超时策略、日志等配置。某个服务没有设置就读取默认(default)的配置。再看看单个FeignClient的注册registerFeignClient
private void registerFeignClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
String className = annotationMetadata.getClassName();
//注册FeignClientFactoryBean
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientFactoryBean.class);
// ... 装配@FeignClient定义的元数据
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
new String[] { alias });
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
扫描到所有@FeignClient的BeanDefinition后,包装成FeignClientFactoryBean,然后注册到spring上下文中。FeignClientFactoryBean是后续处理的关键,它是一个FactoryBean,也即一个代理,在从spring容器中注入时进行被代理对象的处理。到此配置信息及BeanDefinition注册结束。
Feign初始化
承接上文,直接分析FeignClientFactoryBean,这个类是Feign初始化的最核心的类,先整体看下代码结构
@Override
public Object getObject() throws Exception {
return getTarget();
}
<T> T getTarget() {
//实例化FeignContext
FeignContext context = this.applicationContext.getBean(FeignContext.class);
//实例化Feign.Builder,用来生成Feign
Feign.Builder builder = feign(context);
//判断生成代理对象的url, 如果为空,走负载均衡,生成负载均衡的代理类
if (!StringUtils.hasText(this.url)) {
if (!this.name.startsWith("http")) {
this.url = "http://" + this.name;
}
else {
this.url = this.name;
}
this.url += cleanPath();
//负载均衡
return (T) loadBalance(builder, context,
new HardCodedTarget<>(this.type, this.name, this.url));
}
if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
this.url = "http://" + this.url;
}
String url = this.url + cleanPath();
Client client = getOptional(context, Client.class);
if (client != null) {
if (client instanceof LoadBalancerFeignClient) {
// not load balancing because we have a url,
// but ribbon is on the classpath, so unwrap
client = ((LoadBalancerFeignClient) client).getDelegate();
}
builder.client(client);
}
Targeter targeter = get(context, Targeter.class);
//生成默认代理类,不走负载均衡直连
return (T) targeter.target(this, builder, context,
new HardCodedTarget<>(this.type, this.name, url));
}
FeignClientFactoryBean 完成了调用前的所有准备工作,如FeignContext,Feign.builder的创建、判断是否需要负载均衡及设置及Feign的代理类的创建等,下文逐个说明
第一步 实例化Feign上下文FeignContext
FeignContext是Feign初始化需要的上下信息,通过FeignAutoConfiguration引入
@Configuration
@ConditionalOnClass(Feign.class)
@EnableConfigurationProperties({ FeignClientProperties.class,
FeignHttpClientProperties.class })
public class FeignAutoConfiguration {
@Autowired(required = false)
private List<FeignClientSpecification> configurations = new ArrayList<>();
@Bean
public HasFeatures feignFeature() {
return HasFeatures.namedFeature("Feign", Feign.class);
}
@Bean
public FeignContext feignContext() {
FeignContext context = new FeignContext();
context.setConfigurations(this.configurations);
return context;
}
}
FeignAutoConfiguration
是Feign 配置的第一个入口,在这里上文中注册的FeignClientSpecification派上了用场,注入到了FeignClientSpecification集合,顺便提一下Spring支持集合类型对象的注入如List,Map等。在没有引入Sleuth时会注入FeignContext,引入Sleuth之后呢?再看下TraceFeignClientAutoConfiguration:
@Configuration
@ConditionalOnProperty(value = "spring.sleuth.feign.enabled", matchIfMissing = true)
@ConditionalOnClass({ Client.class, FeignContext.class })
@ConditionalOnBean(HttpTracing.class)
@AutoConfigureBefore(FeignAutoConfiguration.class)
@AutoConfigureAfter({ SleuthHystrixAutoConfiguration.class,
TraceHttpAutoConfiguration.class })
public class TraceFeignClientAutoConfiguration {
@Configuration
@ConditionalOnProperty(name = "spring.sleuth.feign.processor.enabled", matchIfMissing = true)
protected static class FeignBeanPostProcessorConfiguration {
//引入Sleuth后,初始化FeignContextBeanPostProcessor
@Bean
static FeignContextBeanPostProcessor feignContextBeanPostProcessor(
BeanFactory beanFactory) {
return new FeignContextBeanPostProcessor(beanFactory);
}
}
}
final class FeignContextBeanPostProcessor implements BeanPostProcessor {
//PostProcessor 将FeignContext包装成TraceFeignContext
@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
if (bean instanceof FeignContext && !(bean instanceof TraceFeignContext)) {
return new TraceFeignContext(traceFeignObjectWrapper(), (FeignContext) bean);
}
return bean;
}
}
@AutoConfigureBefore(FeignAutoConfiguration.class)
,保证了在FeignAutoConfiguration
前初始化,这里定义了一个BeanPostProcessor, 将FeignContext包装成TranceFeignContext,加上了trace的配置信息
第二步 实例化Feign.Builder
Builder是Feign的构造器,进行Feign属性的设置,方便后续Feign对象的创建。回到主类FeignClientFactoryBean中的feign()方法
protected Feign.Builder feign(FeignContext context) {
FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
Logger logger = loggerFactory.create(this.type);
// 从FeignContext获取Feign.Builder
// @formatter:off
Feign.Builder builder = get(context, Feign.Builder.class)
// required values
.logger(logger)
.encoder(get(context, Encoder.class))
.decoder(get(context, Decoder.class))
.contract(get(context, Contract.class));
// @formatter:on
// 加载自定义的配置
configureFeign(context, builder);
return builder;
}
//从FeignContext中获取配置信息
protected <T> T get(FeignContext context, Class<T> type) {
T instance = context.getInstance(this.contextId, type);
if (instance == null) {
throw new IllegalStateException(
"No bean found of type " + type + " for " + this.contextId);
}
return instance;
}
从FeignContext中获取上下文信息来实例化Feign.Builder。回到上文中的FeignContext的创建过程,构造函数中一个入参
FeignClientsConfiguration
,这个类就是FeignClient的属性配置类,源码如下:
public class FeignContext extends NamedContextFactory<FeignClientSpecification> {
//构造函数,指定了FeignClientsConfiguration
public FeignContext() {
super(FeignClientsConfiguration.class, "feign", "feign.client.name");
}
}
public class FeignClientsConfiguration {
//其他属性设置
...
//缺省设置,直接使用Feign.Builder
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
public Feign.Builder feignBuilder(Retryer retryer) {
return Feign.builder().retryer(retryer);
}
//引入hystrix后,使用HystrixFeign.Builder
@Configuration
@ConditionalOnClass({ HystrixCommand.class, HystrixFeign.class })
protected static class HystrixFeignConfiguration {
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
@ConditionalOnProperty(name = "feign.hystrix.enabled")
public Feign.Builder feignHystrixBuilder() {
return HystrixFeign.builder();
}
}
}
引入Sleuth又会如何变化呢?继续深入看下上文中的TraceFeignClientAutoConfiguration,有如下片段
public class TraceFeignClientAutoConfiguration {
@Bean
@Scope("prototype")
@ConditionalOnClass(name = { "com.netflix.hystrix.HystrixCommand",
"feign.hystrix.HystrixFeign" })
@ConditionalOnProperty(name = "feign.hystrix.enabled", havingValue = "true")
Feign.Builder feignHystrixBuilder(BeanFactory beanFactory) {
return SleuthHystrixFeignBuilder.builder(beanFactory);
}
@Bean
@ConditionalOnMissingBean
@Scope("prototype")
@ConditionalOnProperty(name = "feign.hystrix.enabled", havingValue = "false", matchIfMissing = true)
Feign.Builder feignBuilder(BeanFactory beanFactory) {
return SleuthFeignBuilder.builder(beanFactory);
}
}
又出现了两个Builder, SleuthHystrixFeignBuilder、SleuthFeignBuilder,分别适用于有Hystrix和无Hystrix。跟进后会发现,它们内部仍然使用上述的两个Builder,Feign.Builder和HystrixFeign.Builder。更多的是将Client包装成了Sleuth的LazyClient。
第三步 实例化负载均衡代理
ribbon开始登场,继续回到主类FeignClientFactoryBean。
//1. 负载入口
protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
HardCodedTarget<T> target) {
//从FeignContext中获取Client
Client client = getOptional(context, Client.class);
if (client != null) {
builder.client(client);
//从FeignContext或获取Targeter
Targeter targeter = get(context, Targeter.class);
return targeter.target(this, builder, context, target);
}
throw new IllegalStateException(
"No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?");
}
//2. 负载配置入口
@ConditionalOnClass({ ILoadBalancer.class, Feign.class })
@Configuration
@AutoConfigureBefore(FeignAutoConfiguration.class)
@EnableConfigurationProperties({ FeignHttpClientProperties.class })
// Order is important here, last should be the default, first should be optional
// see
// https://github.com/spring-cloud/spring-cloud-netflix/issues/2086#issuecomment-316281653
@Import({ HttpClientFeignLoadBalancedConfiguration.class,
OkHttpFeignLoadBalancedConfiguration.class,
DefaultFeignLoadBalancedConfiguration.class })
public class FeignRibbonClientAutoConfiguration {
...
}
//3. 默认配置
class DefaultFeignLoadBalancedConfiguration {
@Bean
@ConditionalOnMissingBean
public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
SpringClientFactory clientFactory) {
return new LoadBalancerFeignClient(new Client.Default(null, null), cachingFactory,
clientFactory);
}
}
从FeignContext中获取的Client, Client在FeignRibbonClientAutoConfiguration中自动配置,代码中@Import中的XXXFeignLoadBalancedConfiguration根据classpath中Http Client完成
LoadBalancerFeignClient
的实例化。从XXX名字可以看出, 分别对应Apache HttpClient、 OkHttpClient和Java HttpClient(默认)。这里建议使用Apache HttpClient或OkHttpClient,有线程池支持,性能更好。加上sleuth的支持又如何变化?看一看增强后的 TranceFeignContext
class TraceFeignContext extends FeignContext {
@Override
@SuppressWarnings("unchecked")
public <T> Map<String, T> getInstances(String name, Class<T> type) {
//从基类FeignContext中获取属性集合
Map<String, T> instances = this.delegate.getInstances(name, type);
if (instances == null) {
return null;
}
Map<String, T> convertedInstances = new HashMap<>();
//逐个遍历属性,进行wrap
for (Map.Entry<String, T> entry : instances.entrySet()) {
convertedInstances.put(entry.getKey(),
(T) this.traceFeignObjectWrapper.wrap(entry.getValue()));
}
return convertedInstances;
}
//把Client包装成TraceLoadBalancerFeignClient
Object wrap(Object bean) {
if (bean instanceof Client && !(bean instanceof TracingFeignClient)) {
if (ribbonPresent && bean instanceof LoadBalancerFeignClient
&& !(bean instanceof TraceLoadBalancerFeignClient)) {
LoadBalancerFeignClient client = ((LoadBalancerFeignClient) bean);
return new TraceLoadBalancerFeignClient(
(Client) new TraceFeignObjectWrapper(this.beanFactory)
.wrap(client.getDelegate()),
factory(), (SpringClientFactory) clientFactory(),
this.beanFactory);
}
else if (ribbonPresent && bean instanceof TraceLoadBalancerFeignClient) {
return bean;
}
return new LazyTracingFeignClient(this.beanFactory, (Client) bean);
}
return bean;
}
}
TraceFeignContext 把FeignClient返回的Client包装成
TraceLoadBalancerFeignClient
。注意在没有引入ribbon、sleuth时Client直接使用默认对象Client.Default。 至此负载均衡的client代理就完成了创建。
第四步 实例化Feign代理
继续分析FeignClientFactoryBean, 有如下片段
Targeter targeter = get(context, Targeter.class);
return targeter.target(this, builder, context, target);
这里的Targeter是用来生成ReflectiveFeign(Feign的实现类)的工具类,有如下两种实现,再次回到FeignAutoConfiguration:
public class FeignAutoConfiguration {
@Configuration
@ConditionalOnClass(name = "feign.hystrix.HystrixFeign")
protected static class HystrixFeignTargeterConfiguration {
@Bean
@ConditionalOnMissingBean
public Targeter feignTargeter() {
return new HystrixTargeter();
}
}
@Configuration
@ConditionalOnMissingClass("feign.hystrix.HystrixFeign")
protected static class DefaultFeignTargeterConfiguration {
@Bean
@ConditionalOnMissingBean
public Targeter feignTargeter() {
return new DefaultTargeter();
}
}
}
默认使用DefaultTargeter, 引入hystrix后使用HystrixTargeter。继续跟进会发现最终是在实例化Feign代理
public abstract class Feign {
// Targeter 调用的地方
public <T> T target(Target<T> target) {
return build().newInstance(target);
}
//实例化Feign实现类并填充SynchronousMethodHandler.Factory
public Feign build() {
SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
logLevel, decode404, closeAfterDecode, propagationPolicy);
ParseHandlersByName handlersByName =
new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
errorDecoder, synchronousMethodHandlerFactory);
return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
}
}
public class ReflectiveFeign extends Feign {
//创建Feign代理类
@Override
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>();
//将Feign Client 接口中的所有方法,包装成MethodHandler
for (Method method : target.type().getMethods()) {
if (method.getDeclaringClass() == Object.class) {
continue;
} else if (Util.isDefault(method)) {//判断方法是否是默认方法,启用默认handler
DefaultMethodHandler handler = new DefaultMethodHandler(method);
defaultMethodHandlers.add(handler);
methodToHandler.put(method, handler);
} else {
methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
}
}
//创建InvocationHandler,调用时转发到methodHandler
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;
}}
到这就很明朗了,使用Targeter来创建ReflectiveFeign,同时引出了
SynchronousMethodHandler
,这个类很重要,是发送请求的关键类,后文再叙。也可以看到动态代理的基础类InvocationHandler,InvocationHandler的最终实现有两种选择:FeignInvocationHandler(无hystrix)、HystrixInvocationHandler(有hystrix)
//1. 使用Feign.Builder 最终使用的是FeignInvocationHandler
public abstract class Feign {
public static class Builder {
//默认
private InvocationHandlerFactory invocationHandlerFactory =
new InvocationHandlerFactory.Default();
}
}
static final class Default implements InvocationHandlerFactory {
@Override
public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {
return new ReflectiveFeign.FeignInvocationHandler(target, dispatch);
}
}
//2. 使用HystrixFeign.Builder 最终使用的是HystrixInvocationHandler
/** Configures components needed for hystrix integration. */
Feign build(final FallbackFactory<?> nullableFallbackFactory) {
super.invocationHandlerFactory(new InvocationHandlerFactory() {
@Override
public InvocationHandler create(Target target,
Map<Method, MethodHandler> dispatch) {
return new HystrixInvocationHandler(target, dispatch, setterFactory,
nullableFallbackFactory);
}
});
super.contract(new HystrixDelegatingContract(contract));
return super.build();
}
Feign 的动态代理创建完成,并交由Spring容器管理。第二阶段的初始化阶段至此结束。http请求发生时,创建的代理对象最终聚合ribbon、hystrix、sleuth的功能完成整个调用链路的增强或跟踪。
请求调用
上一章节的最后,创建了InvocationHandler,在请求发生时invoke将被调用。以引入hystrix场景说明,看一看HystrixInvocationHandler
HystrixInvocationHandler
final class HystrixInvocationHandler implements InvocationHandler {
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args)
throws Throwable {
...
HystrixCommand<Object> hystrixCommand =
new HystrixCommand<Object>(setterMethodMap.get(method)) {
@Override
protected Object run() throws Exception {
try {
return HystrixInvocationHandler.this.dispatch.get(method).invoke(args);
} catch (Exception e) {
throw e;
} catch (Throwable t) {
throw (Error) t;
}
}
@Override
protected Object getFallback() {
...
}
}
...
}
HystrixCommand hystrix功能的入口,其提供的fallback,熔断等都经由此入口进行增强。在run()方法中开始发起调用,找到方法对应的methodHandler,methodHandler中封装了loadBalanceClient、retryer等组件。methodHandler 上文已有提及,也就是SynchronousMethodHandler,继续跟进
final class SynchronousMethodHandler implements MethodHandler {
@Override
public Object invoke(Object[] argv) throws Throwable {
//根据请求参数构建RequestTemplate
RequestTemplate template = buildTemplateFromArgs.create(argv);
//定义重试策略
Retryer retryer = this.retryer.clone();
while (true) {
try {
// 重要方法在这里
return executeAndDecode(template);
} 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;
}
}
}
Object executeAndDecode(RequestTemplate template) throws Throwable {
Request request = targetRequest(template);
try {
//在初始化阶段创建的Client在这里开始调用
response = client.execute(request, options);
} catch (IOException e) {
...
}
//省略响应处理
...
}
}
这里已经很明确,LoadBalancerFeignClient (Ribbon)开始起作用了。继续跟进
public class LoadBalancerFeignClient implements Client {
@Override
public Response execute(Request request, Request.Options options) throws IOException {
try {
//获取URI
URI asUri = URI.create(request.url());
String clientName = asUri.getHost();
URI uriWithoutHost = cleanUrl(request.url(), clientName);
//封装成RibbonRequest请求
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) {
IOException io = findIOException(e);
if (io != null) {
throw io;
}
throw new RuntimeException(e);
}
}
}
public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
LoadBalancerCommand command = this.buildLoadBalancerCommand(request, requestConfig);
try {
return (IResponse)command.submit(new ServerOperation<T>() {
public Observable<T> call(Server server) {
URI finalUri = AbstractLoadBalancerAwareClient.this.reconstructURIWithServer(server, request.getUri());
ClientRequest requestForServer = request.replaceUri(finalUri);
try {
// 执行ribbon负载均衡请求
return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
} catch (Exception var5) {
return Observable.error(var5);
}
}
}).toBlocking().single();
} catch (Exception var6) {
Throwable t = var6.getCause();
if (t instanceof ClientException) {
throw (ClientException)t;
} else {
throw new ClientException(var6);
}
}
}
这里是ribbon发起请求的地方,至于到底选择何种http 客户端,在FeignRibbonClientAutoConfiguration中进行配置,上文已经说明。默认使用Client.Default设置的客户端
class Default implements Client {
@Override
public Response execute(Request request, Options options) throws IOException {
// 这里使用的java httpclient
HttpURLConnection connection = convertAndSend(request, options);
return convertResponse(connection, request);
}
}
写在最后:本文只梳理了feign如何与spring cloud生态中组件进行交互完成请求,每个组件的功能实现没有涉及,有兴趣的同学可以自行研究,特别是Hystrix和ribbon中使用的rxjava,可以参考
Hystrix核心基础 - 滑动窗口创建过程及demo