openfeign原理

news2025/7/19 6:24:09

openfeign原理

@EnableFeignClients注解启用Feign客户端,通过@Import注解导入了FeignClientsRegistrar类加载额外的Bean。FeignClientsRegistrar实现了ImportBeanDefinitionRegistrar接口,在Spring启动过程中会调用registerBeanDefinitions方法注册BeanDefinition

//FeignClientsRegistrar#registerBeanDefinitions
public void registerBeanDefinitions(AnnotationMetadata metadata,
                                    BeanDefinitionRegistry registry) {
    //处理@EnableFeignClients注解上的配置
    registerDefaultConfiguration(metadata, registry);
    //处理@FeignClient对应的接口
    registerFeignClients(metadata, registry);
}

FeignClientsRegistrar#registerFeignClients:

private void registerDefaultConfiguration(AnnotationMetadata metadata,
                                          BeanDefinitionRegistry registry) {
    Map<String, Object> defaultAttrs = metadata
        .getAnnotationAttributes(EnableFeignClients.class.getName(), true);

    if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
        String name;
        if (metadata.hasEnclosingClass()) {
            name = "default." + metadata.getEnclosingClassName();
        }
        else {
            name = "default." + metadata.getClassName();
        }
        registerClientConfiguration(registry, name,
                                    defaultAttrs.get("defaultConfiguration"));
    }
}

FeignClientsRegistrar#registerFeignClients:

public void registerFeignClients(AnnotationMetadata metadata,
                                 BeanDefinitionRegistry registry) {
    //找到@FeignClient标识的接口类
    ClassPathScanningCandidateComponentProvider scanner = getScanner();
    scanner.setResourceLoader(this.resourceLoader);
    Set<String> basePackages;
    Map<String, Object> attrs = metadata
        .getAnnotationAttributes(EnableFeignClients.class.getName());
    AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
        FeignClient.class);
    final Class<?>[] clients = attrs == null ? null
        : (Class<?>[]) attrs.get("clients");
    if (clients == null || clients.length == 0) {
        scanner.addIncludeFilter(annotationTypeFilter);
        basePackages = getBasePackages(metadata);
    }
    else {
        final Set<String> clientClasses = new HashSet<>();
        basePackages = new HashSet<>();
        for (Class<?> clazz : clients) {
            basePackages.add(ClassUtils.getPackageName(clazz));
            clientClasses.add(clazz.getCanonicalName());
        }
        AbstractClassTestingTypeFilter filter = new 
            AbstractClassTestingTypeFilter() {
            @Override
            protected boolean match(ClassMetadata metadata) {
                String cleaned = metadata.getClassName().replaceAll("\\$", ".");
                return clientClasses.contains(cleaned);
            }
        };
        scanner.addIncludeFilter(
            new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
    }

    for (String basePackage : basePackages) {
        Set<BeanDefinition> candidateComponents = scanner
            .findCandidateComponents(basePackage);
        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"));
                registerFeignClient(registry, annotationMetadata, attributes);
            }
        }
    }
}

FeignClientsRegistrar#registerFeignClient:

private void registerFeignClient(BeanDefinitionRegistry registry,
                                 AnnotationMetadata annotationMetadata, 
                                 Map<String, Object> attributes) {
    String className = annotationMetadata.getClassName();
    //创建了FeignClientFactoryBean实例
    BeanDefinitionBuilder definition = BeanDefinitionBuilder
        .genericBeanDefinition(FeignClientFactoryBean.class);
    validate(attributes);
    definition.addPropertyValue("url", getUrl(attributes));
    definition.addPropertyValue("path", getPath(attributes));
    String name = getName(attributes);
    definition.addPropertyValue("name", name);
    String contextId = getContextId(attributes);
    definition.addPropertyValue("contextId", contextId);
    definition.addPropertyValue("type", className);
    definition.addPropertyValue("decode404", attributes.get("decode404"));
    definition.addPropertyValue("fallback", attributes.get("fallback"));
    definition.addPropertyValue("fallbackFactory", 
                                attributes.get("fallbackFactory"));
    definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
    String alias = contextId + "FeignClient";
    AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
    boolean primary = (Boolean) attributes.get("primary"); // has a default, won't be
    // null
    beanDefinition.setPrimary(primary);
    String qualifier = getQualifier(attributes);
    if (StringUtils.hasText(qualifier)) {
        alias = qualifier;
    }

    BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
                                                           new String[] { alias });
    BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}

FeignClientFactoryBean#getObject:

