Spring Cloud Feign 分析(六)之FeignClient调用过程

前面我们总结了FeignClient代理对象的生成过程,这一节我们也直奔主题:FeignClient调用过程,了解FeignClient调用过程有利于我们提高分析与解决问题的能力!FeignClient的调用过程可以概括为两部分组成,前部分为Hystrix熔断,后部分为Ribbon负载均衡请求及响应返回值!


我们在Spring Cloud Feign 分析(五)之FeignClient代理生成过程中讲解过在生成代理对象的过程中,会给代理对象设置HystrixInvocationHandler动态代理方法,FeignClient接口触发的方法调用均会被这个动态代理方法拦截,所以我们分析下HystrixInvocationHandler这个动态代理方法做了什么事情?


HystrixInvocationHandler

final class HystrixInvocationHandler implements InvocationHandler {
  ......
  @Override
  public Object invoke(final Object proxy, final Method method, final Object[] args)
      throws Throwable {
    ......
    //创建Hystrix命令
    HystrixCommand<Object> hystrixCommand =
        new HystrixCommand<Object>(setterMethodMap.get(method)) {
          @Override
          protected Object run() throws Exception {
            try {
              //调用SynchronousMethodHandler#invoke(Object[] argv)方法
              return HystrixInvocationHandler.this.dispatch.get(method).invoke(args);
            } catch (Exception e) {
              throw e;
            } catch (Throwable t) {
              throw (Error) t;
            }
          }
          //熔断降级方法,默认new UnsupportedOperationException("No fallback available.");
          @Override
          protected Object getFallback() {......}
        };

    if (Util.isDefault(method)) {
      return hystrixCommand.execute();
    } else if (isReturnsHystrixCommand(method)) {
      return hystrixCommand;
    } else if (isReturnsObservable(method)) {
      // Create a cold Observable
      return hystrixCommand.toObservable();
    } else if (isReturnsSingle(method)) {
      // Create a cold Observable as a Single
      return hystrixCommand.toObservable().toSingle();
    } else if (isReturnsCompletable(method)) {
      return hystrixCommand.toObservable().toCompletable();
    } else if (isReturnsCompletableFuture(method)) {
      return new ObservableCompletableFuture<>(hystrixCommand);
    }
    //通过Hystrix触发调用,让整个方法调用具备熔断能力
    return hystrixCommand.execute();
  }
  ......
}

这个代理方法,我们一眼望穿秋水,原来是创建了HystrixCommand命令来进行目标方法的调用分发,这样就使得我们的整个方法调用具备了熔断能力,我们接着看看dispatch.get(method).invoke(args),这个分发器的定义是这样的Map<Method, MethodHandler> dispatch,方法及对应方法的Handler,看起来有点眼熟是吧,我们在分析五(代理生成过程)中ReflectiveFeign#newInstance()这个方法中通过targetToHandlersByName解析而来的,通过这个解析器我们即可得知这个MethodHandler对应SynchronousMethodHandler,所以我们接着分析SynchronousMethodHandler,看看这个同步方法Handler做了哪些事情


SynchronousMethodHandler

final class SynchronousMethodHandler implements MethodHandler {
  ......
  @Override
  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) {
        ......
        continue;
      }
    }
  }

  Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
    //创建目标Request
    //内部会调用我们自定义的RequestInterceptor拦截方法,比如添加traceId头信息用于跟踪等等
    Request request = targetRequest(template);
    if (logLevel != Logger.Level.NONE) {
      logger.logRequest(metadata.configKey(), logLevel, request);
    }
    Response response;
    long start = System.nanoTime();
    try {
      //执行LoadBalancerFeignClient#execute()
      response = client.execute(request, options);
      //确保请求参数已设置
      response = response.toBuilder()
          .request(request)
          .requestTemplate(template)
          .build();
    } catch (IOException e) {
      if (logLevel != Logger.Level.NONE) {
        logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));
      }
      throw errorExecuting(request, e);
    }
    long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
    //如果decoder有则使用
    if (decoder != null)
      return decoder.decode(response, metadata.returnType());
    //异步响应处理器对结果response做最后的解析
    CompletableFuture<Object> resultFuture = new CompletableFuture<>();
    asyncResponseHandler.handleResponse(resultFuture, metadata.configKey(), response,
        metadata.returnType(),
        elapsedTime);
    try {
      if (!resultFuture.isDone())
        throw new IllegalStateException("Response handling not done");
      //阻塞获取最终返回值
      return resultFuture.join();
    } catch (CompletionException e) {
      Throwable cause = e.getCause();
      if (cause != null)
        throw cause;
      throw e;
    }
  }
  ......
}

在SynchronousMethodHandler这个同步方法处理器中我们看到最终调用了这么一段response = client.execute(request, options);这个client从上下文方法传递中也能看出对应LoadBalancerFeignClient这个负载均衡client,至此我们前半段部分Hystrix熔断分析完毕,下文部分将接着讲解后半段部分Ribbon负载均衡请求相关的调用过程


FeignRibbonClientAutoConfiguration

