Spring Cloud 源码分析之OpenFeign

OpenFeign是一个远程客户端请求代理,它的基本作用是让开发者能够以面向接口的方式来实现远程调用,从而屏蔽底层通信的复杂性,它的具体原理如下图所示。

在今天的内容中,我们需要详细分析OpenFeign它的工作原理及源码,我们继续回到这段代码。

@Slf4j
@RestController
@RequestMapping("/order")
public class OrderController {

    @Autowired
    IGoodsServiceFeignClient goodsServiceFeignClient;
    @Autowired
    IPromotionServiceFeignClient promotionServiceFeignClient;
    @Autowired
    IOrderServiceFeignClient orderServiceFeignClient;

    /**
     * 下单
     */
    @GetMapping
    public String order(){
        String goodsInfo=goodsServiceFeignClient.getGoodsById();
        String promotionInfo=promotionServiceFeignClient.getPromotionById();
        String result=orderServiceFeignClient.createOrder(goodsInfo,promotionInfo);
        return result;
    }
}

从这段代码中,先引出对于OpenFeign功能实现的思考。

  1. 声明@FeignClient注解的接口,如何被解析和注入的?
  2. 通过@Autowired依赖注入,到底是注入一个什么样的实例
  3. 基于FeignClient声明的接口被解析后,如何存储?
  4. 在发起方法调用时,整体的工作流程是什么样的?
  5. OpenFeign是如何集成Ribbon做负载均衡解析?

带着这些疑问,开始去逐项分析OpenFeign的核心源码

OpenFeign注解扫描与解析#

思考, 一个被声明了@FeignClient注解的接口,使用@Autowired进行依赖注入,而最终这个接口能够正常被注入实例。

从这个结果来看,可以得到两个结论

  1. @FeignClient声明的接口,在Spring容器启动时,会被解析。
  2. 由于被Spring容器加载的是接口,而接口又没有实现类,因此Spring容器解析时,会生成一个动态代理类。

EnableFeignClient#

@FeignClient注解是在什么时候被解析的呢?基于我们之前所有积累的知识,无非就以下这几种

  • ImportSelector,批量导入bean
  • ImportBeanDefinitionRegistrar,导入bean声明并进行注册
  • BeanFactoryPostProcessor , 一个bean被装载的前后处理器

在这几个选项中,似乎ImportBeanDefinitionRegistrar更合适,因为第一个是批量导入一个bean的string集合,不适合做动态Bean的声明。 而BeanFactoryPostProcessor 是一个Bean初始化之前和之后被调用的处理器。

而在我们的FeignClient声明中,并没有Spring相关的注解,所以自然也不会被Spring容器加载和触发。

那么@FeignClient是在哪里被声明扫描的呢?

在集成FeignClient时,我们在SpringBoot的main方法中,声明了一个注解@EnableFeignClients(basePackages = "com.gupaoedu.ms.api")。这个注解需要填写一个指定的包名。

嗯,看到这里,基本上就能猜测出,这个注解必然和@FeignClient注解的解析有莫大的关系。

下面这段代码是@EnableFeignClients注解的声明,果然看到了一个很熟悉的面孔FeignClientsRegistrar

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
}

FeignClientsRegistrar#

FeignClientRegistrar,主要功能就是针对声明@FeignClient注解的接口进行扫描和注入到IOC容器。

class FeignClientsRegistrar
      implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
    
}

果然,这个类实现了ImportBeanDefinitionRegistrar接口

public interface ImportBeanDefinitionRegistrar {
    default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
        this.registerBeanDefinitions(importingClassMetadata, registry);
    }

    default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    }
}

这个接口有两个重载的方法,用来实现Bean的声明和注册。

简单给大家演示一下ImportBeanDefinitionRegistrar的作用。

gpmall-portal这个项目的com.gupaoedu目录下,分别创建

  1. HelloService.java
  2. GpImportBeanDefinitionRegistrar.java
  3. EnableGpRegistrar.java
  4. TestMain
  1. 定义一个需要被装载到IOC容器中的类HelloService
public class HelloService {
}
  1. 定义一个Registrar的实现,定义一个bean,装载到IOC容器
public class GpImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
                                        BeanDefinitionRegistry registry) {
        BeanDefinition beanDefinition=new GenericBeanDefinition();
        beanDefinition.setBeanClassName(HelloService.class.getName());
        registry.registerBeanDefinition("helloService",beanDefinition);
    }
}
  1. 定义一个注解类
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(GpImportBeanDefinitionRegistrar.class)
public @interface EnableGpRegistrar {
}
  1. 写一个测试类
@Configuration
@EnableGpRegistrar
public class TestMain {

    public static void main(String[] args) {
        ApplicationContext applicationContext=new AnnotationConfigApplicationContext(TestMain.class);
        System.out.println(applicationContext.getBean(HelloService.class));

    }
}
  1. 通过结果演示可以发现,HelloService这个bean 已经装载到了IOC容器。

这就是动态装载的功能实现,它相比于@Configuration配置注入,会多了很多的灵活性。 ok,再回到FeignClient的解析中来。

FeignClientsRegistrar.registerBeanDefinitions#

  • registerDefaultConfiguration 方法内部从 SpringBoot 启动类上检查是否有 @EnableFeignClients, 有该注解的话, 则完成Feign框架相关的一些配置内容注册。
  • registerFeignClients 方法内部从 classpath 中, 扫描获得 @FeignClient 修饰的类, 将类的内容解析为 BeanDefinition , 最终通过调用 Spring 框架中的 BeanDefinitionReaderUtils.resgisterBeanDefinition 将解析处理过的 FeignClient BeanDeifinition 添加到 spring 容器中
//BeanDefinitionReaderUtils.resgisterBeanDefinition 
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
                                    BeanDefinitionRegistry registry) {
    //注册@EnableFeignClients中定义defaultConfiguration属性下的类,包装成FeignClientSpecification,注册到Spring容器。
    //在@FeignClient中有一个属性:configuration,这个属性是表示各个FeignClient自定义的配置类,后面也会通过调用registerClientConfiguration方法来注册成FeignClientSpecification到容器。
//所以,这里可以完全理解在@EnableFeignClients中配置的是做为兜底的配置,在各个@FeignClient配置的就是自定义的情况。
    registerDefaultConfiguration(metadata, registry);
    registerFeignClients(metadata, registry);
}

这里面需要重点分析的就是registerFeignClients方法,这个方法主要是扫描类路径下所有的@FeignClient注解,然后进行动态Bean的注入。它最终会调用registerFeignClient方法。

public void registerFeignClients(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
    registerFeignClient(registry, annotationMetadata, attr
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值