要启用FeignClient首先必须在启动类上加上注解@EnableFeignClients,EnableFeignClients代码如下
查看文本复制打印?

  1. @Retention(RetentionPolicy.RUNTIME)
  2. @Target(ElementType.TYPE)
  3. @Documented
  4. @Import(FeignClientsRegistrar.class)
  5. public @interface EnableFeignClients {
  6. ….

注意到注解@Import(FeignClientsRegistrar.class),FeignClientsRegistrar实现了ImportBeanDefinitionRegistrar,在启动时会执行registerBeanDefinitions动态注册
查看文本复制打印?

  1. class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar {
  2. @Override
  3. public void registerBeanDefinitions(AnnotationMetadata metadata,
  4. BeanDefinitionRegistry registry) {
  5. registerDefaultConfiguration(metadata, registry);
  6. registerFeignClients(metadata, registry);
  7. }
  8. }
  • registerDefaultConfiguration

我们首先看registerDefaultConfiguration,代码不多,直接贴代码
查看文本复制打印?

  1. private void registerDefaultConfiguration(AnnotationMetadata metadata,
  2. BeanDefinitionRegistry registry) {
  3. Map defaultAttrs = metadata
  4. .getAnnotationAttributes(EnableFeignClients.class.getName(), true);
  5. if (defaultAttrs != null && defaultAttrs.containsKey(“defaultConfiguration”)) {
  6. String name;
  7. if (metadata.hasEnclosingClass()) {
  8. name = “default.” + metadata.getEnclosingClassName();
  9. }
  10. else {
  11. name = “default.” + metadata.getClassName();
  12. }
  13. registerClientConfiguration(registry, name,
  14. defaultAttrs.get(“defaultConfiguration”));
  15. }
  16. }
  • 获取配置信息defaultAttrs
  • 注册默认配置类信息,配置类从defaultConfiguration中获取并且名称为 “default.” + metadata.getClassName() 比如启动类为TestApplication,那么名称为default.com.test.TestApplication

registerClientConfiguration将配置信息注册为FeignClientSpecification查看文本复制打印?

  1. private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,
  2. Object configuration) {
  3. BeanDefinitionBuilder builder = BeanDefinitionBuilder
  4. .genericBeanDefinition(FeignClientSpecification.class);
  5. builder.addConstructorArgValue(name);
  6. builder.addConstructorArgValue(configuration);
  7. registry.registerBeanDefinition(
  8. name + “.” + FeignClientSpecification.class.getSimpleName(),
  9. builder.getBeanDefinition());
  10. }

FeignClientSpecification其实就是一个key-value结构体,key就是配置名称,value就是配置类查看文本复制打印?

  1. FeignClientSpecification(String name, Class<?>[] configuration) {
  2. this.name = name;
  3. this.configuration = configuration;
  4. }

在EnableFeignClients注解中参数defaultConfiguration为全局配置类,如果FeignClient没有配置则会获取defaultConfiguration的配置,defaultConfiguration可以配置为任意类,比如
查看文本复制打印?

  1. @EnableFeignClients(defaultConfiguration = GlobalFeignClientConfiguration.class)

GlobalFeignClientConfiguration该如何指定配置呢?我们知道可以通过feign.Builder来手动创建FeignClient,在feign.Builder中有以下变量
查看文本复制打印?

  1. public static class Builder {
  2. private Logger.Level logLevel = Logger.Level.NONE;
  3. private Contract contract = new Contract.Default();
  4. private Client client = new Client.Default(null, null);
  5. private Retryer retryer = new Retryer.Default();
  6. private Logger logger = new NoOpLogger();
  7. private Encoder encoder = new Encoder.Default();
  8. private Decoder decoder = new Decoder.Default();
  9. private QueryMapEncoder queryMapEncoder = new FieldQueryMapEncoder();
  10. private ErrorDecoder errorDecoder = new ErrorDecoder.Default();
  11. private Options options = new Options();
  12. }