//实现FactoryBean接口getObject方法,返回工厂管理的Bean实例
@Override
public Object getObject() throws Exception {
    return getTarget();
}

//根据指定的数据和上下文信息创建Feign客户端
<T> T getTarget() {
    //获得FeignContext对象
    FeignContext context = this.applicationContext.getBean(FeignContext.class);
    Feign.Builder builder = feign(context);

    if (!StringUtils.hasText(this.url)) {
        if (!this.name.startsWith("http")) {
            this.url = "http://" + this.name;
        }
        else {
            this.url = this.name;
        }
        this.url += cleanPath();
        //获得Feign客户端
        return (T) loadBalance(builder, context,
                               new HardCodedTarget<>(this.type, this.name, this.url));
    }
    if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
        this.url = "http://" + this.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 unwrap
            client = ((LoadBalancerFeignClient) client).getDelegate();
        }
        builder.client(client);
    }
    Targeter targeter = get(context, Targeter.class);
    return (T) targeter.target(this, builder, context,
                               new HardCodedTarget<>(this.type, this.name, url));
}

//获得Feign.Builder实例
protected Feign.Builder feign(FeignContext context) {
    FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
    Logger logger = loggerFactory.create(this.type);

    // @formatter:off
    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;
}

//使用FeignContext和Feign.Builder对象配置Feign
protected void configureFeign(FeignContext context, Feign.Builder builder) {
    FeignClientProperties properties = this.applicationContext
        .getBean(FeignClientProperties.class);
    if (properties != null) {
        if (properties.isDefaultToProperties()) {
            configureUsingConfiguration(context, builder);
            configureUsingProperties(
                properties.getConfig().get(properties.getDefaultConfig()),
                builder);
            configureUsingProperties(properties.getConfig().get(this.contextId),
                                     builder);
        }
        else {
            configureUsingProperties(
                properties.getConfig().get(properties.getDefaultConfig()),
                builder);
            configureUsingProperties(properties.getConfig().get(this.contextId),
                                     builder);
            configureUsingConfiguration(context, builder);
        }
    }
    else {
        configureUsingConfiguration(context, builder);
    }
}

//以负载均衡的方式获取Feign客户端
protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
                            HardCodedTarget<T> target) {
    //获取Client对象
    Client client = getOptional(context, Client.class);
    if (client != null) {
        builder.client(client);
        Targeter targeter = get(context, Targeter.class);
        return targeter.target(this, builder, context, target);
    }

    throw new IllegalStateException(
        "No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?");
}

//默认的Target实现类
class DefaultTargeter implements Targeter {

	@Override
	public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,
			FeignContext context, Target.HardCodedTarget<T> target) {
		return feign.target(target);
	}

}

//Feign#target
public <T> T target(Target<T> target) {
    return build().newInstance(target);
}

//通过反射方式创建Feign对象
public Feign build() {
    SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
        new SynchronousMethodHandler
        .Factory(client, retryer, requestInterceptors, logger,                                             logLevel, decode404, closeAfterDecode, propagationPolicy);
    ParseHandlersByName handlersByName = new ParseHandlersByName
        (contract, options, encoder, decoder, queryMapEncoder,
                                errorDecoder, synchronousMethodHandlerFactory);
    return new ReflectiveFeign(
        handlersByName, invocationHandlerFactory, queryMapEncoder);
}

//使用JDK动态代理的方式创建Target的代理对象
@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)));
        }
    }
    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;
}

OpenFeign核心流程

OpenFeign核心流程图

  1. 在Spring项目启动阶段,服务OpenFeign框架会发起一个主动的扫描包的流程;
  2. 从指定目录下扫描并加载所有被@FeignClient注解修饰的接口,然后将这些接口转换成Bean,统一交给Spring来管理;
  3. 然后这些接口会经过MVC Contract协议解析,将方法上的注解解析出来,放到MethodMetadata中;
  4. 根据加载的每个FeignClient接口生成一个动态代理对象,指向一个包含对应方法的MethodHandler的HashMap。生成的动态代理对象会被添加到Spring容器中,并注入到对应的服务中;
  5. 服务A调用接口,准备发起远程调用;
  6. 从动态代理对象Proxy中找到一个MethodHandler实例,生成Request,包含欧服务的请求URL;
  7. 经过负载均衡算法找到一个服务的IP地址,拼接处请求的URL;
  8. 服务B处理服务A发起的远程调用请求,执行业务逻辑后,返回响应给服务A。

