美文网首页
Feign整合Ribbon和Hystrix源码解析

Feign整合Ribbon和Hystrix源码解析

作者: Java学习录 | 来源:发表于2019-11-11 09:15 被阅读0次

    在上篇文章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的负载均衡了,具体内容可以参考之前的文章

    相关文章

      网友评论

          本文标题:Feign整合Ribbon和Hystrix源码解析

          本文链接:https://www.haomeiwen.com/subject/jobzbctx.html