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选择一个服务实例

參考:

相关推荐
蝎子莱莱爱打怪6 天前
XZLL-IM干货系列 04|Netty 长连接实战:Pipeline 怎么排、心跳怎么跳、连接怎么管
后端·微服务·面试
SamDeepThinking7 天前
Java微服务练习方式
java·后端·微服务
米丘10 天前
微前端之 Web Components 完全指南
微服务·html
霸道流氓气质13 天前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
霸道流氓气质13 天前
Spring Boot 微服务性能优化完全指南
spring boot·微服务·性能优化
地瓜伯伯13 天前
从MESI缓存一致性协议讲透synchronized的底层
java·spring boot·spring·spring cloud·微服务·springcloud
Devin~Y13 天前
大厂 Java 面试实录:从音视频内容社区到 AI RAG 的全链路技术设计
java·spring boot·redis·spring cloud·微服务·kafka·音视频
递归尽头是星辰13 天前
AI 访问数据仓库:从直连到微服务化
数据仓库·人工智能·微服务·dataagent·ai数据治理
就改了14 天前
Windows 环境 SkyWalking 完整实操教程
windows·微服务·skywalking
至乐活着14 天前
Docker Compose多服务编排实战:从零搭建Node.js+MySQL+Redis全栈应用
docker·微服务·devops·容器编排·compose