OpenFeign源码

233 阅读7分钟

如何使用 Fegin

@SpringBootApplication

@EnableFeignClients

public class WebApplication {



public static void main(String[] args) {

    SpringApplication.run(WebApplication.class, args);

}



@FeignClient("name")

static interface NameService {

    @RequestMapping("/")

    public String getName();

}

流程图

image.png

SpringCloud Fegin是如何工作的

Feign涉及了两个注解,一个是@EnableFeignClients,用来开启 Feign,另一个是@FeignClient,用来标记要用 Feign 来拦截的请求接口。

EnableFeignClients

@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.TYPE)

@Documented

@Import(FeignClientsRegistrar.class)

public @interface EnableFeignClients {

String[] value() default {};

//扫描路径(生效路径)

String[] basePackages() default {};

//默认包路径

Class<?>[] basePackageClasses() default {};

//全局配置

Class<?>[] defaultConfiguration() default {};

//控制生效指定client

Class<?>[] clients() default {};

}

FeignClient

@Target(ElementType.TYPE)

@Retention(RetentionPolicy.RUNTIME)

@Documented

public @interface FeignClient {

//feign名称(nacos调用名称/contextId为空时使用name)

@AliasFor("name")

String value() default "";



@Deprecated

String serviceId() default "";

//上下文容器隔离id

String contextId() default "";



@AliasFor("value")

String name() default "";



String qualifier() default "";

//请求完整路径,url值存在时, 不会负载均衡

String url() default "";

//当发生http 404错误时,如果该字段位true,会调用decoder进行解码,否则抛出FeignException

boolean decode404() default false;

//当前client的配置列表(基于contextId/name隔离)

Class<?>[] configuration() default {};

//容错回调

Class<?> fallback() default void.class;

//容错回调工厂

Class<?> fallbackFactory() default void.class;

//请求路径

String path() default "";



boolean primary() default true;

}

注册Clients以及配置

先关注对EnableFeignClients的处理,可以看出他使用了@Import(FeignClientsRegistrar.class)注解, FeignClientsRegistrar是一个注册器,主要实现了配置的注入和Client的注入

@Override

public void registerBeanDefinitions(AnnotationMetadata metadata,

      BeanDefinitionRegistry registry) {

   //注册全局配置

   registerDefaultConfiguration(metadata, registry);

   //注册Client配置以及FeignClientFactory(FeignClient对象生成)

   registerFeignClients(metadata, registry);

}





private void registerDefaultConfiguration(AnnotationMetadata metadata,

      BeanDefinitionRegistry registry) {

   //获取EnableFeignClients注解上的配置属性

   Map<String, Object> defaultAttrs = metadata

         .getAnnotationAttributes(EnableFeignClients.class.getName(), true);



   if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {

      String name;

      if (metadata.hasEnclosingClass()) {

         name = "default." + metadata.getEnclosingClassName();

      }

      else {

         name = "default." + metadata.getClassName();

      }

      //获取defaultConfiguration

      registerClientConfiguration(registry, name,

            defaultAttrs.get("defaultConfiguration"));

   }

}





private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,

      Object configuration) {

   //注册FeignClientSpecification

   BeanDefinitionBuilder builder = BeanDefinitionBuilder

         .genericBeanDefinition(FeignClientSpecification.class);

   //为name属性赋值

   builder.addConstructorArgValue(name);

   //为configurations属性赋值

   builder.addConstructorArgValue(configuration);

   //注册到BeanDefinition,后续由spring创建FeignClientSpecification

   registry.registerBeanDefinition(

         name + "." + FeignClientSpecification.class.getSimpleName(),

         builder.getBeanDefinition());

}

/**

1.获取FeignClient注解configuration配置,注册到FeignClientSpecification

2.为FeignClient设置FeignClientFactory

**/

