【深入理解SpringCloud微服务】Spring-Cloud-OpenFeign源码解析(上)

embedded/2024/10/18 14:21:00/

【深入理解SpringCloud微服务】Spring-Cloud-OpenFeign源码解析(上)

  • OpenFeign简单介绍
    • Feign与OpenFeign
    • OpenFeign使用示例
  • OpenFeign原理解析
  • OpenFeign源码解析
    • @EnableFeignClients
    • FeignClientsRegistrar
    • FeignClientFactoryBean
    • FeignInvocationHandler
    • LoadBalancerFeignClient

OpenFeign简单介绍

Feign与OpenFeign

Feign对Ribbon和各种http客户端工具类(如OKHttp、HttpClient、HttpURLConnection等)进行了封装,使得开发者无需再手动调用RestTemplate发起http请求,而是以方法调用的方式向远处服务发起请求。

在这里插入图片描述

当使用Feign之后,开发者要做的就是在接口以及接口方法上使用Feign的注解修饰,Feign就会扫描并给这些接口生成一个代理对象,当我们调用接口的方法时,实际上就是调用代理对象,底层就会通过Ribbon进行负载均衡,然后使用http客户端发起http请求。

在这里插入图片描述

而OpenFeign就是在Feign的基础上复用了SpringMVC的注解,更加的方便开发者使用,降低学习门槛。

在这里插入图片描述

OpenFeign使用示例

一共四步:

在这里插入图片描述

  1. 引入依赖
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
  1. 在启动类上添加@EnableFeignClients注解以启用Feign客户端功能
@SpringBootApplication
@EnableFeignClients
public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);}
}
  1. 定义Feign接口,用于声明要调用的服务方法
@FeignClient(name = "service-provider")
public interface ServiceClient {@GetMapping("/api/users/{id}")User getUser(@PathVariable("id") Long id);@PostMapping("/api/users")User createUser(@RequestBody User user);
}
  1. 在需要使用服务调用的地方注入并使用这个Feign接口
@Service
public class UserService {private final ServiceClient serviceClient;@Autowiredpublic UserService(ServiceClient serviceClient) {this.serviceClient = serviceClient;}public User getUserById(Long id) {return serviceClient.getUser(id);}public User createUser(User user) {return serviceClient.createUser(user);}
}

在这个示例中:

  • @FeignClient 注解用来定义一个Feign客户端,它会代理指定服务的所有接口。
  • 接口中的方法通过HTTP动词和路径来表示对远程服务API的调用。
  • 方法参数可以通过Spring MVC的注解进行映射,如 @PathVariable 和 @RequestBody。

这样,您就可以像调用本地方法一样调用远程服务的方法,而OpenFeign会在后台帮我们处理好所有HTTP请求相关的细节。

OpenFeign原理解析

  1. 使用scanner包扫描器扫描@FeignClient修饰的接口,生成BeanDefinition
  2. 修改BeanDefinition的beanClass属性为FeignClientFactoryBean
  3. FeignClientFactoryBean实现了FactoryBean,getObject()方法进行动态代理生成代理对象
  4. 代理对象内部会调用LoadBalancerFeignClient通过Ribbon负载均衡器执行Feign请求并获取响应

在这里插入图片描述

OpenFeign源码解析

@EnableFeignClients

...
// 导入FeignClientsRegistrar,FeignClientsRegistrar进行包扫描并注册BeanDefinition
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {/*** basePackages()属性的别名*/String[] value() default {};/*** 指定的包扫描路径*/String[] basePackages() default {};...}

@EnableFeignClients内部会通过@Import导入FeignClientsRegistrar,FeignClientsRegistrar会进行包扫描并注册BeanDefinition。

在这里插入图片描述

FeignClientsRegistrar

