Spring Cloud组件源码之OpenFeign源码分析

" Spring 到底是春天的来临万物复苏,还是春转夏的干燥又炎热呢?"  Spring的来临让JavaEE走向了另一个高度。便捷的开发,完美的生态。物极必反,学习Spring的成本越来越低,导致Java程序员越来越密集,越来越廉价…… 那么又有多少 Springer 耐下性子去研究Spring生态的架构和底层实现的细节呢??

版本信息如下:

Spring:5.3.23
Spring Boot:2.6.13
Spring Cloud:3.1.5
Spring Cloud Openfeign:3.1.5
Feign:11.3.0

正文:

既然是研究Spring Cloud源码,那么没有Spring、Spring Boot的前置知识,只会让你永远迈不出第一步。下面是对于Spring Cloud的架构图。

既然是学习OpenFeign源码,我们第一步是不是应该清楚Openfeign的架构,由上图我们得知Spring Cloud 组件名 承上启下,向下兼容组件,向上实现规范和扩展。那么把Spring Cloud Openfeign带入进去就很好理解了,向下兼容Feign这个组件,向上实现规范和扩展。

反观,我们连Feign这个组件的原理都不知道,就去学习Spring Cloud Openfeign那怎么学得懂呢?下面是Feign这个组件的架构思想图。一言以蔽之:简化Http客户端的操作,兼容众多Http客户端框架,向上暴露出接口+注解+配置的简易操作方式。

先学会使用,再来研究其源码,Spring Cloud Feign的使用很简单,其实单独使用Feign这个组件跟其大同小异。接口+注解的使用方法,底层帮开发者自动去调用服务名为deptmanagecloud-provider其中的/provider/pt接口。

//@Service
@FeignClient(value = "deptmanagecloud-provider")
public interface FeignConsumer {
    @GetMapping("/provider/pt")
    String provider();
}

那么,接下来就是去研究其源码,到底如何帮开发者自动去调用的过程。

项目中要使用Spring Cloud OpenFeign就需要使用@EnableFeignClients注解。 所以这将是我们的入手点。

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

此注解配合@Configuration注解一起使用,一般是配合@SpringBootApplication注解(也是一个@Configuration)使用,而@EnableFeignClients注解又使用了@Import注解。所以需要找到FeignClientsRegistrar类的registerBeanDefinitions回掉方法。

class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        registerDefaultConfiguration(metadata, registry);
        registerFeignClients(metadata, registry);
    }


    public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();
        Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
        final Class<?>[] clients = attrs == null ? null : (Class<?>[]) attrs.get("clients");
        // 扫描当前目录下所有的@FeignClient类。
        if (clients == null || clients.length == 0) {
            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));
            }
        }
        else {
            for (Class<?> clazz : clients) {
                candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
            }
        }

        // 遍历当前目录下所有的@FeignClient类生成的BeanDefinition
        for (BeanDefinition candidateComponent : candidateComponents) {
            if (candidateComponent instanceof AnnotatedBeanDefinition) {
                // verify annotated class is an interface
                AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
                AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
                Assert.isTrue(annotationMetadata.isInterface(), "@FeignClient can only be specified on an interface");
                Map<String, Object> attributes = annotationMetadata
                .getAnnotationAttributes(FeignClient.class.getCanonicalName());
                String name = getClientName(attributes);
                registerClientConfiguration(registry, name, attributes.get("configuration"));
                // 给当前目录下所有的@FeignClient类生成的BeanDefinition添加Feign动态代理后的代理Class
                registerFeignClient(registry, annotationMetadata, attributes);
            }
        }
    }

}

Spring Cloud Openfein的核心原理大致就在这里了,所以对以上做个总结:

  1. 使用@EnableFeignClients注解,Spring底层会解析@Import注解,最终会回掉registerBeanDefinitions方法,此方法中调用registerFeignClients
  2. registerFeignClients方法中会去扫描当前路径下所有的类,如果类上带有@FeignClient注解就会生成BeanDefition。
  3. 遍历所有带有@FeignClient注解的类,反射拿到@FeignClient注解的元数据信息,然后调用registerFeignClient方法
  4. registerFeignClient方法会解析带有@FeignClient注解元数据信息当作燃料往Feign里面填充,然后Feign会去动态代理创建出带有@FeignClient注解的类的动态代理类。