public void registerFeignClients(AnnotationMetadata metadata,

      BeanDefinitionRegistry registry) {



   LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();

   //获取EnableFeignClients注解的clients配置

   Map<String, Object> attrs = metadata

         .getAnnotationAttributes(EnableFeignClients.class.getName());

   AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(

         FeignClient.class);

   final Class<?>[] clients = attrs == null ? null

         : (Class<?>[]) attrs.get("clients");

   //clients为空

   if (clients == null || clients.length == 0) {

      ClassPathScanningCandidateComponentProvider scanner = getScanner();

      scanner.setResourceLoader(this.resourceLoader);

      scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));

      Set<String> basePackages = getBasePackages(metadata);

      //扫描所有FeignClient的注解接口

      for (String basePackage : basePackages) {

         candidateComponents.addAll(scanner.findCandidateComponents(basePackage));

      }

   }

   else {

       //获取全局client

      for (Class<?> clazz : clients) {

         candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));

      }

   }



   for (BeanDefinition candidateComponent : candidateComponents) {

      if (candidateComponent instanceof AnnotatedBeanDefinition) {

         // verify annotated class is an interface

 AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;

         AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();

         Assert.isTrue(annotationMetadata.isInterface(),

               "@FeignClient can only be specified on an interface");

         //获取FeignClient注解配置集合

         Map<String, Object> attributes = annotationMetadata

               .getAnnotationAttributes(FeignClient.class.getCanonicalName());



         String name = getClientName(attributes);

         //获取configuration配置,并注入到FeignClientSpecification

         registerClientConfiguration(registry, name,

               attributes.get("configuration"));

         //Feign赋值到FeignClientFactory,由spring创建工厂

         registerFeignClient(registry, annotationMetadata, attributes);

      }

   }

}





private void registerFeignClient(BeanDefinitionRegistry registry,

      AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {

   String className = annotationMetadata.getClassName();

   BeanDefinitionBuilder definition = BeanDefinitionBuilder

         .genericBeanDefinition(FeignClientFactoryBean.class);

   validate(attributes);

   //获取FeignClient配置,为FeignClientFactoryBean属性赋值

   definition.addPropertyValue("url", getUrl(attributes));

   definition.addPropertyValue("path", getPath(attributes));

   String name = getName(attributes);

   definition.addPropertyValue("name", name);

   String contextId = getContextId(attributes);

   definition.addPropertyValue("contextId", contextId);

   definition.addPropertyValue("type", className);

   definition.addPropertyValue("decode404", attributes.get("decode404"));

   definition.addPropertyValue("fallback", attributes.get("fallback"));

   definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));

   definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);



   String alias = contextId + "FeignClient";

   AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();

   beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);



   // has a default, won't be null

 boolean primary = (Boolean) attributes.get("primary");



   beanDefinition.setPrimary(primary);



   String qualifier = getQualifier(attributes);

   if (StringUtils.hasText(qualifier)) {

      alias = qualifier;

   }



   BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,

         new String[] { alias });

   //FeignClientFactoryBean注册BeanDefinition

   BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);

}

FeignClientFactoryBean

实现了FactoryBean接口,FactoryBean的作用是spring在获取bean的时候,会调用getObject方法,bean的创建交由业务实现,FeignClient的builder创建就是在getObject方法中实现

public interface FactoryBean<T> {



 String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";

  

 @Nullable

   T getObject() throws Exception;



    @Nullable

    Class<?> getObjectType();

    

 default boolean isSingleton() {

      return true;

   }

}

FeignContext

  1. 继承NamedContextFactory
  2. NamedContextFactory类的作用是为每个FeignClient创建上下文容器AnnotationConfigApplicationContext,并把client里对应的configurations的bean注册到容器里,起到隔离的作用.获取对应配置的时候,优先取的是当前容器里的配置bean
  3. FeignAutoConfiguration会在启动的时候,将注册的FeignClient对应的FeignClientSpecification,赋值到FeignContext的configurations属性里
@Autowired(required = false)

private List<FeignClientSpecification> configurations = new ArrayList<>();



@Bean

public FeignContext feignContext() {

   FeignContext context = new FeignContext();

   context.setConfigurations(this.configurations);

   return context;

}
//获取 client对应的容器(name或者contextId)

protected AnnotationConfigApplicationContext getContext(String name) {

   if (!this.contexts.containsKey(name)) {

      synchronized (this.contexts) {

         if (!this.contexts.containsKey(name)) {

            this.contexts.put(name, createContext(name));

         }

      }

   }

   return this.contexts.get(name);

}



