【云原生&微服务十三】SpringCloud之深度源码剖析OpenFeign如何扫描所有的FeignClient

网友投稿 257 2022-08-23

【云原生&微服务十三】SpringCloud之深度源码剖析OpenFeign如何扫描所有的FeignClient

文章目录

​​一、前言​​​​二、@FeignClient解析​​

​​1、@FeignClient注解解释​​​​2、@FeignClient注解作用​​

​​1)使用@RibbonClient自定义负载均衡策略​​

​​三、@EnableFeignClients解析​​

​​1、FeignClientsRegistrar类​​​​2、注册默认配置​​​​3、注册所有的FeignClient流程图​​​​4、注册所有的FeignClient​​

​​1)获取包扫描路径​​​​2)扫描所有的FeignClient​​​​3)注册FeignClient​​

​​四、后续文章​​

一、前言

在前面的文章:

我们聊了以下内容:

本文基于OpenFeign低版本(​​SpringCloud 2020.0.x版本之前​​)讨论:@FeignClient注解在哪里被扫描?

PS:本文基于的SpringCloud版本

2.3.7.RELEASE Hoxton.SR9 2.2.6.RELEASE org.springframework.boot spring-boot-dependencies ${spring-boot.version} pom import org.springframework.cloud spring-cloud-dependencies ${spring-cloud.version} pom import com.alibaba.cloud spring-cloud-alibaba-dependencies ${spring-cloud-alibaba.version} pom import

后续分析完Feign的低版本实现,博主会再出一版OpenFeign新版本的系列文章。

我们知道OpenFeign有两个注解:​​@EnableFeignClients​​​ 和 ​​@FeignClient​​,其中:

@EnableFeignClients,用来开启OpenFeign;@FeignClient,标记要用OpenFeign来拦截的请求接口;

为什么Service-B服务中定义了一个ServiceAClient接口(继承自ServiceA的API接口),某Controller 或Service中通过@Autowried注入一个ServiceAClient接口的实例,就可以通过OpenFeign做负载均衡去调用ServiceA服务?

先看@FeignClient注解

二、@FeignClient解析

1、@FeignClient注解解释

@FeignClient注解中定义了一些方法,如下:

1> value()和name()互为别名

表示微服务名;

2> serviceId()

已经废弃了,直接使用name即可;

3> contextId()

存在多个相同名称FeignClient时,可以使用contextId做唯一约束。

4> qualifier()

对应Spring的​​@Qualifier​​注解,在定义@FeignClient时,指定qualifier;在@Autowired注入FeignClient时,使用​​@Qualifier​​注解;

// FeignClient定义@FeignClient(name = "SERVICE-A", contextId = "9999", qualifier = "serviceAClient1")public interface ServiceAClient extends ServiceA {}// FeignClient注入@Autowired@Qualifier("serviceAClient1")private ServiceAClient serviceAClient;

5> url()

用于配置指定服务的地址 / IP,相当于直接请求这个服务,不经过Ribbon的负载均衡。

6> decode404()

当调用请求发生404错误时,如果decode404的值为true,会执行decoder解码用404代替抛出FeignException异常,否则直接抛出异常。

7> configuration()

OpenFeign的配置类,在配置类中可以自定义Feign的Encoder、Decoder、LogLevel、Contract等。

8> fallback()

定义容错的处理类(回退逻辑),fallback类必须实现FeignClient的接口。

9> fallbackFactory()

也是容错的处理,但是可以知道熔断的异常信息。

10> path()

path定义当前FeignClient访问接口时的统一前缀,比如接口地址是/user/get, 如果你定义了前缀是user, 那么具体方法上的路径就只需要写/get 即可。

2、@FeignClient注解作用

用@FeignClient注解标注一个接口后,OpenFeign会对这个接口创建一个对应的动态代理 --> REST client(发送restful请求的客户端),然后可以将这个REST client注入其他的组件(比如ServiceBController);如果启用了ribbon,就会采用负载均衡的方式,来进行在SpringBoot扫描不到的目录下新建一个配置类:

@Configurationpublic class MyConfiguration { @Bean public IRule getRule() { return new MyRule(); } @Bean public IPing getPing() { return new MyPing(); }}

<2> 在SpringBoot可以扫描到的目录下新建一个配置类(被@RibbonClient注解标注):

由于@FeignClient中填的name() / value()是​​SERVICE-A​​​,所以@RibbonClient的value() 也必须是​​SERVICE-A​​​,表示针对调用服务​​SERVICE-A​​时做负载均衡。

@Cinfiguration@RibbonClient(name = "SERVICE-A", configuration = MyConfiguration.class)public class ServiceAConfiguration {}

三、@EnableFeignClients解析

我们知道​​@EnableFeignClients​​注解用于开启OpenFeign,可以大胆猜测,@EnableFeignClients注解 会触发OpenFeign的核心机制:去扫描所有包下面的@FeignClient注解的接口、生成@FeignClient标注接口的动态代理类。

下面我们就基于这两个猜测解析@EnableFeignClients。

