openfeign源码

2020.0.X版本开始的OpenFeign底层不再使用Ribbon了

FeignClient的bean注册过程

1.1 @FeignClientsRegistrar开启对FeignClient的扫描

此处主流程上无区别;

在SpringBoot启动流程中@FeignClientsRegistrar注解开启OpenFeign的入口、OpenFeign扫描所有的FeignClient的流程 高版本和低版本基本一样,低版本的见文章:OpenFeign源码2-Bean注册过程和调用过程

主要流程如下:

  1. 开启扫描FeignClient的入口

在启动类上添加注解 @EnableFeignClients来开启Feign 客户端,@EnableFeignClients注解会通过@Import注解导入配置类FeignClientsRegistrar,在SpringBoot启动流程中注入到启动类的ConfigurationClass的属性中,在注册启动类的BeanDefinition时,会遍历调用其@Import的所有ImportBeanDefinitionRegistrar接口的 registerBeanDefinitions()方法;

java 复制代码
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
}

FeignClientsRegistrar实现了ImportBeanDefinitionRegistrar,它是一个动态注入bean的接口,Spring Boot启动的时候,会去调用这个类中的registerBeanDefinitions来实现动态Bean的装载。它的作用类似于ImportSelector。

  1. 扫描FeignClient
    拿到@EnableFeignClients注解中配置的扫描包路径相关的属性,得到要扫描的包路径; 获取到扫描器ClassPathScanningCandidateComponentProvider,然后给其添加一个注解过滤器(AnnotationTypeFilter),只过滤出包含@FeignClient注解的BeanDefinition; 扫描器的findCandidateComponents(basePackage)方法从包路径下扫描出所有标注了@FeignClient注解并符合条件装配的接口;然后将其在BeanDefinitionRegistry中注册一下;

详细如下: FeignClientsRegistrar实现了ImportBeanDefinitionRegistrar,它是一个动态注入bean的接口,Spring Boot启动的时候,会去调用这个类中的registerBeanDefinitions来实现动态Bean的装载。它的作用类似于ImportSelector。

然后就会进入 FeignClientsRegistrar# registerBeanDefinitionsregisterDefaultConfiguration 方法内部从 SpringBoot 启动类上检查是否有@EnableFeignClients, 有该注解的话, 则完成 Feign 框架相关的一些配置内容注册registerFeignClients 方法内部从 classpath 中, 扫描获得 @FeignClient修饰的类, 将类的内容解析为 BeanDefinition , 最终通过调用 Spring 框架中的BeanDefinitionReaderUtils.resgisterBeanDefinition 将解析处理过的 FeignClientBeanDeifinition 添加到 spring 容器中.