class FeignClientsRegistrarimplements ImportBeanDefinitionRegistrar, ... {...@Overridepublic void registerBeanDefinitions(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {...}
}

FeignClientsRegistrar实现了Spring的ImportBeanDefinitionRegistrar接口。当我们通过@Import导入ImportBeanDefinitionRegistrar类型的bean时,Spring会调用ImportBeanDefinitionRegistrar的registerBeanDefinitions()方法。OpenFeign在registerBeanDefinitions()方法进行了包扫描并注册Feign客户端。

在这里插入图片描述

	@Overridepublic void registerBeanDefinitions(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {...// 进行包扫描并注册Feign客户端registerFeignClients(metadata, registry);}...public void registerFeignClients(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {// 使用Spring提供的包扫描器ClassPathScanningCandidateComponentProvider进行包扫描ClassPathScanningCandidateComponentProvider scanner = getScanner();...for (String basePackage : basePackages) {// 进行包扫描,扫描@FeignClient注解修饰的接口// 返回扫描加载到的BeanDefinitionSet<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);for (BeanDefinition candidateComponent : candidateComponents) {if (candidateComponent instanceof AnnotatedBeanDefinition) {...// 注册到Spring容器registerFeignClient(registry, annotationMetadata, attributes);}}}}

OpenFeign在registerBeanDefinitions()中,使用了Spring提供的包扫描器ClassPathScanningCandidateComponentProvider进行包扫描,扫描@FeignClient注解修饰的接口,生成BeanDefinition并返回。

在这里插入图片描述

	private void registerFeignClient(BeanDefinitionRegistry registry,AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {String className = annotationMetadata.getClassName();// 设置BeanDefinition的beanClass属性为FeignClientFactoryBean类型BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);...AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();...BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,new String[] { alias });// 注册到Spring容器BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);}

获取到包扫描返回的BeanDefinition之后,设置BeanDefinition的beanClass为FeignClientFactoryBean,最后注册到Spring容器中。

在这里插入图片描述

FeignClientFactoryBean

class FeignClientFactoryBeanimplements FactoryBean<Object>, ... {@Overridepublic Object getObject() throws Exception {// 动态代理生成代理对象 ...}}

FeignClientFactoryBean实现了Spring的FactoryBean接口,如果一个bean实现了FactoryBean接口,Spring会在实例化bean的时候,调用getObject()方法进行实例化,而不会进行常规的推断构造进行反射实例化。而FactoryBean#getObject()方法配合动态代理是常用的套路,OpenFeign也是如此。

在这里插入图片描述

  @Overridepublic <T> T newInstance(Target<T> target) {...// JDK动态代理的InvocationHandler,类型是FeignInvocationHandlerInvocationHandler handler = factory.create(target, methodToHandler);// 使用JDK动态代理返回代理对象T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),new Class<?>[] {target.type()}, handler);...return proxy;}

FeignClientFactoryBean的getObject()方法最终会调用到ReflectiveFeign#newInstance()方法,里面通过JDK动态代理生成代理对象并返回,代理对象对应的InvocationHandler是FeignInvocationHandler。

在这里插入图片描述

FeignInvocationHandler

生成的代理对象就会被Spring处理@Autowired注解时注入到对应接口类型的属性当中。当我们调用该接口的方法时,实际上调用的就是代理对象,然后就会被FeignInvocationHandler拦截处理。

FeignInvocationHandler#invoke():

    @Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {...return dispatch.get(method).invoke(args);}

dispatch.get(method).invoke(args)调用进入到SynchronousMethodHandler#invoke():

  @Overridepublic Object invoke(Object[] argv) throws Throwable {...while (true) {try {return executeAndDecode(template, options);} catch (...) {...}}}Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {...try {response = client.execute(request, options);} catch (IOException e) {...} finally {...}}

client.execute(request, options)调用就会进入到LoadBalancerFeignClient的execute()方法,因此FeignInvocationHandler就是调用LoadBalancerFeignClient进行负载均衡以及发送http请求等处理的。

在这里插入图片描述

LoadBalancerFeignClient

LoadBalancerFeignClient里面的处理使用到了RxJava,稍微有点复杂,如果不想看的话,只需要知道LoadBalancerFeignClient其实就是干了以下三件事:

  1. 负载均衡:通过Ribbon的ILoadBalancer的chooseServer()方法选出一个实例
  2. 重写url:根据选出的实例重构URI
  3. 发起http请求:默认使用HttpURLConnection,返回Response对象

在这里插入图片描述

如果还打算继续研究LoadBalancerFeignClient里面的细节,请看下一篇文章,做好烧脑的准备。

下面附上本次源码解析的流程图:

在这里插入图片描述


http://www.ppmy.cn/embedded/97682.html

相关文章

一:《Python基础语法汇总》— 数据类型与输入输出

1.认识Python&#xff1a; ​ Python是人与计算机交流的语言&#xff0c;编程语言的一种&#xff0c;是面向对象语言 ​ 程序设计原则&#xff1a;KISS原则 – 简洁胜于复杂 2.Python的优势&#xff1a; ​ 语法简洁明了&#xff0c;实现代码短&#xff0c;模块众多 3.Pyt…

JavaScript初级——对象和函数

一、对象的简介 1、JS中的数据类型 —— String 字符串 —— Number 数值 —— Boolean 布尔值 —— Null 空值 —— Undefined 未定义 ——以上五种类型属于基本数据类型&#xff0c;以后我们看到的值只要不是上面这五种&#xff0c;则为对象 —— Object 对象 2…

【Python学习-UI界面】PyQt5 小部件5-QCheckBox

样式如下: 当将QCheckBox对象添加到父窗口时&#xff0c;文本标签之前会出现一个矩形框。 和QRadioButton一样&#xff0c;它也是一个可选择的按钮。 它通常用于用户被要求选择一个或多个可用选项的场景。 不同于单选按钮&#xff0c;复选框默认情况下不是互斥的。 为了限制…

eNSP 华为ACL配置

华为ACL配置 需求&#xff1a; 公司保证财务部数据安全&#xff0c;禁止研发部门和互联网访问财务服务器&#xff0c;但总裁办不受影响 R1&#xff1a; <Huawei>sys [Huawei]sys Router1 [Router1]undo info-center enable [Router1]int g1/0/0 [Router1-GigabitEth…

Nginx--防盗链问题

前言&#xff1a;本博客仅作记录学习使用&#xff0c;部分图片出自网络&#xff0c;如有侵犯您的权益&#xff0c;请联系删除 一、什么是盗链 盗链是一种网络行为&#xff0c;指的是一个网站未经授权&#xff0c;直接使用另一个网站资源&#xff08;如图片、视频、音乐、文件等…

EmguCV学习笔记 VB.Net 第1章 EmguCV

版权声明&#xff1a;本文为博主原创文章&#xff0c;转载请在显著位置标明本文出处以及作者网名&#xff0c;未经作者允许不得用于商业目的。 EmguCV学习笔记目录 Vb.net EmguCV学习笔记目录 C# 笔者的博客网址&#xff1a;VB.Net-CSDN博客 教程相关说明以及如何获得pdf教程…

vulnhub系列:Hackademic.RTB1

vulnhub系列&#xff1a;Hackademic.RTB1 靶机下载 一、信息收集 nmap 扫描存活&#xff0c;根据 mac 地址寻找 IP nmap 192.168.23.0/24nmap 扫描端口&#xff0c;开放端口&#xff1a;22、80 nmap 192.168.23.143 -p- -Pn -sV -O访问80端口&#xff0c;页面发现 target …

【热门文章】Eureka原理实践

以下是关于“Eureka 原理实践”的一些可能的步骤和要点&#xff1a; 一、Eureka 原理概述 Eureka 是 Netflix 开发的服务发现框架&#xff0c;其核心原理包括服务注册、服务发现和心跳机制。 服务注册&#xff1a;服务提供者在启动时将自身的信息&#xff08;如服务名称、IP 地…