Loading...
墨滴

lyq

2021/11/05  阅读:37  主题:默认主题

Spring Cloud OpenFeign源码解析(纠错篇)

Spring Cloud OpenFeign源码解析(纠错篇)


前言

在Spring Cloud Netflix Ribbon源码解析(五)中,有关OpenFeign与Ribbon结合的源码分析中,关于AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig)这段代码的分析中,我错误的推断此处调用的是RibbonLoadBalancingHttpClient.execute方法,忘记了同学,请回看。由于是通过子类FeignLoadBalancer调用的executeWithLoadBalancer方法,所以这里其实是调用的FeignLoadBalancerexecute方法,那么今天纠正下这个错误,重新分析下这块相关的源码。

源码分析

FeignLoadBalancer

@Override
public RibbonResponse execute(RibbonRequest request, IClientConfig configOverride)
  throws IOException 
{
 Request.Options options;
 if (configOverride != null) {
  //如果客户端配置了openfeign相关超时时间,就是用openfeign的
  RibbonProperties override = RibbonProperties.from(configOverride);
  options = new Request.Options(override.connectTimeout(this.connectTimeout),
    override.readTimeout(this.readTimeout));
 }
 else {
  //如果没有提供,就是用ribbon的超时时间
  options = new Request.Options(this.connectTimeout, this.readTimeout);
 }
 Response response = request.client().execute(request.toRequest(), options);
 return new RibbonResponse(request.getUri(), response);
}

见代码注释。 调用request.client().execute(request.toRequest(), options)方法,首先通过request.client()返回一个client,然后调用它的execute方法。先来看看这个client是什么。

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);
  //在此处设置的client,即this.delegate
  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);
 }
}

这个client是在调用LoadBalancerFeignClientexecute方法时被设置到了RibbonRequest对象中的,而这个client是通过构造器传入的。

public LoadBalancerFeignClient(Client delegate,
 CachingSpringLoadBalancerFactory lbClientFactory,
  SpringClientFactory clientFactory)
 
{
 this.delegate = delegate;
 this.lbClientFactory = lbClientFactory;
 this.clientFactory = clientFactory;
}

之前介绍过LoadBalancerFeignClient是在FeignRibbonClientAutoConfiguration中导入并配置的

@ConditionalOnClass({ ILoadBalancer.classFeign.class })
@ConditionalOnProperty(value 
"spring.cloud.loadbalancer.ribbon.enabled",
  matchIfMissing = true)
@Configuration(proxyBeanMethods = false)
@AutoConfigureBefore(FeignAutoConfiguration.class)
@EnableConfigurationProperties(
{ FeignHttpClientProperties.class })
// Order is important herelast should be the defaultfirst 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 
{
 ......
}

通过@Import注解导入了HttpClientFeignLoadBalancedConfiguration.class、OkHttpFeignLoadBalancedConfiguration.class、DefaultFeignLoadBalancedConfiguration.class三个配置类

HttpClientFeignLoadBalancedConfiguration.class

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(ApacheHttpClient.class)
@ConditionalOnProperty(value 
"feign.httpclient.enabled", matchIfMissing = true)
@Import(HttpClientFeignConfiguration.class)
class HttpClientFeignLoadBalancedConfiguration 
{

 @Bean
 @ConditionalOnMissingBean(Client.class)
 public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
   SpringClientFactory clientFactoryHttpClient httpClient
{
  ApacheHttpClient delegate = new ApacheHttpClient(httpClient);
  return new LoadBalancerFeignClient(delegate, cachingFactory, clientFactory);
 }

}

条件是classpath中存在ApacheHttpClient.class并且在上下文中没有Client.class类型的bean时,向spring容器中注册Client.class类型的bean,并通过构造器传入ApacheHttpClient对象。

