springcloud-openFeign简单梳理

news/2024/11/16 11:31:52/

OpenFeign

  openFeign是springcloud中,服务间进行调用的常用方式。了解它,可以更好的处理服务间调用问题。

@EnableFeignClients

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

  项目模块装配Feign相关。
重点关注。@Import。导入要给FeignClientsRegistrar类。

class FeignClientsRegistrarimplements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {

  FeignClientsRegistrar类中实现接口ImportBeanDefinitionRegistrar,通过实现方法registerBeanDefinitions。完成@FeignClient注解相关类的注入到ioc容器。

@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {registerDefaultConfiguration(metadata, registry);registerFeignClients(metadata, registry);
}

@FeignClient相关类的扫描,注入

  只记录常用的方式,其他可以看源码。这里只关注通过组件扫描注入。组件扫描用到类
ClassPathScanningCandidateComponentProvider,可以重写类的isCandidateComponent方法。完成相关组件的扫描,并封装为beanDefinition。

protected ClassPathScanningCandidateComponentProvider getScanner() {return new ClassPathScanningCandidateComponentProvider(false, this.environment) {@Overrideprotected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {boolean isCandidate = false;if (beanDefinition.getMetadata().isIndependent()) {if (!beanDefinition.getMetadata().isAnnotation()) {isCandidate = true;}}return isCandidate;}};}

  在openFeign中,只是扫描过滤了,非注解等相关类。通过设置IncludeFilter过滤,过滤类型是注解过滤FeignClient

ClassPathScanningCandidateComponentProvider scanner = getScanner();scanner.setResourceLoader(this.resourceLoader);scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));Set<String> basePackages = getBasePackages(metadata);for (String basePackage : basePackages) {candidateComponents.addAll(scanner.findCandidateComponents(basePackage));}

  扫描范围为,通过方法getBasePackages获取跟包。逻辑主要是,标注注解EnableFeignClients的属性value,basePackages,basePackageClasses配置的内容。如果未配置,默认为当前类所在的包。我的理解是标注注解EnableFeignClients的类的包。

protected Set<String> getBasePackages(AnnotationMetadata importingClassMetadata) {Map<String, Object> attributes = importingClassMetadata.getAnnotationAttributes(EnableFeignClients.class.getCanonicalName());Set<String> basePackages = new HashSet<>();for (String pkg : (String[]) attributes.get("value")) {if (StringUtils.hasText(pkg)) {basePackages.add(pkg);}}for (String pkg : (String[]) attributes.get("basePackages")) {if (StringUtils.hasText(pkg)) {basePackages.add(pkg);}}for (Class<?> clazz : (Class[]) attributes.get("basePackageClasses")) {basePackages.add(ClassUtils.getPackageName(clazz));}if (basePackages.isEmpty()) {basePackages.add(ClassUtils.getPackageName(importingClassMetadata.getClassName()));}return basePackages;}

  扫描并返回对应类的beanDefinition后,遍历。获取注解FeignClient上的属性信息,通过BeanDefinitionBuilder构建类型是FeignClientFactoryBean的beanDefinition。并注册到BeanDefinitionRegistry中。FeignClientFactoryBean是一个工厂bean,可以通过getObject获取到@FeignClient注解代理的Bean。

FeignClientFactoryBean

  上面介绍的,注解@FeignClient标注的类,都被封装为FeignClientFactoryBean类型的beadDefinition注册到IOC。接下来看看,通过FeignClientFactoryBean如何获取到注解@FeignClient的代理对象,完成方法的调用。