看到这里,能力比较强的读者就能明白,Spring Cloud Openfeign作为中间层,向上实现规范和扩展接口,向下兼容组件本身。拿这里举例子,Spring Cloud Openfein使用Spring的扩展点@Import来解析@FeignClient注解,把@FeignClient注解元数据信息作为燃料,填充给Feign组件本身,Feign组件获取到燃料后动态代理带有@FeignClient注解的类。在后续的依赖注入和方法调用都是走代理对象,此时我们只需要把Feign组件中动态代理的流程和代理回掉弄明白即可。

那么,接着看到registerFeignClient方法的实现。

private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata,
    Map<String, Object> attributes) {
    String className = annotationMetadata.getClassName();
    Class clazz = ClassUtils.resolveClassName(className, null);
    ConfigurableBeanFactory beanFactory = registry instanceof ConfigurableBeanFactory
    ? (ConfigurableBeanFactory) registry : null;
    String contextId = getContextId(beanFactory, attributes);
    String name = getName(attributes);
    FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();
    factoryBean.setBeanFactory(beanFactory);
    factoryBean.setName(name);
    factoryBean.setContextId(contextId);
    factoryBean.setType(clazz);
    factoryBean.setRefreshableClient(isClientRefreshEnabled());
    // 这里是Spring的一个扩展点,最终创建Bean对象的时候,会回掉此处。
    BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(clazz, () -> {
        factoryBean.setUrl(getUrl(beanFactory, attributes));
        factoryBean.setPath(getPath(beanFactory, attributes));
        factoryBean.setDecode404(Boolean.parseBoolean(String.valueOf(attributes.get("decode404"))));
        Object fallback = attributes.get("fallback");
        if (fallback != null) {
            factoryBean.setFallback(fallback instanceof Class ? (Class<?>) fallback
                : ClassUtils.resolveClassName(fallback.toString(), null));
        }
        Object fallbackFactory = attributes.get("fallbackFactory");
        if (fallbackFactory != null) {
            factoryBean.setFallbackFactory(fallbackFactory instanceof Class ? (Class<?>) fallbackFactory
                : ClassUtils.resolveClassName(fallbackFactory.toString(), null));
        }
        return factoryBean.getObject();
    });

    BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, qualifiers);
    BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);

}

这里很简单,拿到了@FeginClient注解的元数据信息,创建了FeignClientFactortyBean对象,把数据全部注入到FeignClientFactortyBean对象中,并且这里使用了Spring的instanceSupplier扩展点,也即最终创建Bean对象的时候,会把创建的权利交给开发者自定义。所以最终创建的对象就是FeignClientFactortyBean对象的getObject方法。

public class FeignClientFactoryBean
implements FactoryBean<Object>, InitializingBean, ApplicationContextAware, BeanFactoryAware {


    @Override
    public Object getObject() {
        return getTarget();
    }

    <T> T getTarget() {
        // 从工厂中拿到FeignContext对象。
        FeignContext context = beanFactory != null ? beanFactory.getBean(FeignContext.class)
        : applicationContext.getBean(FeignContext.class);

        // 从上下文中创建出Feign.Builder对象,
        // 这里需要补充一下前置知识,Feign.Builder用于产出Feign对象,而Feign对象就是请求的上下文对象。
        Feign.Builder builder = feign(context);

        if (!StringUtils.hasText(url)) {
            …………
            // 使用Feign创建出动态代理的类
            return (T) loadBalance(builder, context, new HardCodedTarget<>(type, name, url));
        }

        …………

        // 使用Feign创建出动态代理的类
        Targeter targeter = get(context, Targeter.class);
        return (T) targeter.target(this, builder, context, new HardCodedTarget<>(type, name, url));
    }

    protected Feign.Builder feign(FeignContext context) {
        FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
        Logger logger = loggerFactory.create(type);

        // 从Spring工厂中拿到Feign组件中一个个可自定义配置的组件
        // 而我们啥时候配置过这些自定义的组件呢?
        // 没错,就是Spring boot帮我们自动配置的。
        // 所以只需要找到Spring boot帮我们自动配置的组件即可
        // 并且也能够明白,我们可以通过@Configuration+@Bean的方式自定义Feign的组件
        Feign.Builder builder = get(context, Feign.Builder.class)
                // required values
        .logger(logger)
        .encoder(get(context, Encoder.class))
        .decoder(get(context, Decoder.class))
        .contract(get(context, Contract.class));
        // @formatter:on

        configureFeign(context, builder);

        return builder;
    }
}

