收款定制开发【云原生&微服务十四】SpringCloud之深度源码剖析OpenFeign如何扫描所有的FeignClient

文章目录

一、前言

收款定制开发在前面的文章:

收款定制开发我们聊了以下内容:

  1. OpenFeign的概述、收款定制开发为什么会使用Feign代替Ribbon?
  2. Feign和OpenFeign的区别?
  3. 详细的OpenFeign收款定制开发实现声明式客户端负载均衡案例
  4. OpenFeign中拦截器RequestInterceptor的使用
  5. OpenFeign收款定制开发的一些常用配置(超时、数据压缩、日志输出)
  6. SpringCloud之OpenFeign收款定制开发的核心组件(Encoder、Decoder、Contract)
  7. 在SpringBoot收款定制开发启动流程中开启OpenFeign的入口

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

PS:收款定制开发本文基于的SpringCloud版本

 <properties>    <spring-boot.version>2.3.7.RELEASE</spring-boot.version>    <spring-cloud.version>Hoxton.SR9</spring-cloud.version>    <spring-cloud-alibaba.version>2.2.6.RELEASE</spring-cloud-alibaba.version></properties><dependencyManagement>    <dependencies>        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-dependencies</artifactId>            <version>${spring-boot.version}</version>            <type>pom</type>            <scope>import</scope>        </dependency>        <!--整合spring cloud-->        <dependency>            <groupId>org.springframework.cloud</groupId>            <artifactId>spring-cloud-dependencies</artifactId>            <version>${spring-cloud.version}</version>            <type>pom</type>            <scope>import</scope>        </dependency>        <!--整合spring cloud alibaba-->        <dependency>            <groupId>com.alibaba.cloud</groupId>            <artifactId>spring-cloud-alibaba-dependencies</artifactId>            <version>${spring-cloud-alibaba.version}</version>            <type>pom</type>            <scope>import</scope>        </dependency>    </dependencies></dependencyManagement>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

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

我们知道OpenFeign收款定制开发有两个注解:@EnableFeignClients@FeignClient,其中:

  1. @EnableFeignClients,用来开启OpenFeign;
  2. @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;
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

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,就会采用负载均衡的方式,来进行http请求的发送。

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

可以用@RibbonClient标注一个配置类,在@RibbonClient注解的configuration属性中可以指定配置类,自定义自己的ribbon的ILoadBalancer;@RibbonClient的名称,要跟@FeignClient的名称一样。

<1> 在SpringBoot扫描不到的目录下新建一个配置类:

@Configurationpublic class MyConfiguration {    @Bean    public IRule getRule() {        return new MyRule();    }    @Bean    public IPing getPing() {        return new MyPing();    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

<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 {}
  • 1
  • 2
  • 3
  • 4
  • 5

三、@EnableFeignClients解析

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

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

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

1、FeignClientsRegistrar类

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

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

2、注册默认配置

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

方法流程解析:

  1. 首先获取@EnableFeignClients注解的全部属性;
  2. 如果属性不为空,并且属性中包含defaultConfiguration,则默认字符串default. 和 启动类全路径名拼接到一起;
  3. 然后再拼接上.FeignClientSpecification,作为beanName,构建出一个BeanDefinition,将其注册到BeeanDefinictionRegistry中。

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

3、注册所有的FeignClient流程图

4、注册所有的FeignClient

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


方法逻辑解析:

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

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

1)获取包扫描路径

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

方法执行逻辑解析:

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

2)扫描所有的FeignClient

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

具体代码执行流程如下:

方法逻辑解析:

  1. 首先扫描出指定路径下的所有Class文件;
  2. 接着遍历每个Class文件,使用Scanner中的@FeignClient过滤器过滤出所有被@FeignClient注解标注的Class;
  3. 最后将过滤出的所有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如何负载均衡?

网站建设定制开发 软件系统开发定制 定制软件开发 软件开发定制 定制app开发 app开发定制 app开发定制公司 电商商城定制开发 定制小程序开发 定制开发小程序 客户管理系统开发定制 定制网站 定制开发 crm开发定制 开发公司 小程序开发定制 定制软件 收款定制开发 企业网站定制开发 定制化开发 android系统定制开发 定制小程序开发费用 定制设计 专注app软件定制开发 软件开发定制定制 知名网站建设定制 软件定制开发供应商 应用系统定制开发 软件系统定制开发 企业管理系统定制开发 系统定制开发