OkHttpFeignLoadBalancedConfiguration.class

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(OkHttpClient.class)
@ConditionalOnProperty("feign.okhttp.enabled")
@Import(OkHttpFeignConfiguration.class)
class OkHttpFeignLoadBalancedConfiguration 
{

 @Bean
 @ConditionalOnMissingBean(Client.class)
 public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
   SpringClientFactory clientFactoryokhttp3.OkHttpClient okHttpClient
{
  OkHttpClient delegate = new OkHttpClient(okHttpClient);
  return new LoadBalancerFeignClient(delegate, cachingFactory, clientFactory);
 }

}

条件是classpath中存在OkHttpClient.class并且feign.okhttp.enabled=true并且在上下文中没有Client.class类型的bean时,向spring容器中注册Client.class类型的bean,并通过构造器传入OkHttpClient对象

DefaultFeignLoadBalancedConfiguration.class

@Configuration(proxyBeanMethods = false)
class DefaultFeignLoadBalancedConfiguration {

 @Bean
 @ConditionalOnMissingBean
 public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
   SpringClientFactory clientFactory)
 
{
  return new LoadBalancerFeignClient(new Client.Default(nullnull), cachingFactory,
    clientFactory);
 }

}

在上面2个配置类中条件都不满足的情况下,会默认向spring上下文中注册Client.class类型的bean,并通过构造器传入new Client.Default(null, null)对象。

有了上面的分析,在我当前的环境下,默认生效的就是DefaultFeignLoadBalancedConfiguration配置类,那么LoadBalancerFeignClient构造器中接收到的client就是new Client.Default(null, null)的实例。 回到FeignLoadBalancer类的execute方法中,继续看看new Client.Default(null, null)execute方法调用情况

public interface Client {
    Response execute(Request var1, Options var2) throws IOException;

    public static class Default implements Client {
        private final SSLSocketFactory sslContextFactory;
        private final HostnameVerifier hostnameVerifier;

        public Default(SSLSocketFactory sslContextFactory, HostnameVerifier hostnameVerifier) {
            this.sslContextFactory = sslContextFactory;
            this.hostnameVerifier = hostnameVerifier;
        }

        public Response execute(Request request, Options options) throws IOException {
            HttpURLConnection connection = this.convertAndSend(request, options);
            return this.convertResponse(connection).toBuilder().request(request).build();
        }
  ......
   }

......

}

可以清晰的看到,默认使用的HttpURLConnection发起的远程调用。至于怎么切换底层的http客户端,在关于OpenFeign的源码分析中已经介绍过,经过今天的讲解,相信大家理解起来会更加深刻。

补充

上次我们提到AbstractLoadBalancerAwareClient有很多子类,并且错误的引导大家:在OpenFeign与Ribbon的结合下,最终调用的是RibbonLoadBalancingHttpClientexecute方法。正确的解释是:在OpenFeign与Ribbon组合下,使用的是FeignLoadBalancer这个子类。那么上次提到的在RibbonClientConfiguration中导入并配置的AbstractLoadBalancerAwareClient的子类(见<<Spring Cloud Netflix Ribbon源码解析(五)>>)是在什么时候被用到呢?答案是在Ribbon单独使用时,并通过配置ribbon.httpclient.enabled=true或者ribbon.okhttp.enabled=true来启用的。

关于openfeign再补充一个知识点:在发起远程调用前,可以通过实现RequestInterceptor接口,并重写apply方法,然后注册到spring上下文中,就可以实现比如在请求中统一携带token值。最终这个拦截器类会在SynchronousMethodHandlertargetRequest中被调用:

Request targetRequest(RequestTemplate template) {
    for (RequestInterceptor interceptor : requestInterceptors) {
      interceptor.apply(template);
    }
    return target.apply(template);
  }

如分析有误,还请指正,谢谢!

更多原创文章,请扫码关注我的微信公众号
更多原创文章,请扫码关注我的微信公众号

lyq

2021/11/05  阅读:37  主题:默认主题

作者介绍

lyq