这里非常的简单,创建出Feign.Builder对象,此对象用于构建出Feign对象,而Feign对象作为请求的整个上下文。而我们需要把之前@FeignClient注解的燃料交给Feign,还有Spring boot帮我们自动配置的燃料也一同交给Feign,所以这里feign()方法从Spring的工厂中拿到Spring boot自动配置的组件。下面图来自Spring Cloud Openfeign中spring.factory自动配置文件自动配置的类。所以再再再再再一次证明了Spring Cloud Openfeign是承上启下。向上对接规范和扩展接口,向下对接组件本身

 

接下来,我们直接看向重点,如何动态代理和方法拦截的即可。

public abstract class Feign {
    // build方法构建出ReflectiveFeign类
    // 然后调用ReflectiveFeign类的newInstance方法
    public <T> T target(Target<T> target) {
        return build().newInstance(target);
    } 

    public Feign build() {
        super.enrich();

        SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
        new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors,
            responseInterceptor, logger, logLevel, dismiss404, closeAfterDecode,
            propagationPolicy, forceDecoding);
        ParseHandlersByName handlersByName =
        new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
            errorDecoder, synchronousMethodHandlerFactory);
        return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
    }
}


 这里把所有Feign中组件准备好,构建出ReflectiveFeign类,然后调用newInstance方法。

@Override
public <T> T newInstance(Target<T> target) {
    Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
    Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
    List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();

    // 解析所有方法
    for (Method method : target.type().getMethods()) {
      if (method.getDeclaringClass() == Object.class) {
        continue;
    } else if (Util.isDefault(method)) {
        DefaultMethodHandler handler = new DefaultMethodHandler(method);
        defaultMethodHandlers.add(handler);
        methodToHandler.put(method, handler);
    } else {
        methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
    }
}
// 创建出JDK动态代理中的拦截器对象
InvocationHandler handler = factory.create(target, methodToHandler);
// 动态代理,并且反射实例化代理对象
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
    new Class<?>[] {target.type()}, handler);

for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
  defaultMethodHandler.bindTo(proxy);
}
return proxy;
}

这里可以看到创建出JDK动态代理中所需的拦截器对象InvocationHandler,然后调用Proxy.newProxyInstance方法创建出代理类的实例对象。所以接下来看到如何拦截并且处理的就完美的闭环了。

@Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      if ("equals".equals(method.getName())) {
        try {
          Object otherHandler =
              args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
          return equals(otherHandler);
        } catch (IllegalArgumentException e) {
          return false;
        }
      } else if ("hashCode".equals(method.getName())) {
        return hashCode();
      } else if ("toString".equals(method.getName())) {
        return toString();
      }

      return dispatch.get(method).invoke(args);
    }


@Override
  public Object invoke(Object[] argv) throws Throwable {
    RequestTemplate template = buildTemplateFromArgs.create(argv);
    Options options = findOptions(argv);
    Retryer retryer = this.retryer.clone();
    while (true) {
      try {
        return executeAndDecode(template, options);
      } catch (RetryableException e) {
        try {
          retryer.continueOrPropagate(e);
        } catch (RetryableException th) {
          Throwable cause = th.getCause();
          if (propagationPolicy == UNWRAP && cause != null) {
            throw cause;
          } else {
            throw th;
          }
        }
        if (logLevel != Logger.Level.NONE) {
          logger.logRetry(metadata.configKey(), logLevel);
        }
        continue;
      }
    }
  }

// 执行,也即调用某个Http客户端的http请求
Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
    Request request = targetRequest(template);
        …………
    response = client.execute(request, options);
        …………
  }

最终回掉到SynchronousMethodHandler的invoke方法中,可以很清楚的看到内部就开始使用某个Http客户端,发送Http请求了。

总结:

能够理解,框架的定位,框架向上兼容什么,向下兼容什么,这对于理解源码是非常重要的一个部分。