protected AnnotationConfigApplicationContext createContext(String name) {

   AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();

   //优先注册FeignClient里的configurations

   if (this.configurations.containsKey(name)) {

      for (Class<?> configuration : this.configurations.get(name)

            .getConfiguration()) {

         context.register(configuration);

      }

   }

   //再注册EnableFeignClients里的defaultConfigurations

   for (Map.Entry<String, C> entry : this.configurations.entrySet()) {

      if (entry.getKey().startsWith("default.")) {

         for (Class<?> configuration : entry.getValue().getConfiguration()) {

            context.register(configuration);

         }

      }

   }

   context.register(PropertyPlaceholderAutoConfiguration.class,

         this.defaultConfigType);

   context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(

         this.propertySourceName,

         Collections.<String, Object>singletonMap(this.propertyName, name)));

   if (this.parent != null) {

      // Uses Environment from parent as well as beans

 context.setParent(this.parent);

      // jdk11 issue

 // https://github.com/spring-cloud/spring-cloud-netflix/issues/3101

 context.setClassLoader(this.parent.getClassLoader());

   }

   context.setDisplayName(generateDisplayName(name));

   context.refresh();

   return context;

}

类图

FeignClient创建

FeignClient的创建是依赖FeignClientFactoryBean来创建,触发条件是注入FeignClient或者getBean

FeignClientFactoryBean

@Override

public Object getObject() throws Exception {

   return getTarget();

}



<T> T getTarget() {

   //获取FeignContext对象

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

   //通过FeignContext获取client配置,构建Builder

   Feign.Builder builder = feign(context);

   //判断client是否配置url,没有配置则走负载均衡

   if (!StringUtils.hasText(url)) {

      if (!name.startsWith("http")) {

         url = "http://" + name;

      }

      else {

         url = name;

      }

      url += cleanPath();

      return (T) loadBalance(builder, context,

            new HardCodedTarget<>(type, name, url));

   }

   if (StringUtils.hasText(url) && !url.startsWith("http")) {

      url = "http://" + 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<>(type, name, url));

}





protected Feign.Builder feign(FeignContext context) {

   //获取日志工厂

   FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);

   Logger logger = loggerFactory.create(type);



   //此处配置取的是FeignConetext的默认配置FeignClientsConfiguration

 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

   //获取configurations配置和proper

 configureFeign(context, builder);



   return builder;

}



protected void configureFeign(FeignContext context, Feign.Builder builder) {

   //获取feign.client开头的配置

   //feign.client.default.开头的配置是针对所有client的全局配置

   //feign.client.contextId.开头的是针对某个client的配置

   FeignClientProperties properties = applicationContext

         .getBean(FeignClientProperties.class);

   //是否获取inheritParentContext父容器上下文配置

   FeignClientConfigurer feignClientConfigurer = getOptional(context,

         FeignClientConfigurer.class);

   setInheritParentContext(feignClientConfigurer.inheritParentConfiguration());

   //properties配置不为空,并且可以获取父上下文的配置bean

   if (properties != null && inheritParentContext) {

      //feign.client.defaultToProperties:true时候

      //覆盖顺序properties.contextId->properties.default->configurations

      if (properties.isDefaultToProperties()) {

         //获取configurations配置

         configureUsingConfiguration(context, builder);

         //获取properties.default,配置重复覆盖前一项

         configureUsingProperties(

               properties.getConfig().get(properties.getDefaultConfig()),

               builder);

         //获取properties. contextId,配置重复覆盖前一项

         configureUsingProperties(properties.getConfig().get(contextId), builder);

      }

      else {

         //feign.client.defaultToProperties:false时候

         //覆盖顺序configurations->properties.contextId->properties.default->

         configureUsingProperties(

               properties.getConfig().get(properties.getDefaultConfig()),

               builder);

         configureUsingProperties(properties.getConfig().get(contextId), builder);

         configureUsingConfiguration(context, builder);

      }

   }

   else {

      //只获取configurations配置

      configureUsingConfiguration(context, builder);

   }

}

loadBalance负载均衡