// 通过getObject获取client对象。
@Override
public Object getObject() {return getTarget();
}
<T> T getTarget() {FeignContext context = beanFactory != null? beanFactory.getBean(FeignContext.class): applicationContext.getBean(FeignContext.class);Feign.Builder builder = feign(context);// @FeignClient注解中url属性不存在的业务逻辑if (!StringUtils.hasText(url)) {if (LOG.isInfoEnabled()) {LOG.info("For '" + name+ "' URL not provided. Will try picking an instance via load-balancing.");}if (!name.startsWith("http")) {url = "http://" + name;}else {url = name;}url += cleanPath();return (T) loadBalance(builder, context,new HardCodedTarget<>(type, name, url));}// @FeignClient注解中url属性存在的业务逻辑if (StringUtils.hasText(url) && !url.startsWith("http")) {url = "http://" + url;}String url = this.url + cleanPath();Client client = getOptional(context, Client.class);if (client != null) {if (client instanceof LoadBalancerFeignClient) {// not load balancing because we have a url,// but ribbon is on the classpath, so unwrapclient = ((LoadBalancerFeignClient) client).getDelegate();}if (client instanceof FeignBlockingLoadBalancerClient) {// not load balancing because we have a url,// but Spring Cloud LoadBalancer is on the classpath, so unwrapclient = ((FeignBlockingLoadBalancerClient) client).getDelegate();}if (client instanceof RetryableFeignBlockingLoadBalancerClient) {// not load balancing because we have a url,// but Spring Cloud LoadBalancer is on the classpath, so unwrapclient = ((RetryableFeignBlockingLoadBalancerClient) client).getDelegate();}builder.client(client);}Targeter targeter = get(context, Targeter.class);return (T) targeter.target(this, builder, context,new HardCodedTarget<>(type, name, url));}

contexts,每一个@FeignClient标注的接口都有自己的应用上下文的理解(下面会用到)

   通过工厂类获取目标对象的时候,首先获取FeignContext对象。FeignContext是用于创建和管理Feign Client所依赖的各种类的工厂类
  每个Feign Client会关联一个AnnotationConfigApplicationContext实例,用于存取Feign Client所依赖的各种类的实例
  (1)configurations中有根据name配置到的配置类,注册到AnnotationConfigApplicationContext。name = contextId-->value-->name-->serviceId(优先级从前到后,哪个属性不为空) + "FeignClientSpecification";
  (2)default默认的配置类,注册到AnnotationConfigApplicationContext。默认的配置类是@EnableFeignClients注解中属性defaultConfiguration中配置的配置类
  (3)注册PropertyPlaceholderAutoConfiguration
  (4)设置parent为当前服务应用上下文
  (5)刷新应用上下文

protected AnnotationConfigApplicationContext createContext(String name) {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();if (this.configurations.containsKey(name)) {for (Class<?> configuration : this.configurations.get(name).getConfiguration()) {context.register(configuration);}}for (Map.Entry<String, C> entry : this.configurations.entrySet()) {if (entry.getKey().startsWith("default.")) {for (Class<?> configuration : entry.getValue().getConfiguration()) {context.register(configuration);}}}context.register(PropertyPlaceholderAutoConfiguration.class,this.defaultConfigType);context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(this.propertySourceName,Collections.<String, Object>singletonMap(this.propertyName, name)));if (this.parent != null) {// Uses Environment from parent as well as beanscontext.setParent(this.parent);// jdk11 issue// https://github.com/spring-cloud/spring-cloud-netflix/issues/3101context.setClassLoader(this.parent.getClassLoader());}context.setDisplayName(generateDisplayName(name));context.refresh();return context;}

  注册AnnotationConfigApplicationContext,首先是注册服务接口的配置类,其次是注册全局的配置类,最后是注册默认的配置类defaultConfigType。defaultConfigType是FeignContext类实例化默认传入的FeignClientsConfiguration.class。
FeignClientsConfiguration
  每个FeignClient注解标注的接口,对应的AnnotationConfigApplicationContext属性,allowEagerClassLoading = true。也就是说后加载的允许覆盖前面加载的类。
在这里插入图片描述

configurations

  configurations中保存了每个Feign Client所依赖的配置类,在创建AnnotationConfigApplicationContext的过程中,这些配置类会被注入到Bean工厂中。

Feign.Builder

  用于构建feign对象。

  1. 通过ContextId获取到对应的AnnotationConfigApplicationContext实例。获取Feign.Builder对象。
  2. Feign.Builder的build方法创建feign对象ReflectiveFeign
  3. ReflectiveFeign对象的newInstance方法返回的就是一个代理对象,代理对象的InvocationHandler实现类就是ReflectiveFeign对象的内部类FeignInvocationHandler。

@FeignClient中url属性值:存在

  // @FeignClient注解中url属性存在的业务逻辑if (StringUtils.hasText(url) && !url.startsWith("http")) {url = "http://" + url;}String url = this.url + cleanPath();Client client = getOptional(context, Client.class);if (client != null) {if (client instanceof LoadBalancerFeignClient) {// not load balancing because we have a url,// but ribbon is on the classpath, so unwrapclient = ((LoadBalancerFeignClient) client).getDelegate();}if (client instanceof FeignBlockingLoadBalancerClient) {// not load balancing because we have a url,// but Spring Cloud LoadBalancer is on the classpath, so unwrapclient = ((FeignBlockingLoadBalancerClient) client).getDelegate();}if (client instanceof RetryableFeignBlockingLoadBalancerClient) {// not load balancing because we have a url,// but Spring Cloud LoadBalancer is on the classpath, so unwrapclient = ((RetryableFeignBlockingLoadBalancerClient) client).getDelegate();}builder.client(client);}Targeter targeter = get(context, Targeter.class);return (T) targeter.target(this, builder, context,new HardCodedTarget<>(type, name, url));

拼接请求路径。

  通过url,path拼接路径。lg:http://… + /path;

获取client

  通过方法getOptional获取client。

Client client = getOptional(context, Client.class);
  1. 通过FeignContext的getInstance方法,获取到contextId对应的应用上下文applicationContext。
  2. 在这个应用上下文中获取到类型为Client.class的bean对象。并返回对象。
  3. 根据client所实现的接口类型,转换为对应的类型。
  4. 相同的方式,通过contextId获取类型为Targeter.class的bean对象。
  5. 调用Targeter的target方法,默认是调用Feign.build的build()方法创建ReflectiveFeign对象,调用对象的newInstance方法,创建代理对象。返回代理@FeignClient标注接口的代理对象。

  方法执行,调用代理对象ReflectiveFeign中的内部类FeignInvocationHandler的invoke方法。根据Method找到对应的MethodHandler。默认实现是SynchronousMethodHandler。

  1. 方法之前前,通过RequestInterceptor对请求进行拦截处理。
  2. 调用client.execute方法,请求调用。
  3. 结果封装

@FeignClient中url属性值:不存在

  @FeignClient中url为空,则url默认 = http:// + name + path;
  通过loadBalance方法构建请求代理对象。loadBalance。

loadBalance

  1. 通过FeignContext,获取到当前contextId对应的applicationContext。
  2. 通过applicationContext获取类型为client.class的实现类。
  3. 通过Feign.build设置client为通过applicationContext获取到的client。
  4. 通过applicationContext获取类型为Targeter.class的实现类。
  5. 调用Targeter.target方法获取到代理对象。

配置文件

feign: client: defaultToProperties: false # 是否以配置文件中配置为主。默认为trueconfig: #对应FeignClientProperties类的config成员变量default: # 日志级别logger-level: BASIC# 超时时间connect-timeout: 10000

在这里插入图片描述
  将config下的配置封装为config = Map<String,FeignClientConfiguration>。FeignClientConfiguration封装上面的配置。lg:日志级别,超时时长。Map中的key为@FeignClient注解属性contextId。默认为default,也可以为没有给Feign接口配置对应的参数。
  配置文件中的参数优先级别。feign接口级别的配置 --> 默认配置

请求参数体封装

  入口在ReflectiveFeign的newInstance方法。
在这里插入图片描述

ParseHandlersByName

  ParseHandlersByName是Feign.build的build()方法的时候构建的。
Feign.build.build()
  openFeign中的contract默认是SpringmvcContract,支持mvc注解的解析。
  ParseHandlersByName.apply方法。调用contract的parseAndValidateMetadata方法。解析获取到Feign接口的methodMetaData集合。然后遍历方法的源数据,构造Map对象,key:feign的接口名 + "#" + 方法名 + "(" + parameterTypeName集合 + ")"
value:SynchronousMethodHandler对象。对象里面包含MethodMetadata对象。
   我们知道,Feign接口代理的执行,最终会调用SynchronousMethodHandler.invoke方法。SynchronousMethodHandler对象中保存了client对象和方法的源信息。就可以进行业务的调用。

ParseHandlersByName.apply

SpringMvcFeignContract

  SpringMvcFeignContract继承SpringMvcContract。遍历Feign接口中的Method,调用Contract的parseAndValidateMetadata方法,解析封装为MethodMetadata。遍历MethodMetadata,对template进行处理。最下面,将 buildTemplate传给SynchronousMethodHandler对象。在这里插入图片描述

  方法执行时,调用SynchronousMethodHandler的invoke方法,invoke方法中调用buildTemplate的create方法,根据入参构建RequestTemplate对象。
在这里插入图片描述

  调用executeAndDecodej进行请求调用和编码处理。
  (1)获取RequestInteceptor,对template进行请求前拦截。
  (2)通过调用client.execute(request, options),发出请求。
在这里插入图片描述

路由,响应处理待完善


http://www.ppmy.cn/news/44130.html

相关文章

3、Web前端学习规划:CSS - 学习规划系列文章

CSS作为Web前端开发的第2种重要的语言&#xff0c;笔者建议在学了HTML之后进行。CSS主要是对于HTML做一个渲染&#xff0c;其也带了一些语言语法函数&#xff0c;功能也非常强大。 1、 简介&#xff1b; CSS(层叠样式表)是一种用于描述网页样式的语言。它可以控制网页中的字体、…

ERTEC200P-2 PROFINET设备完全开发手册(5-3)

5.3 标识和维护数据&#xff08;I&M&#xff09; 标识和维护数据是一类特殊的数据记录&#xff0c;其中 “I&M0”&#xff08;“16#AFF0”&#xff09;用于有关模块或设备的常规信息。包含的信息例如&#xff1a;订货号/Order-ID, 硬件软件版本/hard- and software v…

pandas笔记:offset.DateOffset

进行date的偏移 class pandas.tseries.offsets.DateOffset 1 参数说明 n 偏移量表示的时间段数。 如果没有指定时间模式&#xff0c;则默认为n天。 normalize是否将DateOffset偏移的结果向下舍入到前一天午夜**kwds 添加到偏移量的时间参数 年&#xff08;years&#xff09…

Linux复习 / 动静态库QA梳理 | 如何使用第三方库?

文章目录前言Q&A概念Q&#xff1a;使用静态库和使用动态库的程序有什么区别&#xff1f;Q&#xff1a;什么是静态链接/动态链接&#xff1f;使用与制作Q&#xff1a;如何制作动静态库&#xff1f;Q&#xff1a;如何使用第三方库&#xff1f;Q&#xff1a;程序加载时&#x…

Web 技术标准组织

个人博客 授人以鱼不如授人以渔. 所谓“一流的企业制定标准&#xff0c;二流的企业申请专利&#xff0c;三流的企业兜售产品”&#xff0c;这种说法虽不中亦不远。 追求专业精神的 IT 从业者不能只埋首于眼前的一亩三分地&#xff0c;被动承受变化&#xff0c;而要溯流而上&…

【剑指offer|6.寻找峰值】

0.寻找峰值 关键点: 返回任意一个峰值的下标即可nums[-1]nums[n]负无穷 输入&#xff1a;nums [1,2,3,1] 输出&#xff1a;2 解释&#xff1a;3 是峰值元素&#xff0c;你的函数应该返回其索引 2 1.傻瓜编程(纯属玩乐) class Solution { public:int findPeakElement(vector&l…

FMCW激光雷达,未来已来

2021年1月&#xff0c;一家名为Avea的激光雷达初创公司&#xff0c;与日本电装宣布达成合作协议&#xff0c;双方将共同推进FMCW&#xff08;调频连续波&#xff09;激光雷达的量产&#xff0c;目标是满足大众市场的需求。 众所周知&#xff0c;目前&#xff0c;大多数车载激光…

C++智能指针:更简单、更高效的内存管理方法

C智能指针&#xff1a;从新手到高手的心理密码C Smart Pointers: Psychological Passcodes from Beginner to Expert智能指针简介 (Introduction to Smart Pointers)智能指针类型 (Types of Smart Pointers)a. shared\_ptr (共享指针)b. unique\_ptr (独占指针)c. weak\_ptr (弱…