​​@EnableFeignClients​​​注解中通过​​@Import​​​导入了一个​​FeignClientsRegistrar​​类,FeignClientsRegistrar负责FeignClient的注册(即:扫描指定包下的@FeignClient注解标注的接口、生成FeignClient动态代理类、触发后面的其他流程)。

1、FeignClientsRegistrar类

由于​​FeignClientsRegistrar​​​实现自​​ImportBeanDefinitionRegistrar​​​,结合我们在​​SpringBoot启动流程中开启OpenFeign的入口(ImportBeanDefinitionRegistrar详解)​​​一文对OpenFeign入口的分析,得知,在SpringBoot启动过程中会进入到​​FeignClientsRegistrar#registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry)​​方法;

​​registerBeanDefinitions()​​方法是feign的核心入口方法,其中会做两件事:注册默认的配置、注册所有的FeignClient。下面我们分开来看;

2、注册默认配置

​​registerDefaultConfiguration()​​方法负责注册OpenFeign的默认配置。具体的代码执行流程如下:

方法流程解析:

注册默认配置流程很简单清晰,复杂的在于注册所有的FeignClient,下面我就继续来看。

3、注册所有的FeignClient流程图

4、注册所有的FeignClient

registerFeignClients()方法负责注册所有的FeignClient;

方法逻辑解析:

首先获取@EnableFeignClients注解的所有属性,主要为了拿到扫描包路径(basePackages);因为一般不会在@EnableFeignClients注解中配置clients属性,所以会进入到clients属性为空时的逻辑;然后通过​​getScanner()​​方法获取扫描器:ClassPathScanningCandidateComponentProvider,并将上下文AnnotationConfigServletWebServerApplicationContext作为扫描器的ResourceLoader;接着给扫描器​​ClassPathScanningCandidateComponentProvider​​​添加一个注解过滤器(​​AnnotationTypeFilter​​),只过滤出包含@FeignClient注解的BeanDefinition;再通过​​getBasePackages(metadata)​​​方法获取​​@EnableFeingClients​​注解中的指定的包扫描路径 或 扫描类;如果没有获取到,则默认扫描启动类所在的包路径;然后进入到核心逻辑:通过​​scanner.findCandidateComponents(basePackage)​​方法从包路径下扫描出所有标注了@FeignClient注解并符合条件装配的接口;最后将FeignClientConfiguration 在BeanDefinitionRegistry中注册一下,再对FeignClient做真正的注册操作。

下面,我们细看一下如何获取包扫描路径?如何扫描到FeignClient?如何注册FeignClient?

1)获取包扫描路径

​​FeignClientsRegistrar#getBasePackages(metadata)​​方法负责获取包路径;

方法执行逻辑解析:

首先获取@EnableFeignClients注解中的全部属性;如果指定了​​basePackages​​,则采用basePackages指定的目录作为包扫描路径;如果指定了一些​​basePackageClasses​​,则采用basePackageClasses指定的类们所在的目录 作为包扫描路径;如果既没有指定​​basePackages​​,也没有指定basePackageClasses,则采用启动类所在的目录作为包扫描路径。默认是这种情况。

2)扫描所有的FeignClient

​​ClassPathScanningCandidateComponentProvider#findCandidateComponents(String basePackage)​​方法负责扫描出指定目录下的所有标注了@FeignClient注解的Class类(包括interface、正常的Class)。

具体代码执行流程如下:

方法逻辑解析:

首先扫描出指定路径下的所有Class文件;接着遍历每个Class文件,使用Scanner中的@FeignClient过滤器过滤出所有被@FeignClient注解标注的Class;最后将过滤出的所有Class返回。

细看一下​​isCandidateComponent(MetadataReader metadataReader)​​方法:

其中会遍历Scanner中的所有excludeFilters和includeFilters对当前Class做过滤操作,就此处,仅有一个includeFilter,用来过滤出标注了@FeignClient注解的Class,具体的过滤逻辑如下:

到这里,FeignClient的扫描也就结束了;

3)注册FeignClient

扫描到所有的FeignClient之后,需要将其注入到Spring中,​​FeignClientsRegistrar#registerFeignClient()​​方法负责这个操作;

注册FeignClient实际就是构建一个FeignClient对应的BeanDefinition,然后将FeignClient的一些属性配置设置为BeanDefinition的property,最后将BeanDefinition注册到Spring的临时容器。在处理FeignClient的属性配置时,如果@FeignClient中配置了qualifier,则使用qualifier作为beanName。

到这里已经完成了包的扫描、FeignClient的解析、FeignClient数据以BeanDefinition的形式存储到spring框架中的BeanDefinitionRegistry中。

下面需要去创建实现标注了@FeignClient注解的ServiceAClient接口的动态代理,将动态代理作为一个bean,注入给调用方(ServiceBControler);这个我们放在下一篇文章聊,敬请期待。

四、后续文章

OpenFeign如何生成FeignClient的动态代理类?OpenFeign如何负载均衡?

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:【云原生&微服务>SCG网关篇六】Spring Cloud Gateway内置的18种Filter使用姿势
下一篇:声音品牌化──差异化营销新路径!(打造品牌声量)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~