protected <T> T loadBalance(Feign.Builder builder, FeignContext context,

      HardCodedTarget<T> target) {

   //获取负载均衡客户端,支持ribbon和loadBanlancer,默认选择ribbon

   //FeignRibbonClientAutoConfiguration支持ribbon负载均衡配置

   //FeignLoadBalancerAutoConfiguration支持loadBalancer负载均衡配置

   Client client = getOptional(context, Client.class);

   if (client != null) {

      builder.client(client);

      Targeter targeter = get(context, Targeter.class);

      //feign动态代理调用

      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?");

}
FeignRibbonClientAutoConfiguration

默认选择



@ConditionalOnClass({ ILoadBalancer.class, Feign.class })

@ConditionalOnProperty(value = "spring.cloud.loadbalancer.ribbon.enabled",

      matchIfMissing = true)

@Configuration(proxyBeanMethods = false)

@AutoConfigureBefore(FeignAutoConfiguration.class)

@EnableConfigurationProperties({ FeignHttpClientProperties.class })

 // Order is important here, last should be the default, first should be optional

 // see

 // https://github.com/spring-cloud/spring-cloud-netflix/issues/2086#issuecomment-316281653

//导入支持HttpClient OkHttpClient和HttpUrlConnection三种配置

@Import({ HttpClientFeignLoadBalancedConfiguration.class,

      OkHttpFeignLoadBalancedConfiguration.class,

      DefaultFeignLoadBalancedConfiguration.class })

public class FeignRibbonClientAutoConfiguration {



   @Bean

   @Primary

   @ConditionalOnMissingBean

   @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")

   public CachingSpringLoadBalancerFactory cachingLBClientFactory(

         SpringClientFactory factory) {

      return new CachingSpringLoadBalancerFactory(factory);

   }



   @Bean

   @Primary

   @ConditionalOnMissingBean

   @ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")

   public CachingSpringLoadBalancerFactory retryabeCachingLBClientFactory(

         SpringClientFactory factory, LoadBalancedRetryFactory retryFactory) {

      return new CachingSpringLoadBalancerFactory(factory, retryFactory);

   }



   @Bean

   @ConditionalOnMissingBean

   public Request.Options feignRequestOptions() {

      return LoadBalancerFeignClient.DEFAULT_OPTIONS;

   }



}
OkHttpClient

注入条件:

  1. 类路径有OkHttpClient.class
  2. 配置文件有feign.okhttp.enabled
@Configuration(proxyBeanMethods = false)

@ConditionalOnClass(OkHttpClient.class)

@ConditionalOnProperty("feign.okhttp.enabled")

@Import(OkHttpFeignConfiguration.class)

class OkHttpFeignLoadBalancedConfiguration {



   @Bean

   @ConditionalOnMissingBean(Client.class)

   public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,

         SpringClientFactory clientFactory, okhttp3.OkHttpClient okHttpClient) {

      OkHttpClient delegate = new OkHttpClient(okHttpClient);

      return new LoadBalancerFeignClient(delegate, cachingFactory, clientFactory);

   }



}
HttpClient

注入条件:

  1. 类路径有ApacheHttpClient.class
  2. feign.httpclient.enabled,没有配置默认生效
@Configuration(proxyBeanMethods = false)

@ConditionalOnClass(ApacheHttpClient.class)

@ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)

@Import(HttpClientFeignConfiguration.class)

class HttpClientFeignLoadBalancedConfiguration {



   @Bean

   @ConditionalOnMissingBean(Client.class)

   public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,

         SpringClientFactory clientFactory, HttpClient httpClient) {

      ApacheHttpClient delegate = new ApacheHttpClient(httpClient);

      return new LoadBalancerFeignClient(delegate, cachingFactory, clientFactory);

   }



}
HttpUrlConnection

注入条件:

以上两个client缺省默认

@Configuration(proxyBeanMethods = false)

class DefaultFeignLoadBalancedConfiguration {



   @Bean

   @ConditionalOnMissingBean

   public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,

         SpringClientFactory clientFactory) {

      return new LoadBalancerFeignClient(new Client.Default(null, null), cachingFactory,

            clientFactory);

   }



}
FeignLoadBalancerAutoConfiguration

注入条件

  1. 引入loadBalancer jar包
  2. @AutoConfigureAfter(FeignRibbonClientAutoConfiguration.class)由于此处加载的条件是在ribbon后面,所以默认不会生效,因为ribbon配置注入后,@ConditionalOnMissingBean(Client.class),导致loadBalancer里的client不会注入


@ConditionalOnClass(Feign.class)

@ConditionalOnBean(BlockingLoadBalancerClient.class)

@AutoConfigureBefore(FeignAutoConfiguration.class)

@AutoConfigureAfter(FeignRibbonClientAutoConfiguration.class)

@EnableConfigurationProperties(FeignHttpClientProperties.class)

@Configuration(proxyBeanMethods = false)

 // Order is important here, last should be the default, first should be optional

 // see

 // https://github.com/spring-cloud/spring-cloud-netflix/issues/2086#issuecomment-316281653