java 复制代码
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
      BeanDefinitionRegistry registry) {
   // todo 将@EnableFeignClients注解中的defaultConfiguration属性注册到一个缓存map
   registerDefaultConfiguration(metadata, registry);
   // todo 1) 扫描所有@FeignClient注解的接口,即扫描到所有Feign接口
   // 2) 将每个@FeignClient注解的configuration属性注册到一个缓存map
   // 3) 根据@FeignClient注解元数据生成 FeignClientFactoryBean 的BeanDefinition,
   //    并将这个BeanDefinition注册到一个缓存map
   registerFeignClients(metadata, registry);
}
  • registerDefaultConfiguration:

    java 复制代码
    private void registerDefaultConfiguration(AnnotationMetadata metadata,
          BeanDefinitionRegistry registry) {
       // 获取到@EnableFeignClients注解的属性值
       // 第二个参数true,表示将Class类型的属性变为了String类型
       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();
          }
          // todo 注册这个defaultConfiguration属性
          registerClientConfiguration(registry, name,
                defaultAttrs.get("defaultConfiguration"));
       }
    }
    private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object configuration) {
       // 生成一个Builder
       BeanDefinitionBuilder builder = BeanDefinitionBuilder
             .genericBeanDefinition(FeignClientSpecification.class);
       builder.addConstructorArgValue(name);
       builder.addConstructorArgValue(configuration);
       registry.registerBeanDefinition(
             name + "." + FeignClientSpecification.class.getSimpleName(),
             builder.getBeanDefinition()); // 获取到构建的BeanDefinition
    }
  • registerFeignClients

    java 复制代码
    public void registerFeignClients(AnnotationMetadata metadata,
          BeanDefinitionRegistry registry) {
       // 定义一个扫描器
       ClassPathScanningCandidateComponentProvider scanner = getScanner();
       // 初始化扫描器
       scanner.setResourceLoader(this.resourceLoader);
    
       Set<String> basePackages;
    
       // 获取@EnableFeignClients注解的属性元数据
       Map<String, Object> attrs = metadata
             .getAnnotationAttributes(EnableFeignClients.class.getName());
       AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
             FeignClient.class);
       // 获取clients属性值
       final Class<?>[] clients = attrs == null ? null
             : (Class<?>[]) attrs.get("clients");
       // 若clients属性为空,则启用类路径扫描
       if (clients == null || clients.length == 0) {
          // 为扫描器添加一个扫描@FeignClient注解的Filter
          scanner.addIncludeFilter(annotationTypeFilter);
          // todo 获取@EnableFeignClients注解中所有指定的基本包
          basePackages = getBasePackages(metadata);
       }
       else {
          // 指定了clients属性,将所有指定的Feign接口类添加到集合
          final Set<String> clientClasses = new HashSet<>();
          basePackages = new HashSet<>();
          for (Class<?> clazz : clients) {
             basePackages.add(ClassUtils.getPackageName(clazz));
             clientClasses.add(clazz.getCanonicalName());
          }
          AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() {
             @Override
             protected boolean match(ClassMetadata metadata) {
                String cleaned = metadata.getClassName().replaceAll("$", ".");
                return clientClasses.contains(cleaned);
             }
          };
          scanner.addIncludeFilter(
                new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
       }
    
       // 遍历这些基本包,将其它的@FeignClient接口找到并写入到集合
       for (String basePackage : basePackages) {
          Set<BeanDefinition> candidateComponents = scanner
                .findCandidateComponents(basePackage);
          // 遍历 候选组件 集合(所有Feign接口都在这个集合中)
          for (BeanDefinition candidateComponent : candidateComponents) {
             // 只处理Feign接口组件
             if (candidateComponent instanceof AnnotatedBeanDefinition) {
                // verify annotated class is an interface
                AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
                // 从BeanDefinition中获取注解元数据
                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());
                // todo 获取Feign名称
                String name = getClientName(attributes);
                // 将当前遍历的Feign接口的configuration注册到缓存map
                registerClientConfiguration(registry, name,
                      attributes.get("configuration"));
                // 将FeignClientFactoryBean的BeanDefinition注册到缓存map
                registerFeignClient(registry, annotationMetadata, attributes);
             }
          }
       }
    }

    registerFeignClient 在这个方法中,就是去组装BeanDefinition,也就是Bean的定义,然后注册到Spring IOC容器。

    java 复制代码
    private void registerFeignClient(BeanDefinitionRegistry registry,
          AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
       // 获取FeignClient接口的类全路径
       String className = annotationMetadata.getClassName();
       // 生成一个beanFactory,其会为FeignClientFactoryBean生成一些必要的组件
       BeanDefinitionBuilder definition = BeanDefinitionBuilder
             .genericBeanDefinition(FeignClientFactoryBean.class);
       validate(attributes);
       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";
       // 获取到FeignClientFactoryBean的beanDefinition
       AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
    
       boolean primary = (Boolean) attributes.get("primary"); // has a default, won't be null
    
       beanDefinition.setPrimary(primary);
    
       String qualifier = getQualifier(attributes);
       if (StringUtils.hasText(qualifier)) {
          alias = qualifier;
       }
    
       // 生成beanDefinition的holder,通过holder可以获取到这个beanDefinition
       BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
             new String[] { alias });
       // 把BeanDefinition的这个bean定义注册到IOC容器
       BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
    }

    我们关注一下,BeanDefinitionBuilder是用来构建一个BeanDefinition的,它是通过 genericBeanDefinition 来构建的,并且传入了一个FeignClientFactoryBean的类,代码如下。

    java 复制代码
    public static BeanDefinitionBuilder genericBeanDefinition(Class<?> beanClass) {
       BeanDefinitionBuilder builder = new BeanDefinitionBuilder(new GenericBeanDefinition());
       builder.beanDefinition.setBeanClass(beanClass);
       return builder;
    }

