Feign整合Ribbon和Hystrix源码解析

36次阅读

共计 8347 个字符,预计需要花费 21 分钟才能阅读完成。

在上篇文章 Feign 自动装配中,我们提到了 Feign 的自动装配的原理, 以及 Feign 整合 Ribbon 和 Hystrix 的核心在类 FeignClientFactoryBean 中,那么本篇文章就来揭开这个类的神秘面纱

首先,我们看到这个类实现了 FactoryBean 这个接口,这个接口的主要作用就是利用 getObject() 来创建一些实例化过程比较复杂的 bean,更多关于这个接口的内容可以参考这篇文章:Spring 扩展点之 FactoryBean 接口

我们直接来看这个类的 getObject 方法:

    public Object getObject() throws Exception {FeignContext context = applicationContext.getBean(FeignContext.class);
        Feign.Builder builder = feign(context);

        if (!StringUtils.hasText(this.url)) {
            String url;
            if (!this.name.startsWith("http")) {url = "http://" + this.name;}
            else {url = this.name;}
            url += cleanPath();
            return loadBalance(builder, context, new HardCodedTarget<>(this.type,
                    this.name, 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 lod 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 targeter.target(this, builder, context, new HardCodedTarget<>(this.type, this.name, url));
    }
  1. 获取 bean:FeignContext,这个 bean 上篇文章已经说过了。里面包含了各个 Feign 客户端的配置
2. 构建 Feign.Builder

设置编解码器

    protected Feign.Builder feign(FeignContext context) {FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
        Logger logger = loggerFactory.create(this.type);

        Feign.Builder builder = get(context, Feign.Builder.class)
                .logger(logger)
                .encoder(get(context, Encoder.class))
                .decoder(get(context, Decoder.class))
                .contract(get(context, Contract.class));
        configureFeign(context, builder);

        return builder;
    }

设置日志、重试策略、错误 code 解析、超时时间、拦截器

    protected void configureUsingConfiguration(FeignContext context, Feign.Builder builder) {Logger.Level level = getOptional(context, Logger.Level.class);
        if (level != null) {builder.logLevel(level);
        }
        Retryer retryer = getOptional(context, Retryer.class);
        if (retryer != null) {builder.retryer(retryer);
        }
        ErrorDecoder errorDecoder = getOptional(context, ErrorDecoder.class);
        if (errorDecoder != null) {builder.errorDecoder(errorDecoder);
        }
        Request.Options options = getOptional(context, Request.Options.class);
        if (options != null) {builder.options(options);
        }
        Map<String, RequestInterceptor> requestInterceptors = context.getInstances(this.name, RequestInterceptor.class);
        if (requestInterceptors != null) {builder.requestInterceptors(requestInterceptors.values());
        }

        if (decode404) {builder.decode404();
        }
    }
  1. 判断 Feign 是否指定 url 属性,正常情况下是没有指定 url 的,所以会添加一个 http:// 前缀
4. 获取代理
protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
                            HardCodedTarget<T> target) {Client client = getOptional(context, Client.class);
    if (client != null) {builder.client(client);
        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?");
}

首先获取 Client 的实现类,这个实现类是 LoadBalancerFeignClient, 这个类里融合了 Ribbon 的相关内容。然后将Client 包装到 Feign.Builder 中,接着获取 Targeter,这里我们存在 Hystrix 环境,所以Targeter 的实现类为HystrixTargeter

    public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
                        Target.HardCodedTarget<T> target) {if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {return feign.target(target);
        }
        feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign;
        SetterFactory setterFactory = getOptional(factory.getName(), context,
            SetterFactory.class);
        if (setterFactory != null) {builder.setterFactory(setterFactory);
        }
        Class<?> fallback = factory.getFallback();
        if (fallback != void.class) {return targetWithFallback(factory.getName(), context, target, builder, fallback);
        }
        Class<?> fallbackFactory = factory.getFallbackFactory();
        if (fallbackFactory != void.class) {return targetWithFallbackFactory(factory.getName(), context, target, builder, fallbackFactory);
        }

        return feign.target(target);
    }

接着以 Feign 客户端设置了 fallback 为例

    private <T> T targetWithFallback(String feignClientName, FeignContext context,Target.HardCodedTarget<T> target,HystrixFeign.Builder builder, Class<?> fallback) {T fallbackInstance = getFromContext("fallback", feignClientName, context, fallback, target.type());
        return builder.target(target, fallbackInstance);
    }

接着就是这个代理的创建,现在这个代理中包含了 Ribbon 和 Hystrix。而这个代理类的实现类是HystrixInvocationHandler

  public Object invoke(final Object proxy, final Method method, final Object[] args)
      throws Throwable {
    // early exit if the invoked method is from java.lang.Object
    // code is the same as ReflectiveFeign.FeignInvocationHandler
    if ("equals".equals(method.getName())) {
      try {
        Object otherHandler =
            args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
        return equals(otherHandler);
      } catch (IllegalArgumentException e) {return false;}
    } else if ("hashCode".equals(method.getName())) {return hashCode();
    } else if ("toString".equals(method.getName())) {return toString();
    }

    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() {if (fallbackFactory == null) {return super.getFallback();
        }
        try {Object fallback = fallbackFactory.create(getExecutionException());
          Object result = fallbackMethodMap.get(method).invoke(fallback, args);
          if (isReturnsHystrixCommand(method)) {return ((HystrixCommand) result).execute();} else if (isReturnsObservable(method)) {
            // Create a cold Observable
            return ((Observable) result).toBlocking().first();
          } else if (isReturnsSingle(method)) {
            // Create a cold Observable as a Single
            return ((Single) result).toObservable().toBlocking().first();} else if (isReturnsCompletable(method)) {((Completable) result).await();
            return null;
          } else {return result;}
        } catch (IllegalAccessException e) {
          // shouldn't happen as method is public due to being an interface
          throw new AssertionError(e);
        } catch (InvocationTargetException e) {
          // Exceptions on fallback are tossed by Hystrix
          throw new AssertionError(e.getCause());
        }
      }
    };

    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();}
    return hystrixCommand.execute();}