最后,如果本帖对您有一定的帮助,希望能点赞+关注+收藏!您的支持是给我最大的动力,后续会一直更新各种框架的使用和框架的源码解读~!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/9451.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

VS2022编译libui库

libui是一个 C 中简单且可移植(但并非不灵活)的 GUI 库,它使用每个平台原生的GUI技术进行绘制。 官网地址:链接 本文将使用VS2022编译libui库,操作系统为Windows10。 1. 下载源代码 首先在官网下载源代码,由于此代码不依赖第三库,故只需下载源代码即可进行编译。 我下…

JavaScript函数中的arguments(js函数中的arguments,函数默认参数arguments)

简述&#xff1a;js中的函数大家都比较熟悉&#xff0c;今天来分享下函数中的默认参数arguments。js的函数参数和其他的语言有些不同&#xff0c;它并不介意你传进来多少个参数&#xff0c;以及参数的数据类型&#xff0c;即使你在定义函数时&#xff0c;只设置了两个形参&…

「解析」Matplotlib 绘制折线图

相比于【优雅】matplotlib 常见图、【优雅】matplotlib 3D图 而言&#xff0c;折线图使用的频率会更高一些&#xff0c;在此整理下最近使用 Matplotlib 绘制折线图常用的一些配置&#xff0c;小伙伴们只需要修改对应的 aug_list、list 即可直接使用 # !/usr/bin/env python …

asp.net mvc学习(三)

Razor语法 Razor语法特点 结构性强&#xff0c;容易阅读。易于学习。不是新的语言。在任何编辑器上编写Razor都很容易。与ide充分结合。 Razor语法主要规则 Razor代码封装于{...}中。 行内表达式&#xff08;变量和函数&#xff09;以开头 代码语句以分号结尾 字符串由引号…

Python 进阶指南(编程轻松进阶):八、常见的 Python 陷阱

原文&#xff1a;http://inventwithpython.com/beyond/chapter8.html 虽然 Python 是我最喜欢的编程语言&#xff0c;但它也不是没有缺陷。每种语言都有缺点&#xff08;有些比其他的多&#xff09;&#xff0c;Python 也不例外。新的 Python 程序员必须学会避免一些常见的“陷…

JavaScript 中问号的三种用法 ??和?.以及?: 您知道吗?

最近看了一些关于JavaScript的测试脚本&#xff0c;觉得JS 中问号的用法还是蛮有意思的&#xff0c;于是做了一下总结&#xff0c;在这里分享给大家&#xff01;JS中的问号大概有三种用法&#xff0c;分别是&#xff1a;空值合并操作符、可选链操作符和三目运算。 问号问号&…

如何使用手机远程锁定电脑?

​“有时我已经到家了&#xff0c;却忘记锁上我的公司的电脑。每次我都害怕我电脑上的数据丢失。我可以在手机上远程锁定我的Windows计算机以避免这个问题吗&#xff1f;” 答案是肯定的&#xff01;很多人可能会遇到同样的下班不锁电脑的问题&#xff0c;有的人可能尝…

新规拉开中国生成式AI“百团大战”序幕?

AI将走向何方&#xff1f; ChatGPT在全球范围掀起的AI热潮正在引发越来越多的讨论&#xff0c;AI该如何管理&#xff1f;AI该如何发展&#xff1f;一系列问题都成为人们热议的焦点。此前&#xff0c;马斯克等海外名人就在网络上呼吁OpenAI暂停ChatGPT的模型训练和迭代&#xf…

算法套路八——二叉树深度优先遍历(前、中、后序遍历)

算法套路八——二叉树深度优先遍历&#xff08;前、中、后序遍历&#xff09; 算法示例&#xff1a;LeetCode98&#xff1a;验证二叉搜索树 给你一个二叉树的根节点 root &#xff0c;判断其是否是一个有效的二叉搜索树。 有效 二叉搜索树定义如下&#xff1a; 节点的左子树只…

Shell脚本之免交互

一、Here Document免交互 1、 概念 Here Document使用I/O重定向的方式将命令列表提供给交互式程序或命令&#xff0c;比如 ftp、cat 或 read 命令。 是标准输入的一种替代品可以帮助脚本开发人员不必使用临时文件来构建输入信息&#xff0c;而是直接就地生产出一个"文件…

