Spring Cloud - Openfeign 实现原理分析

本文详细分析了Spring Cloud OpenFeign的实现原理,包括动态注册BeanDefinition、实例初始化以及函数调用和网络请求的过程。OpenFeign通过@FeignClient注解创建Bean实例,扫描接口类信息,并利用反射生成代理接口类,实现在调用方法时发送网络请求。
摘要由CSDN通过智能技术生成

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
  • 28
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

梦睡了

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值