Spring Cloud - Openfeign 实现原理分析

OpenFeign简介

OpenFeign 是一个声明式 RESTful 网络请求客户端。OpenFeign 会根据带有注解的函数信息构建出网络请求的模板,在发送网络请求之前,OpenFeign 会将函数的参数值设置到这些请求模板中。虽然 OpenFeign 只能支持基于文本的网络请求,但是它可以极大简化网络请求的实现,方便编程人员快速构建自己的网络请求应用。

核心组件与概念

在阅读源码时,可以沿着两条线路进行,一是被@FeignClient注解修饰的接口类如何创建,也就是其 Bean 实例是如何被创建的;二是调用这些接口类的网络请求相关函数时,OpenFeign 是如何发送网络请求的。而 OpenFeign 相关的类也可以以此来进行分类,一部分是用来初始化相应的 Bean 实例的,一部分是用来在调用方法时发送网络请求。

动态注册BeanDefinition

1. FeignClientsRegistrar

@EnableFeignClients 有三个作用,一是引入FeignClientsRegistrar;二是指定扫描FeignClient的包信息,就是指定FeignClient接口类所在的包名;三是指定FeignClient接口类的自定义配置类。@EnableFeignClients注解的定义如下所示:

public @interface EnableFeignClients {
    // 下面三个函数都是为了指定需要扫描的包
   String[] value() default {};
   String[] basePackages() default {};
   Class<?>[] basePackageClasses() default {};
    // 指定自定义feign client的自定义配置,可以配置 Decoder、Encoder和Contract等组件
    // FeignClientsConfiguration 是默认的配置类
   Class<?>[] defaultConfiguration() default {};
   // 指定被@FeignClient修饰的类,如果不为空,那么路径自动检测机制会被关闭
   Class<?>[] clients() default {};
}
// org.springframework.cloud.openfeign.FeignClientsRegistrar#registerBeanDefinitions
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
      BeanDefinitionRegistry registry) {
   // 从 EnableFeignClients 的属性值来构建 Feign 的自定义 Configuration 进行注册
   registerDefaultConfiguration(metadata, registry);
   // 扫描 package , 注册被 @FeignClient 修饰的接口类的Bean信息
   registerFeignClients(metadata, registry);
}

private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,
      Object configuration) {
   // 使用 BeanDefinitionBuilder 来生成 BeanDefinition, 并注册到 registry 上
   BeanDefinitionBuilder builder = BeanDefinitionBuilder
         .genericBeanDefinition(FeignClientSpecification.class);
   builder.addConstructorArgValue(name);
   builder.addConstructorArgValue(configuration);
   registry.registerBeanDefinition(
         name + "." + FeignClientSpecification.class.getSimpleName(),
         builder.getBeanDefinition());
}

FeignClientSpecification 类实现了 NamedContextFactory.Specification 接口,它是 OpenFeign 组件实例化的重要一环,它持有自定义配置类提供的组件实例,供 OpenFeign 使用。SpringCloud 框架使用 NamedContextFactory 创建一系列的运行上下文,来让对应的 Specification 在这些上下文中创建实例对象。

// org.springframework.cloud.openfeign.FeignAutoConfiguration
@Autowired(required = false)
private List<FeignClientSpecification> configurations = new ArrayList<>();
 
@Bean
public FeignContext feignContext() {
    // 创建 FeignContext 实例, 并将 FeignClientSpecification 注入
   FeignContext context = new FeignContext();
   context.setConfigurations(this.configurations);
   return context;
}
// org.springframework.cloud.openfeign.FeignContext#FeignContext
public FeignContext() {
    // 将默认的 FeignClientsConfiguration 作为参数传递给构造函数
   super(FeignClientsConfiguration.class, "feign", "feign.client.name");
}

NamedContextFactory 是 FeignContext 的父类, 其 createContext 方法会创建具有名称为 Spring 的AnnotationConfigApplicationContext 实例作为当前上下文的子上下文。这些 AnnotationConfigApplicationContext 实例可以管理 OpenFeign 组件的不同实例。

NamedContextFactory 的实现代码如下:

// org.springframework.cloud.context.named.NamedContextFactory#createContext
protected AnnotationConfigApplicationContext createContext(String name) {
   AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
   // 获取该 name 所对应的 configuration ,如果有的话,就注册到子 context 中
   if (this.configurations.containsKey(name)) {
      for (Class<?> configuration : this.configurations.get(name).getConfiguration()) {
         context.register(configuration);
      }
   }
   // 注册 default 的 Configuration, 也就是 FeignClientsRegistrar 类的 registerDefaultConfiguration 方法中注册的Configuration
   for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
      if (entry.getKey().startsWith("default.")) {
         for (Class<?> configuration : entry.getValue().getConfiguration()) {
            context.register(configuration);
         }
      }
   }
   // 注册 PropertyPlaceholderAutoConfiguration 和 FeignClientsConfiguration 配置类
   context.register(PropertyPlaceholderAutoConfiguration.class, this.defaultConfigType);
   // 设置子 context 的 Environment 的 propertySource 属性源
   context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(
         this.propertySourceName,
         Collections.<String, Object>singletonMap(this.propertyName, name)));
   // 所有 context 的 parent 都相同,这样的话,一些相同的Bean可以通过 parent context 来获取
   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;
}

2. 扫描类信息

FeignClientsRegistrar 做的第二件事情是扫描指定包下的类文件,注册 @FeignClient 注解修饰的接口类信息。

// org.springframework.cloud.openfeign.FeignClientsRegistrar#registerFeignClients
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());
   // 依照 Annotation 来进行 TypeFilter ,只会扫描出被 FeignClient 修饰的类
   AnnotationTypeFilter annotationTypeFilter = n

相关推荐

  1. Springcloud OpenFeign实现(二)

    2024-03-27 23:20:03       55 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-03-27 23:20:03       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-03-27 23:20:03       100 阅读
  3. 在Django里面运行非项目文件

    2024-03-27 23:20:03       82 阅读
  4. Python语言-面向对象

    2024-03-27 23:20:03       91 阅读

热门阅读

  1. 如何用OBD创建OceanBase集群

    2024-03-27 23:20:03       31 阅读
  2. docker的安装和镜像的拉取

    2024-03-27 23:20:03       40 阅读
  3. XGB-24:使用Scikit-Learn估计器接口

    2024-03-27 23:20:03       43 阅读
  4. 使用指纹的锁屏解锁流程

    2024-03-27 23:20:03       41 阅读
  5. ChatGPT编码技巧:探索人工智能写代码的奥秘

    2024-03-27 23:20:03       40 阅读
  6. 在 Redis 中,`EVAL` 命令用于执行一段 Lua 脚本

    2024-03-27 23:20:03       36 阅读
  7. 数学分析复习:实数项级数的收敛

    2024-03-27 23:20:03       33 阅读
  8. 查看windwos系统信息

    2024-03-27 23:20:03       43 阅读
  9. 关于C/C++头文件引起的编译问题

    2024-03-27 23:20:03       41 阅读
  10. 美易官方:盘前道指期货涨0.5%,游戏驿站跌逾15%

    2024-03-27 23:20:03       35 阅读