@Import({ HttpClientFeignLoadBalancerConfiguration.class,

      OkHttpFeignLoadBalancerConfiguration.class,

      DefaultFeignLoadBalancerConfiguration.class })

public class FeignLoadBalancerAutoConfiguration {



}

Nacos支持负载均衡

fegin动态代理为FeginClient注解的接口生成代理类,拦截每个方法,获取基于spring注解的参数,最终调用

LoadBalancerFeignClient.execute方法

@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);

      //根据Request封装参数到RibbonRequest

      FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(

            this.delegate, request, uriWithoutHost);

      //根据Options封装ribbon的配置

      IClientConfig requestConfig = getClientConfig(options, clientName);

      return lbClient(clientName)

            .executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();

   }

   catch (ClientException e) {

      IOException io = findIOException(e);

      if (io != null) {

         throw io;

      }

      throw new RuntimeException(e);

   }

}



private FeignLoadBalancer lbClient(String clientName) {

   return this.lbClientFactory.create(clientName);

}



IClientConfig getClientConfig(Request.Options options, String clientName) {

   IClientConfig requestConfig;

   //FeignClient没有配置Options时,获取ribbonClient的配置

   if (options == DEFAULT_OPTIONS) {

      requestConfig = this.clientFactory.getClientConfig(clientName);

   }

   else {

      //配置了Options,取FeignClient配置(连接和请求超时时间)

      requestConfig = new FeignOptionsClientConfig(options);

   }

   return requestConfig;

}

CachingSpringLoadBalancerFactory

此处由于RibbonNacosAutoConfiguration类注入了NacosRibbonClientConfiguration,并在NacosRibbonClientConfiguration中重新注入了ServerList,右NacosServerList实现从Nacos配置中心拉取服务地址,覆盖RibbonClientConfiguration中的ConfigurationBasedServerList

//创建FeignLoadBalancer

public FeignLoadBalancer create(String clientName) {

   FeignLoadBalancer client = this.cache.get(clientName);

   if (client != null) {

      return client;

   }

   //加载ribbon client对应配置,此处暂不深究

   //在RibbonClientConfiguration中注入

   IClientConfig config = this.factory.getClientConfig(clientName);

   //获取上下文的ZoneAwareLoadBalancer

   ILoadBalancer lb = this.factory.getLoadBalancer(clientName);

   ServerIntrospector serverIntrospector = this.factory.getInstance(clientName,

         ServerIntrospector.class);

   client = this.loadBalancedRetryFactory != null

         ? new RetryableFeignLoadBalancer(lb, config, serverIntrospector,

               this.loadBalancedRetryFactory)

         : new FeignLoadBalancer(lb, config, serverIntrospector);

   this.cache.put(clientName, client);

   return client;

}

FeignLoadBalancer

public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {

    LoadBalancerCommand<T> command = buildLoadBalancerCommand(request, requestConfig);



    try {

        //此处根据前面的Nacos获取server的负载均衡方式,获取一条ip地址和端口

        return command.submit(

            new ServerOperation<T>() {

                @Override

                public Observable<T> call(Server server) {

                    URI finalUri = reconstructURIWithServer(server, request.getUri());

                    S requestForServer = (S) request.replaceUri(finalUri);

                    try {

                        //回调,获取server成功后,再调用FeignLoadBalancer.execute方法,调用原生Okhtt

                        return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));

                    } 

                    catch (Exception e) {

                        return Observable.error(e);

                    }

                }

            })

            .toBlocking()

            .single();

    } catch (Exception e) {

        Throwable t = e.getCause();

        if (t instanceof ClientException) {

            throw (ClientException) t;

        } else {

            throw new ClientException(e);

        }

    }

    

}


@Override

public RibbonResponse execute(RibbonRequest request, IClientConfig configOverride)

      throws IOException {

   Request.Options options;

   //此处获取的是getClientConfig方法里设置的超时时间设置

   if (configOverride != null) {

      RibbonProperties override = RibbonProperties.from(configOverride);

      options = new Request.Options(override.connectTimeout(this.connectTimeout),

            override.readTimeout(this.readTimeout));

   }

   else {

      options = new Request.Options(this.connectTimeout, this.readTimeout);

   }

   Response response = request.client().execute(request.toRequest(), options);

   return new RibbonResponse(request.getUri(), response);

}