核心思想

  1. OpenFeign扫描带有@FeignClient注解的接口,然后为其生成一个动态代理;
  2. 动态代理中包含有接口方法的MethodHandlerMethodHandler中又包含经过MVC Contract解析注解后的元数据;
  3. 发起请求时,MethodHandler会生成一个Request
  4. 负载均衡器Ribbon会从服务列表中选取一个Server,拿到对应的IP地址后,拼接成最后的URL,就可以发起远程服务调用了。

总结

  1. OpenFeign是声明式的HTTP客户端,可以像访问本地方法一样进行远程调用;
  2. 提供了HTTP请求的模板,编写简单的接口和插入注解,就可以定义好HTTP请求的参数、格式、地址等信息;
  3. 整合了负载均衡组件Ribbon和服务熔断组件Hystrix

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

自动化项目倍加福WCS-PG210E使用GSD文件

1&#xff0e;硬件电气连接 WCS-PG210E WCS3B WCS2B Pin 颜色 Pin 颜色 24V UB 1 BN棕色 2 WH白色 0V GND 3 BU蓝色 3 BU蓝色 RS485- RS485- 4 BK黑色 1 BN棕色 RS485 RS485 2 WH白色 4 BK黑色 保留 5 GY灰色 5 GY灰色 2. 安装W…

Nginx (4):nginx动静分离

什么是动静分离不解释了&#xff0c;网上说的很清楚&#xff0c;这里只说配置 目的 02虚拟机运行一个tomcat&#xff0c;处理动态请求&#xff0c;而对静态文件的访问则交给01虚拟机。操作 下面是01虚拟机的配置文件内容&#xff1a; server {listen 82;listen [::]:82;#root /…

六、nacos环境隔离、服务配置拉取和多环境配置共享

文章目录一、环境隔离-namespace1.namespace理解2.创建命名空间二、Nacos-实现配置管理三、nacos-实现服务配置拉取1.非热更新2.热更新&#xff1a;四、实现多环境配置共享1.开发环境&#xff1a;2.测试环境3.结论一、环境隔离-namespace 1.namespace理解 Nacos中服务存储和数…

Element Plus 组件库相关技术:7. 组件实现的基本流程及 Icon 组件的实现

前言 本章节我们将要实现 Icon 组件&#xff0c;Icon 组件应该是所有组件里面最简单的一个组件了&#xff0c;所以我们由简入深&#xff0c;循序渐进进行学习。Icon 组件虽然简单&#xff0c;但它却包含了一个组件的全部基础流程&#xff0c;通过实现 Icon 组件进行理解 Eleme…

疫情失业之下,测试的未来在哪里

前天和测试圈子里一个朋友聊了关于今年求职招聘市场行情和个人认知以及发展副业的话题。 聊起了今年的求职招聘行情&#xff0c;他说他们公司已经裁了一波人了&#xff0c;估计年底还会有一波裁员。 今年的市场冷的有点吓人&#xff0c;在这么下去&#xff0c;他也会担心自己…

nacos实现负载均衡、权重

文章目录一、nacos服务分级存储模型二、Nacos-NacosRule 实现负载均衡三、nacos-服务实例的权重设置一、nacos服务分级存储模型 修改 application.yml 配置文件&#xff1a; spring:cloud:nacos:server-addr: localhost:8848discovery:cluster-name: HZ #集群位置&#xff0c…

Linux C/C++ 学习笔记(九):百万并发的服务器实现

本文内容参考自(2条消息) Linux C/C 开发&#xff08;学习笔记十三)&#xff1a;百万并发的服务器实现_菊头蝙蝠的博客-CSDN博客_linux百万并发 一、connection_refuesed ---->文件系统最大的进程fd个数 nat 模式&#xff0c;物理机的VMnet8网卡&#xff0c;连接到了VMnet…

selenium--关闭窗口,指定窗口大小,前进,后退,刷新等等

关闭窗口跳转到指定页面窗口大小设置返回上个页面前进到下一个页面页面刷新关闭窗口 在selenium中执行完关闭窗口一般有两种方法&#xff1a; driver.close() driver.quit()这两个都是常用的方法&#xff0c;但是他们有什么区别呢&#xff1f; 对于driver.close(),他是关闭当…

【FME实战教程】003:FME读取地理空间数据(矢量、栅格、点云、三维模型、数据库、地理服务)大全