这些都可以通过defaultConfiguration重新定义,比如下面这个指定了Loger.Level
查看文本复制打印?

  1. public class GlobalFeignClientConfiguration {
  2. @Bean
  3. public Level level() {
  4. return Level.FULL;
  5. }
  6. }

当然也可以通过配置文件进行配置
查看文本复制打印?

  1. feign:
  2. client:
  3. config:
  4. feignName:
  5. connectTimeout: 5000
  6. readTimeout: 5000
  7. loggerLevel: full
  8. errorDecoder: com.example.SimpleErrorDecoder
  9. retryer: com.example.SimpleRetryer
  10. requestInterceptors:
    • com.example.FooRequestInterceptor
    • com.example.BarRequestInterceptor
  11. decode404: false
  12. encoder: com.example.SimpleEncoder
  13. decoder: com.example.SimpleDecoder
  14. contract: com.example.SimpleContract
  • registerFeignClients

registerFeignClients先扫描所有带注解FeignClient的类,并注册到Spring容器中
查看文本复制打印?

  1. public void registerFeignClients(AnnotationMetadata metadata,
  2. BeanDefinitionRegistry registry) {
  3. LinkedHashSet candidateComponents = new LinkedHashSet<>();
  4. Map attrs = metadata
  5. .getAnnotationAttributes(EnableFeignClients.class.getName());
  6. AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
  7. FeignClient.class);
  8. //…扫描FeignClient类(部分代码省略)
  9. for (BeanDefinition candidateComponent : candidateComponents) {
  10. if (candidateComponent instanceof AnnotatedBeanDefinition) {
  11. //… 注册Feign客户端(部分代码省略)
  12. Map attributes = annotationMetadata
  13. .getAnnotationAttributes(FeignClient.class.getCanonicalName());
  14. String name = getClientName(attributes);
  15. registerClientConfiguration(registry, name,
  16. attributes.get(“configuration”));
  17. registerFeignClient(registry, annotationMetadata, attributes);
  18. }
  19. }
  20. }

会将Client注册为FeignClientFactoryBean,这样Spring Boot在获取Feign实例时就会调用FeignClientFactoryBean.getTarget方法
查看文本复制打印?

  1. private void registerFeignClient(BeanDefinitionRegistry registry,
  2. AnnotationMetadata annotationMetadata, Map attributes) {
  3. String className = annotationMetadata.getClassName();
  4. BeanDefinitionBuilder definition = BeanDefinitionBuilder
  5. .genericBeanDefinition(FeignClientFactoryBean.class);
  6. //…省略部分代码
  7. AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
  8. BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
  9. new String[] { alias });
  10. BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
  11. }

最终会通过targeter.target创建一个代理对象
查看文本复制打印?

  1. T getTarget() {
  2. FeignContext context = applicationContext.getBean(FeignContext.class);
  3. Feign.Builder builder = feign(context);
  4. //…省略部分代码
  5. Targeter targeter = get(context, Targeter.class);
  6. return (T) targeter.target(this, builder, context,
  7. new HardCodedTarget<>(type, name, url));
  8. }

创建代理对象的代码位于feign.Feign.target中,至此整个Feign从初始化到最终实例化就全部完成

Nacos FeignClient调用原理

如果使用Nacos作为注册中心,只要在依赖中添加nacos的starter并且在配置文件中指定nacos的地址就接入完成
查看文本复制打印?

  1. com.alibaba.cloud
  2. spring-cloud-starter-alibaba-nacos-discovery
  3. 2.1.1.RELEASE

接入nacos后,Feign调用将从nacos配置中心获取获取服务信息,比较重要的就是服务的地址和端口,那么这一切时如何实现的,我们就从spring-cloud-starter-alibaba-nacos-discovery开始,spring-cloud-starter-alibaba-nacos-discovery本身没有包含任何的代码(不知道为何这样设计),但依赖了spring-cloud-alibaba-nacos-discovery
查看文本复制打印?

  1. com.alibaba.cloud
  2. spring-cloud-alibaba-nacos-discovery
  3. org.springframework.boot
  4. spring-boot-starter

