Spring-cloud Feign 的理解

栏目: Java · 发布时间: 7年前

内容简介:feign的调用流程读取注解信息:feigh流程:

feign的调用流程

读取注解信息: EnableFeignClients --> FeignClientsRegistrar --> FeignClientFactoryBean

feigh流程: ReflectiveFeign --> Contract --> SynchronousMethodHandler

相关configuration: FeignClientsConfigurationFeignAutoConfigurationDefaultFeignLoadBalancedConfigurationFeignRibbonClientAutoConfiguration (Ribbon)

在FeignClientsRegistrar中:

@Override
    public void registerBeanDefinitions(AnnotationMetadata metadata,
            BeanDefinitionRegistry registry) {
        //注册feign配置信息
        registerDefaultConfiguration(metadata, registry);
        //注册feign client
        registerFeignClients(metadata, registry);
    }

    private void registerFeignClient(BeanDefinitionRegistry registry,
            AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
        String className = annotationMetadata.getClassName();
        //准备注入FeignClientFactoryBean
        BeanDefinitionBuilder definition = BeanDefinitionBuilder
                .genericBeanDefinition(FeignClientFactoryBean.class);
        ...
    }

查看FeignClientFactoryBean:

@Override
    public Object getObject() throws Exception {
        FeignContext context = applicationContext.getBean(FeignContext.class);
        //构建Feign.Builder
        Feign.Builder builder = feign(context);
        //如果注解没有指定URL
        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));
        }
        //如果指定了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) {
                // 因为指定了URL且classpath下有Ribbon,获取client的delegate(unwrap)
                // 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 targeter.target(this, builder, context, new HardCodedTarget<>(
                this.type, this.name, url));
    }
    
    protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
            HardCodedTarget<T> target) {
        //获取Feign Client实例
        Client client = getOptional(context, Client.class);
        if (client != null) {
            builder.client(client);
            //DefaultTargeter或者HystrixTargeter
            Targeter targeter = get(context, Targeter.class);
            //调用builder的target,其中就调用了Feign的newInstance
            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?");
    }

FeignClientsConfiguration 配置了 Feign.Builder ,prototype类型:

@Bean
    @Scope("prototype")
    @ConditionalOnMissingBean
    public Feign.Builder feignBuilder(Retryer retryer) {
        return Feign.builder().retryer(retryer);
    }

Feign的Builder.build返回了一个 ReflectiveFeign :

public Feign build() {
      SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
          new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
                                               logLevel, decode404);
      ParseHandlersByName handlersByName =
          new ParseHandlersByName(contract, options, encoder, decoder,
                                  errorDecoder, synchronousMethodHandlerFactory);
      //ReflectiveFeign构造参数
      //ParseHandlersByName作用是通过传入的target返回代理接口下的方法的各种信息(MethodHandler)
      //Contract:解析接口的方法注解规则,生成MethodMetadata
      //Options:Request超时配置
      //Encoder:请求编码器
      //Decoder:返回解码器
      //ErrorDecoder:错误解码器
      //SynchronousMethodHandler.Factory是构建SynchronousMethodHandler的工厂
      //Client:代表真正执行HTTP的组件
      //Retryer:该组决定了在http请求失败时是否需要重试
      //RequestInterceptor:请求前的拦截器
      //Logger:记录日志组件,包含各个阶段记录日志的方法和留给用户自己实现的log方法
      //Logger.Level:日志级别
      //decode404:处理404的策略,返回空还是报错
      //synchronousMethodHandlerFactory通过所有的信息去包装一个synchronousMethodHandler,在调用invoke方法的时候执行HTTP
      return new ReflectiveFeign(handlersByName, invocationHandlerFactory);
    }

在调用 Feign.Builder 的target的时候,调用了 ReflectiveFeign.newInstance :

/**
   * creates an api binding to the {@code target}. As this invokes reflection, care should be taken
   * to cache the result.
   */
  @SuppressWarnings("unchecked")
  @Override
  //接收Target参数(包含feign代理接口的类型class,名称,http URL)
  public <T> T newInstance(Target<T> target) {
    //首先通过**ParseHandlersByName**解析出接口中包含的方法,包装RequestTemplate,组装成<name, MethodHandler>
    Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
    Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
    //接口default方法List
    List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();

    for (Method method : target.type().getMethods()) {
      if (method.getDeclaringClass() == Object.class) {
        continue;
      } else if(Util.isDefault(method)) {
        DefaultMethodHandler handler = new DefaultMethodHandler(method);
        defaultMethodHandlers.add(handler);
        methodToHandler.put(method, handler);
      } else {
        methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
      }
    }
    //InvocationHandlerFactory.Default()返回了一个ReflectiveFeign.FeignInvocationHandler对象,通过传入的methodHandler map 调用目标对象的对应方法
    InvocationHandler handler = factory.create(target, methodToHandler);
    //生成JDK代理对象
    T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[]{target.type()}, handler);
    //绑定接口的默认方法到代理对象
    for(DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
      defaultMethodHandler.bindTo(proxy);
    }
    return proxy;
  }

生成Feign代理对象的基本流程图:

Spring-cloud Feign 的理解

当调用接口方法时,实际上就是调用代理对象invoke方法:

@Override
  public Object invoke(Object[] argv) throws Throwable {
    //工厂创建请求模版
    RequestTemplate template = buildTemplateFromArgs.create(argv);
    //每次克隆一个新的Retryer
    Retryer retryer = this.retryer.clone();
    while (true) {
      try {
        //这里调用实际的Feign client execute
        return executeAndDecode(template);
      } catch (RetryableException e) {
        //失败重试
        retryer.continueOrPropagate(e);
        if (logLevel != Logger.Level.NONE) {
          logger.logRetry(metadata.configKey(), logLevel);
        }
        continue;
      }
    }
  }

DefaultFeignLoadBalancedConfiguration 里实例化了 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);
            //delegate这里是Client.Default实例,底层调用的是java.net原生网络访问
            FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
                    this.delegate, request, uriWithoutHost);

            IClientConfig requestConfig = getClientConfig(options, clientName);
            //executeWithLoadBalancer会根据ribbon的负载均衡算法构建url,这里不展开
            return lbClient(clientName).executeWithLoadBalancer(ribbonRequest,
                    requestConfig).toResponse();
        }
        catch (ClientException e) {
            IOException io = findIOException(e);
            if (io != null) {
                throw io;
            }
            throw new RuntimeException(e);
        }
    }

以上所述就是小编给大家介绍的《Spring-cloud Feign 的理解》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Programming in Haskell

Programming in Haskell

Graham Hutton / Cambridge University Press / 2007-1-18 / GBP 34.99

Haskell is one of the leading languages for teaching functional programming, enabling students to write simpler and cleaner code, and to learn how to structure and reason about programs. This introduc......一起来看看 《Programming in Haskell》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码