Netflix Feign - Spring Cloud 整合 Feign 源码(三)

83 阅读2分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。


4. 实例化FeignClient

  1. 构造FeignClientFactoryBean的BeanDefinition注册到beanDefinitionMaps中。

     Spring IOC 
     AbstractApplicationContext
     refresh()
     // 注册 BeanDefinition 
     invokeBeanFactoryPostProcessors(beanFactory)
     
    
BeanDefinitionBuilder definition = BeanDefinitionBuilder
      .genericBeanDefinition(FeignClientFactoryBean.class);
  1. 回调FeignClientFactoryBean.getObject()方法获取FeignClient实例

FeignClientFactoryBean 实现了 FactoryBean,在SpringIOC第十一步finishBeanFactoryInitialization(beanFactory)通过FactoryBean.getObject()方法返回Bean实例。

class FeignClientFactoryBean
      implements FactoryBean<Object>, InitializingBean, ApplicationContextAware

getObject

getObject() 调用 getTraget() 方法。

FeignContext

获取FeignContext实例,通过构造器模式获得Feign.Builder实例。

FeignContext context = this.applicationContext.getBean(FeignContext.class);

FeignContexnt继承NamedContextFactory,通过Map<String, C> configurations提供各个FeignClient的上下文数据互相隔离的能力。

public class FeignContext extends NamedContextFactory<FeignClientSpecification>

FeignContext 构造时,加载FeignClientsConfiguration,注入默认的Decoder、Encoder、FeignLoggerFactory、Contract。

Logger:DefaultFeignLoggerFactory
Encoder:SpringEncoder
Decoder:SpringDecoder(ResponseEntityDecoder)
Contract:SpringMvcContract
Feign.Builder:Feign.builder().retryer(retryer);Retryer.NEVER_RETRY

FeignContext 内部保存一个 Map<String, C> configurations,由List<FeignClientSpecification> configurations的name和FeignClientSpecification映射而成。

List<FeignClientSpecification> configurations,来自于 FeignClientsRegister构造注册FeignClient的BeanDefinition之前,获取@FeignClient注解的name属性和configuration属性,构造FeignClientSpecification的BeanDefinition的集合。是所有@FeignClient注解的集合。


public class FeignAutoConfiguration {

   @Autowired(required = false)
   private List<FeignClientSpecification> configurations = new ArrayList<>();

   @Bean
   public FeignContext feignContext() {
      FeignContext context = new FeignContext();
      // 设置 configurations 填充 NamedContextFactory的configurations。
      context.setConfigurations(this.configurations);
      return context;
   }

Feign.Builder

Feign.Builder builder = feign(context);

通过feign方法,从FeignContext context上下文中根据当前FeignClientFactoryBean的contextId(就是@FeignClient注解设置测name)获取对应的子容器。

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

   // @formatter:off
   Feign.Builder builder = get(context, Feign.Builder.class)
         // required values
         .logger(logger)
         .encoder(get(context, Encoder.class))
         .decoder(get(context, Decoder.class))
         .contract(get(context, Contract.class));
   // @formatter:on

   configureFeign(context, builder);

   return builder;
}

通过context获取contextId,从Map<String, AnnotationConfigApplicationContext> contexts中获取对应的子容器,然后通过指定的BeanType从子容器中获取BeanType实例。然后放入Feign.Builder中。

Feign.Builder 默认使用整合了Hystrix的HystrixFeign.Builder

在fegin方法中先配置基础的默认的Logger、Decoder、Encoder、Contract之后,在configureFeign()方法中根据FeignClientProperties propertiesFeignContext context 中的配置信息,配置Feign.Builder中的各个组件。

Logger.Level、Retryer、ErrorDecoder、Request.Options、List<RequestInterceptor>

先配置自定义的Configuration,然后是springboot配置文件中默认的客户端配置,最后时springboot配置文件中指定服务名称的客户端配置。依次覆盖。

重构url

如果@FeignClient注解没有执行url属性,重构url(http://eureka-provider-ribbon-feign-api-impl),通过loadBalance构造整合Ribbon的具备负载均衡的FeignClient。

loadBalance()

loadBalance()方法通过Targeter实现类HystrixTargeter.target获取FeignClient实例。

@Override
public Object getObject() throws Exception {
   return getTarget();
}

/**
 * @param <T> the target type of the Feign client
 * @return a {@link Feign} client created with the specified data and the context
 * information
 */
<T> T getTarget() {
   FeignContext context = this.applicationContext.getBean(FeignContext.class);
   Feign.Builder builder = feign(context);

   if (!StringUtils.hasText(this.url)) {
      if (!this.name.startsWith("http")) {
         this.url = "http://" + this.name;
      }
      else {
         this.url = this.name;
      }
      this.url += cleanPath();
      return (T) loadBalance(builder, context,
            new HardCodedTarget<>(this.type, this.name, this.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 load balancing because we have a url,
         // but ribbon is on the classpath, so unwrap
         client = ((LoadBalancerFeignClient) client).getDelegate();
      }
      if (client instanceof FeignBlockingLoadBalancerClient) {
         // not load balancing because we have a url,
         // but Spring Cloud LoadBalancer is on the classpath, so unwrap
         client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
      }
      builder.client(client);
   }
   Targeter targeter = get(context, Targeter.class);
   return (T) targeter.target(this, builder, context,
         new HardCodedTarget<>(this.type, this.name, url));
}