这里就利用到了 Hystrix 的知识,更多关于 Hystrix 的内容可以参考之前的文章

接着深入 invoke 方法

public Object invoke(Object[] argv) throws Throwable {RequestTemplate template = buildTemplateFromArgs.create(argv);
    Retryer retryer = this.retryer.clone();
    while (true) {
        try {return executeAndDecode(template);
        } catch (RetryableException e) {retryer.continueOrPropagate(e);
            if (logLevel != Logger.Level.NONE) {logger.logRetry(metadata.configKey(), logLevel);
            }
            continue;
        }
    }
}

这里构建了请求信息和重试策略,具体请求内容在下面:

Object executeAndDecode(RequestTemplate template) throws Throwable {Request request = targetRequest(template);

    if (logLevel != Logger.Level.NONE) {logger.logRequest(metadata.configKey(), logLevel, request);
    }

    Response response;
    long start = System.nanoTime();
    try {response = client.execute(request, options);
      // ensure the request is set. TODO: remove in Feign 10
      response.toBuilder().request(request).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);

    boolean shouldClose = true;
    try {if (logLevel != Logger.Level.NONE) {
        response =
            logger.logAndRebufferResponse(metadata.configKey(), logLevel, response, elapsedTime);
        // ensure the request is set. TODO: remove in Feign 10
        response.toBuilder().request(request).build();}
      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 {return decode(response);
        }
      } else if (decode404 && response.status() == 404 && void.class != metadata.returnType()) {return decode(response);
      } else {throw errorDecoder.decode(metadata.configKey(), response);
      }
    } catch (IOException e) {if (logLevel != Logger.Level.NONE) {logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime);
      }
      throw errorReading(request, response, e);
    } finally {if (shouldClose) {ensureClosed(response.body());
      }
    }
  }

再往下深入就是 Ribbon 的负载均衡了,具体内容可以参考之前的文章

正文完
 0