综上代码分析,其实实现逻辑很简单。

  1. 创建一个BeanDefinitionBuilder

  2. 创建一个工厂Bean,并把从@FeignClient注解中解析的属性设置到这个FactoryBean

  3. 调用registerBeanDefinition注册到IOC容器中

1.2 为FeignClient生成动态代理类

区别主要体现在这里;

在注册FeignClient到Spring容器时,构建的BeanDefinition的beanClas是FeignClientFactoryBeanFeignClientFactoryBean是一个工厂,保存了@FeignClient注解的所有属性值,在Spring容器初始化的过程中,其会根据之前扫描出的FeignClient信息构建FeignClient的动态代理类。详见OpenFeign源码2-Bean注册过程和调用过程

底层通信Client的区别?

在使用Feign.Builder构建FeignClient的时候,获取到的Client是FeignBlockingLoadBalancerClient(这其中的逻辑后面聊,在OpenFeign低版本是LoadBalancerFeignClient);用于生成FeignClientTargeterDefaultTargeter(在OpenFeign低版本是HystrixTargeter,高版本移除了Hystrix,采用Spring Cloud Circuit Breaker 做限流熔断);

具体体现在FeignClientFactoryBean#loadBalance()方法,其是一个进行负载均衡的FeignClient动态代理生成方法;

OpenFeign低版本:

FeignBlockingLoadBalancerClient何时注入到Spring容器?

FeignBlockingLoadBalancerClient注入到Spring容器的方式和OpenFeign低版本的LoadBalancerFeignClient是一样的;

进入到FeignBlockingLoadBalancerClient类中,看哪里调用了它唯一一个构造函数;

找到FeignBlockingLoadBalancerClient发现有三个地方调用了它的构造函数,new了一个实例;

  • DefaultFeignLoadBalancedConfiguration
  • HttpClientFeignLoadBalancedConfiguration
  • HttpClient5FeignLoadBalancerConfiguration
  • OkHttpFeignLoadBalancedConfiguration

再结合默认的配置,只有DefaultFeignLoadBalancedConfiguration中的Client符合条件装配;

可以通过引入Apache HttpClient的maven依赖使用HttpClientFeignLoadBalancedConfiguration

或引入OkHttpClient的maven依赖并在application.yml文件中指定feign.okhttp.enabled属性为true使用OkHttpFeignLoadBalancedConfiguration

DefaultTargeter在哪里注入到Spring容器?

DefaultTargeter注入到Spring容器的方式和OpenFeign低版本的HystrixTargeter是一样的;

FeignAutoConfiguration类中可以找到Targeter注入到Spring容器的逻辑;

后续生成动态代理类的逻辑和旧版本一样

都体现在ReflectiveFeign#newInstance()方法中:

2. Client处理负载均衡(核心区别)

上面提到OpenFeign高版本获取到的ClientFeignBlockingLoadBalancerClient,而低版本的是LoadBalancerFeignClientLoadBalancerFeignClient基于Ribbon实现负载均衡FeignBlockingLoadBalancerClient就靠OpenFeign自己实现负载均衡

OpenFeign如何处理一个HTTP请求见博文:

接下来浅看一下FeignBlockingLoadBalancerClient是如何做负载均衡的!!

2.1 FeignBlockingLoadBalancerClient选择一个服务实例

參考:

相关推荐
DT辰白2 小时前
如何解决基于 Redis 的网关鉴权导致的 RESTful API 拦截问题?
后端·微服务·架构
老猿讲编程4 小时前
技术发展历程:从 CORBA 到微服务
微服务·云原生·架构
time_silence9 小时前
微服务——服务通信与接口设计
微服务
Java程序之猿1 天前
微服务分布式(一、项目初始化)
分布式·微服务·架构
Yvemil71 天前
《开启微服务之旅:Spring Boot Web开发举例》(一)
前端·spring boot·微服务
Yvemil71 天前
《开启微服务之旅:Spring Boot Web开发》(二)
前端·spring boot·微服务
维李设论1 天前
Node.js的Web服务在Nacos中的实践
前端·spring cloud·微服务·eureka·nacos·node.js·express
jwolf21 天前
基于K8S的微服务:一、服务发现,负载均衡测试(附calico网络问题解决)
微服务·kubernetes·服务发现
Yvemil71 天前
《开启微服务之旅:Spring Boot Web开发举例》(二)
前端·spring boot·微服务