这里面包含starter相关的代码,spring.factories定义如下

  1. org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  2. com.alibaba.cloud.nacos.NacosDiscoveryAutoConfiguration,\
  3. com.alibaba.cloud.nacos.ribbon.RibbonNacosAutoConfiguration,\
  4. com.alibaba.cloud.nacos.endpoint.NacosDiscoveryEndpointAutoConfiguration,\
  5. com.alibaba.cloud.nacos.discovery.NacosDiscoveryClientAutoConfiguration,\
  6. com.alibaba.cloud.nacos.discovery.configclient.NacosConfigServerAutoConfiguration
  7. org.springframework.cloud.bootstrap.BootstrapConfiguration=\
  8. com.alibaba.cloud.nacos.discovery.configclient.NacosDiscoveryClientConfigServiceBootstrapConfiguration

我们这里关注NacosDiscoveryClientConfigServiceBootstrapConfiguration
查看文本复制打印?

  1. @ConditionalOnClass(ConfigServicePropertySourceLocator.class)
  2. @ConditionalOnProperty(value = “spring.cloud.config.discovery.enabled”, matchIfMissing = false)
  3. @Configuration
  4. @ImportAutoConfiguration({ NacosDiscoveryClientAutoConfiguration.class,
  5. NacosDiscoveryAutoConfiguration.class })
  6. public class NacosDiscoveryClientConfigServiceBootstrapConfiguration {
  7. }

这里使用了注解ImportAutoConfiguration,该注解会自动导入配置的类。 我们进入NacosDiscoveryClientAutoConfiguration
查看文本复制打印?

  1. @Configuration
  2. @ConditionalOnNacosDiscoveryEnabled
  3. @AutoConfigureBefore({ SimpleDiscoveryClientAutoConfiguration.class,
  4. CommonsClientAutoConfiguration.class })
  5. public class NacosDiscoveryClientAutoConfiguration {
  6. @Bean
  7. public DiscoveryClient nacosDiscoveryClient(
  8. NacosDiscoveryProperties discoveryProperties) {
  9. return new NacosDiscoveryClient(discoveryProperties);
  10. }
  11. //…省略部分代码
  12. }

这里会注入一个NacosDiscoveryClient对象,该对象实现接口DiscoveryClient。DiscoveryClient在spring cloud中负责从注册中心获取服务列表,也就是说底层Feign并不是直接和注册中心打交道,而是通过DiscoveryClient发现服务,那么只要实现了DiscoveryClient就能实现自定义注册中心。
NacosDiscoveryClient.java查看文本复制打印?

  1. public class NacosDiscoveryClient implements DiscoveryClient {
  2. private static final Logger log = LoggerFactory.getLogger(NacosDiscoveryClient.class);
  3. public static final String DESCRIPTION = “Spring Cloud Nacos Discovery Client”;
  4. private NacosDiscoveryProperties discoveryProperties;
  5. public NacosDiscoveryClient(NacosDiscoveryProperties discoveryProperties) {
  6. this.discoveryProperties = discoveryProperties;
  7. }
  8. @Override
  9. public String description() {
  10. return DESCRIPTION;
  11. }
  12. @Override
  13. public List getInstances(String serviceId) {
  14. try {
  15. String group = discoveryProperties.getGroup();
  16. List instances = discoveryProperties.namingServiceInstance()
  17. .selectInstances(serviceId, group, true);
  18. return hostToServiceInstanceList(instances, serviceId);
  19. }
  20. catch (Exception e) {
  21. throw new RuntimeException(
  22. “Can not get hosts from nacos server. serviceId: “ + serviceId, e);
  23. }
  24. }
  25. //…省略部分代码
  26. }

以获取服务实例getInstances为例,discoveryProperties.namingServiceInstance()获取了一个NamingService对象,这个对象包含nacos api所有的操作,有了这个对象就能和nacos通信。

http://zhengjianfeng.cn/?p=516