FME读取地理空间数据&#xff08;矢量、栅格、点云、三维模型、空间数据库、地理服务&#xff09;大全。 文章目录1. FME读取数据1.1 读取矢量1.1.1 读取Shapefile1.1.2 读取dwg1.2 读取栅格数据1.2.1 影像DOM1.3 读取地理数据库1.3.1 读取文件数据库&#xff08;.gdb&#xff…

机械原理复习试题

​ 编辑切换为居中 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; ​ 编辑切换为居中 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; ​ 编辑 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; ​ 编辑…

聚类分析的基本概念和方法

聚类分析的基本概念和方法 文章目录聚类分析的基本概念和方法前言一、什么是聚类分析1、聚类分析基本流程与步骤2、 什么是好的聚类方法3、聚类的模型评估4、聚类分析的比较5、聚类分析的挑战二、基本聚类方法概述三、划分算法1、基本概念2、k-means 聚类方法1、k-means 方法的…

CMake中configure_file的使用

CMake中的configure_file命令用于将一个文件拷贝到另一个位置并修改其内容&#xff0c;其格式如下&#xff1a; configure_file(<input> <output>[NO_SOURCE_PERMISSIONS | USE_SOURCE_PERMISSIONS |FILE_PERMISSIONS <permissions>...][COPYONLY] [ESCAPE_…

01 一条SQL 语句是如何执行的?

select * from teacher where id 10 1、一条简单的sql语句底层的执行过程是怎么样的&#xff1f; 答&#xff1a;一条sql执行会经过连接器、查询缓存、分析器、优化器和执行器等步骤。 2、连接器的作用是什么&#xff1f; 答&#xff1a;sql查询&#xff0c;首先连接到这个数…

【机器学习项目实战10例】(四):利用XGBoost实现短期电力负荷预测

💥 项目专栏:【机器学习项目实战10例】 文章目录 一、利用XGBoost实现短期电力负荷预测二、数据集介绍三、将数据进行标准化四、形成训练数据五、划分训练集、测试集六、定义模型七、模型训练八、训练集、测试集验证九、网络搜索十、绘制结果一、利用XGBoost实现短期电力负荷…

分布式事务

一、事务 1.1、什么是事务&#xff1f; 事务&#xff08;transaction&#xff09;是访问并操作数据库中数据的一个程序执行单元&#xff0c;由开始事务和提交事务之间的所有的语句组成。事务的结束有两种&#xff0c;一个是事务中间的所有操作执行成功&#xff0c;提交事务。一…

UE5笔记【九】蓝图BluePrint;

新建一个第三视角游戏。然后打开关卡蓝图。 长得跟材料编辑器一样。 这里是我们创建Node和新功能的地方。 首先我们新建一个游戏开始的地方。右键&#xff1a;Begin搜索。 我们需要打印一行字&#xff1a;欢迎来到游戏世界。我们需要添加一个打印文本的结点&#xff1a;PrintT…

APS自动排产 — 排产结果拉动物料需求计划

一、APS系统生产计划前应该注意哪些 建立好基础资料 标准产能&#xff1a;所有产品的标准产能&#xff0c;来自于工程技术部。如果工程技术部无法提供标准产能&#xff0c;则请生产部门根据实际提供相对准确的标准产能。技术资料&#xff1a;产品的物料清单(BOM)、图纸、工程…

2023最新SSM计算机毕业设计选题大全(附源码+LW)之java线上学习系统8e88w

做毕业设计一定要选好题目。毕设想简单&#xff0c;其实很简单。这里给几点建议&#xff1a; 1&#xff1a;首先&#xff0c;学会收集整理&#xff0c;年年专业都一样&#xff0c;岁岁毕业人不同。很多人在做毕业设计的时候&#xff0c;都犯了一个错误&#xff0c;那就是不借鉴…

ArcGIS中ArcMap图层属性表的中文字段乱码的解决方法

本文介绍ArcMap软件打开图层的属性表后&#xff0c;出现字段中汉字乱码情况的解决方法。 有时在使用ArcMap软件时&#xff0c;会发现一些图层的属性表中&#xff0c;原本应该是中文的字段却出现乱码的情况&#xff1b;如下图所示&#xff0c;其中NAME99一栏应该是图层中各个要素…

50-51 - C++对象模型分析

---- 整理自狄泰软件唐佐林老师课程 1. 回归本质 1.1 class是一种特殊的struct 在内存中class依旧可以看作 变量的集合class与struct遵循相同的 内存对齐 规则class中的成员函数与成员变量是 分开存放 的 每个对象有独立的成员变量所有对象共享类中的成员函数 1.2 值得思考…