No.033<软考>《(高项)备考大全》【第17章】战略管理

【第17章】战略管理1 章节相关2 战略管理2.1 组织战略管理2.1 组织战略类型和层次2.1.1 组织事业战略类型2.1.2 组织事业战略类型2.1.3 组织完整的战略包括三个层次2.1.4 组织战略从层次分为组织层战略、事业层战略、职能层战略等2.1.5 平横计分卡2.1.6 项目组合管理3 练习题参…

Leetcode.111 二叉树的最小深度

题目链接 Leetcode.111 二叉树的最小深度 easy 题目描述 给定一个二叉树&#xff0c;找出其最小深度。 最小深度是从 根节点 到 最近叶子节点 的 最短路径上的节点数量。 说明: 叶子节点是指没有子节点的节点。 示例 1&#xff1a; 输入&#xff1a;root [3,9,20,null,nul…

车载网络 - Autosar网络管理 - 网络管理简介

一、什么是CAN网络管理及它的作用 现在的车辆是由大量的ECU节点组成的&#xff0c;为了能使各ECU能够正确并及时地进行CAN通信&#xff0c;需要有一套机制来统一协调总线上各节点的休眠唤醒&#xff0c;这套机制就是CAN网络管理&#xff08;NM&#xff09;。 网络管理的目的是保…

项目2:后端管理员项目结构初始化

项目2&#xff1a;后端管理员项目结构初始化 1.创建数据库和表 2.初始化父项目 3.初始化项目模块 4.初始化core核心模块&#xff08;代码生成器&#xff09; 项目2&#xff1a;后端管理员项目结构初始化 1.创建表 创建数据库 编码使用utf-8 sql语句 /*Navicat Premium …

18_I.MX6ULL_I2C实验

目录 I2C简介 起始位 停止位 数据传输 应答信号 I2C写时序 I2C读时序 I2C多字节读写时序 相关寄存器 AP3216C简介 实验源码 I2C简介 I2C是最常用的通信接口,众多的传感器都会提供I2C接口来和主控相连,比如陀螺仪、加速度计、触摸屏等等。所以I2C是做嵌入式开发必须…

【高项】项目人力资源管理,沟通管理与干系人管理(十大管理)

【高项】项目人力资源管理&#xff0c;沟通管理与干系人管理&#xff08;十大管理&#xff09; 文章目录1、人力资源管理1.1 什么是人力资源管理&#xff1f;1.2 如何进行人力资源管理&#xff1f;&#xff08;过程&#xff09;1.3 人力资源管理工具1.4 人力资源管理文件2、沟通…

语雀笔记备份导出

参考: https://www.cnblogs.com/ssslinppp/p/17020303.htmlhttps://github.com/yuque/yuque-exporterhttps://zhuanlan.zhihu.com/p/582287220https://www.yuque.com/duzh929/blog/ocffqghttps://www.yuque.com/hijiaobu/datalife/onf6sy#BKajf 现在需要超级管理员,若是没有超级…

【华为机试真题详解JAVA实现】—整数与IP地址间的转换

目录 一、题目描述 二、解题代码 一、题目描述 原理:ip地址的每段可以看成是一个0-255的整数,把每段拆分成一个二进制形式组合起来,然后把这个二进制数转变成 一个长整数。 举例:一个ip地址为10.0.3.193 每段数字 相对应的二进制数 10 000…

GDPU C语言 天码行空6

1. 数组顺序查找 ⭐ 语法题 #include<stdio.h>int main() {int n,x,i;int a[102];scanf("%d", &n);for (i 0; i < n; i){scanf("%d", &a[i]);}scanf("%d", &x);int idx -1;//记录x的最大下标int max 0;// 记录大于x的数…

如何写一个优质高效的网络项目实施方案?这篇文章值得收藏!

随着互联网技术的不断发展&#xff0c;网络项目的实施成为了许多企业和组织的重要任务。网络项目实施方案是指在进行网络项目实施时&#xff0c;为了保障项目的顺利进行&#xff0c;达到项目目标和交付要求&#xff0c;所制定的详细计划和操作指南。一个好的网络项目实施方案对…