@ConditionalOnClass({ ILoadBalancer.class, Feign.class })
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.ribbon.enabled",matchIfMissing = true)
@Configuration(proxyBeanMethods = false)
@AutoConfigureBefore(FeignAutoConfiguration.class)
@EnableConfigurationProperties({ FeignHttpClientProperties.class })
@Import({ HttpClientFeignLoadBalancedConfiguration.class,
        OkHttpFeignLoadBalancedConfiguration.class,
        DefaultFeignLoadBalancedConfiguration.class })
public class FeignRibbonClientAutoConfiguration {
    //无RetryTemplate则使用此FeignLoadBalancer实例缓存工厂
    @Bean
    @Primary
    @ConditionalOnMissingBean
    @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
    public CachingSpringLoadBalancerFactory cachingLBClientFactory(
            SpringClientFactory factory) {
        return new CachingSpringLoadBalancerFactory(factory);
    }
    //有RetryTemplate则使用此FeignLoadBalancer实例缓存工厂
    @Bean
    @Primary
    @ConditionalOnMissingBean
    @ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
    public CachingSpringLoadBalancerFactory retryabeCachingLBClientFactory(
            SpringClientFactory factory, LoadBalancedRetryFactory retryFactory) {
        return new CachingSpringLoadBalancerFactory(factory, retryFactory);
    }

    //设置默认请求选项,从Ribbon的SpringClientFactory获取IClientConfig配置参数
    @Bean
    @ConditionalOnMissingBean
    public Request.Options feignRequestOptions() {
        return LoadBalancerFeignClient.DEFAULT_OPTIONS;
    }
}

当前配置类会在FeignAutoConfiguration之前进行,CachingSpringLoadBalancerFactory工厂类会根据clientName创建FeignLoadBalancer并缓存供LoadBalancerFeignClient后续直接使用


DefaultFeignLoadBalancedConfiguration

@Configuration(proxyBeanMethods = false)
class DefaultFeignLoadBalancedConfiguration {
    @Bean
    @ConditionalOnMissingBean
    public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
            SpringClientFactory clientFactory) {
        return new LoadBalancerFeignClient(new Client.Default(null, null), cachingFactory,
                clientFactory);
    }
}

创建一个默认的Client => LoadBalancerFeignClient,具备负载均衡功能的Client,是Feign负载均衡客户端的默认实现。


CachingSpringLoadBalancerFactory

public class CachingSpringLoadBalancerFactory {
    private volatile Map<String, FeignLoadBalancer> cache = new ConcurrentReferenceHashMap<>();
    ......
    public FeignLoadBalancer create(String clientName) {
        //缓存中获取FeignLoadBalancer
        FeignLoadBalancer client = this.cache.get(clientName);
        if (client != null) {
            return client;
        }
        //通过Ribbon的SpringClientFactory工厂类获取Ribbon客户端相关配置接口
        IClientConfig config = this.factory.getClientConfig(clientName);
        //通过Ribbon的SpringClientFactory工厂类获取负载均衡接口
        ILoadBalancer lb = this.factory.getLoadBalancer(clientName);
        //通过Ribbon的SpringClientFactory工厂类获取安全端口接口
        ServerIntrospector serverIntrospector = this.factory.getInstance(clientName,
                ServerIntrospector.class);
        //创建FeignLoadBalancer
        client = this.loadBalancedRetryFactory != null
                ? new RetryableFeignLoadBalancer(lb, config, serverIntrospector,
                        this.loadBalancedRetryFactory)
                : new FeignLoadBalancer(lb, config, serverIntrospector);
        //放入缓存
        this.cache.put(clientName, client);
        return client;
    }
}

通过Ribbon对外提供的SpringClientFactory这个工厂类,我们可以获取各种Ribbon客户端相关的配置,Ribbon客户端配置详情介绍请参阅Spring Cloud Ribbon 分析(三)之RibbonClientConfiguration客户端配置


LoadBalancerFeignClient

public class LoadBalancerFeignClient implements Client {
    ......
    @Override
    public Response execute(Request request, Request.Options options) throws IOException {
        try {
            //转换为URI格式
            URI asUri = URI.create(request.url());
            //获取clientName
            String clientName = asUri.getHost();
            URI uriWithoutHost = cleanUrl(request.url(), clientName);
            //创建RibbonRequest
            FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
                    this.delegate, request, uriWithoutHost);
            //获取Ribbon IClientConfig
            IClientConfig requestConfig = getClientConfig(options, clientName);
            //创建FeignLoadBalancer并执行负载均衡方法
            return lbClient(clientName)
                    .executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();
        }
        catch (ClientException e) {
            IOException io = findIOException(e);
            if (io != null) {
                throw io;
            }
            throw new RuntimeException(e);
        }
    }
    ......
    //如果有LoadBalancedRetryFactory则创建RetryableFeignLoadBalancer
    private FeignLoadBalancer lbClient(String clientName) {
        return this.lbClientFactory.create(clientName);
    }
    ......
}

上文中lbClient(clientName).executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse()执行过程可以参阅Spring Cloud Ribbon 分析(四)之Feign集成中的LoadBalancerFeignClient标题部分,其中会分析通过Ribbon发起负载均衡到获取返回值的过程,这里我们就不做重复阐述。


至此,Feign模块总结也告一段落,这里笔者只总结和分析了流程中比较重要的几个部分,如果想知道更多Feign原理则需要亲自分析相关的知识点,如果文章对你有所帮助,就点赞关注吧!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容