OpenFeign动态代理、源码分析

news2024/11/17 10:58:00

1、OpenFeign概述

OpenFeign 组件的前身是 Netflix Feign 项目,由 Netflix 公司开发。后来 Feign 项目被贡献给了开源组织,随后Feign退出历史舞台。

OpenFeign是Spring Cloud在Feign的基础上支持了SpringMVC的注解,如@RequestMapping等等。OpenFeign的@FeignClient可以解析SpringMVC的@RequestMapping注解下的接口,并通过动态代理的方式产生实现类。实现类中OpenFeign 提供了一种声明式的远程调用接口,它可以大幅简化远程调用的编程体验。

2、OpenFeign的动态代理(JDK的动态代理)

OpenFeing 通过 Java 动态代理生成了一个“代理类”,这个代理类将接口调用转化成为了一个远程服务调用。

SpringCloud的服务调用方中

  1. 在项目启动阶段,OpenFeign 框架会发起一个主动的扫包流程,从指定的目录下扫描并加载所有被 @FeignClient 注解修饰的接口。

  2. OpenFeign 会针对每一个 FeignClient 接口生成一个动态代理对象

  3. 动态代理对象会被添加到 Spring 上下文中,并注入到对应的服务里

  4. 发起底层方法调用

在这里插入图片描述


class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {

	
	private ResourceLoader resourceLoader;

	private Environment environment;

	FeignClientsRegistrar() {
	}

    //...省略

	@Override
	public void setResourceLoader(ResourceLoader resourceLoader) {
		this.resourceLoader = resourceLoader;
	}

    /**
     * ImportBeanDefinitionRegistrar以Spring钩子函数的方式自定义Definitions
     */
	@Override
	public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        // 注册默认的Feign配置
		registerDefaultConfiguration(metadata, registry);
        // 注册所有定义的FeignClient
		registerFeignClients(metadata, registry);
	}

	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"));
		}
	}

	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");
		if (clients == null || clients.length == 0) {
            // 扫描被FeignClient注解修饰的类
			ClassPathScanningCandidateComponentProvider scanner = getScanner();
			scanner.setResourceLoader(this.resourceLoader);
			scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
			Set<String> basePackages = getBasePackages(metadata);
			for (String basePackage : basePackages) {
                // 扫描BeanDefinition
				candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
			}
		}
		else {
			for (Class<?> clazz : clients) {
				candidateComponents.add(new AnnotatedGenericBeanDefinition(clazz));
			}
		}
        // 封装为BeanDefinition注册进Spring容器中
		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);
			}
		}
	}

	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());
        // 动态创建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));
			}
            // 负载均衡及动态代理FeignClientFactoryBean.getObject()
			return factoryBean.getObject();
		});
		definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
		definition.setLazyInit(true);
		validate(attributes);

		AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
		beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);
		beanDefinition.setAttribute("feignClientsRegistrarFactoryBean", factoryBean);

		// has a default, won't be null
		boolean primary = (Boolean) attributes.get("primary");

		beanDefinition.setPrimary(primary);

		String[] qualifiers = getQualifiers(attributes);
		if (ObjectUtils.isEmpty(qualifiers)) {
			qualifiers = new String[] { contextId + "FeignClient" };
		}

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

		registerOptionsBeanDefinition(registry, contextId);
	}
    //...省略

	private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object configuration) {
        // 定义了一个BeanDefinition
		BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FeignClientSpecification.class);
		builder.addConstructorArgValue(name);
		builder.addConstructorArgValue(configuration);
        // 将BeanDefinition注册到BeanFactory中
		registry.registerBeanDefinition(name + "." + FeignClientSpecification.class.getSimpleName(),
				builder.getBeanDefinition());
	}
    /**
     * 读取配置文件
     */
	@Override
	public void setEnvironment(Environment environment) {
		this.environment = environment;
	}

    //...省略

}
public class FeignClientFactoryBean
		implements FactoryBean<Object>, InitializingBean, ApplicationContextAware, BeanFactoryAware {
    //...省略
    /**
     * 创建负载均衡的client
     */
	protected <T> T loadBalance(Feign.Builder builder, FeignContext context, HardCodedTarget<T> target) {
		Client client = getOptional(context, Client.class);
		if (client != null) {
			builder.client(client);
			applyBuildCustomizers(context, builder);
			Targeter targeter = get(context, Targeter.class);
            // 创建动态代理对象
			return targeter.target(this, builder, context, target);
		}
        // 如果使用openFeign 未引用spring-cloud-starter-loadbalancer包就会抛出异常
		throw new IllegalStateException(
				"No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-loadbalancer?");
	}
    //...省略

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

	/**
	 * @param <T> the target type of the Feign client
	 * @return a {@link Feign} client created with the specified data and the context
	 * information
	 */
	<T> T getTarget() {
        // FeignClient是Client的动态代理对象,而Client对象是真正执行http请求的对象
        // 获取Feign的上下文
		FeignContext context = beanFactory != null ? beanFactory.getBean(FeignContext.class)
				: applicationContext.getBean(FeignContext.class);
		Feign.Builder builder = feign(context);
        // 如果url没有定义,则进入到该判断中创建对象,该判断中创建的对象具有负载均衡功能
        // @FeignClient(value = "lyy-cloud-server", path = "/user",url = "192.168.0.105") 如果配置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));
		}
		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 FeignBlockingLoadBalancerClient) {
				// not load balancing because we have a url,
				// but Spring Cloud LoadBalancer is on the classpath, so unwrap
				client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
			}
			if (client instanceof RetryableFeignBlockingLoadBalancerClient) {
				// not load balancing because we have a url,
				// but Spring Cloud LoadBalancer is on the classpath, so unwrap
				client = ((RetryableFeignBlockingLoadBalancerClient) client).getDelegate();
			}
			builder.client(client);
		}

		applyBuildCustomizers(context, builder);

		Targeter targeter = get(context, Targeter.class);
        // 创建动态代理对象
		return (T) targeter.target(this, builder, context, new HardCodedTarget<>(type, name, url));
	}
//...省略

}

// 创建动态代理对象
targeter.target()→DefaultTargeter.target()→Feign.Builder.target()→Feign.newInstance()→
ReflectiveFeign.newInstance()。

public class ReflectiveFeign extends Feign {
    private final ReflectiveFeign.ParseHandlersByName targetToHandlersByName;
    private final InvocationHandlerFactory factory;
    private final QueryMapEncoder queryMapEncoder;

    ReflectiveFeign(ReflectiveFeign.ParseHandlersByName targetToHandlersByName, InvocationHandlerFactory factory, QueryMapEncoder queryMapEncoder) {
        this.targetToHandlersByName = targetToHandlersByName;
        this.factory = factory;
        this.queryMapEncoder = queryMapEncoder;
    }

    public <T> T newInstance(Target<T> target) {
        // nameToHandler 主要用于处理用户自定义的方法
        Map<String, MethodHandler> nameToHandler = this.targetToHandlersByName.apply(target);
        Map<Method, MethodHandler> methodToHandler = new LinkedHashMap();
        // DefaultMethodHandler用于处理接口中default方法
        List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList();
        Method[] var5 = target.type().getMethods();
        int var6 = var5.length;
        // 遍历接口中的所有方法
        for(int var7 = 0; var7 < var6; ++var7) {
            // 定义的方法
            Method method = var5[var7];
            // 跳过Object中的方法
            if (method.getDeclaringClass() != Object.class) {
                // 如果是default方法,则创建DefaultMethodHandler处理
                if (Util.isDefault(method)) {
                    DefaultMethodHandler handler = new DefaultMethodHandler(method);
                    defaultMethodHandlers.add(handler);
                    methodToHandler.put(method, handler);
                } else {
                    // 用户自定义的方法,此时从nameToHandler中拿出SynchronousMethodHandler进行映射
                    methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
                }
            }
        }
        // 创建InvocationHandler 核心代理对象,代理逻辑都封装在该对象中。
        // 注意,这里传递了methodToHandler(method→MethodHandler)这个映射,
        // MethodHandler可能是DefaultMethodHandler(处理default方法)或者SynchronousMethodHandler(处理用户定义的远程调用方法)。
        // 代理过程中,会根据方法名称dispatch到这个映射中对应的MethodHandler进行处理。
        InvocationHandler handler = this.factory.create(target, methodToHandler);
        // 创建代理对象
        T proxy = Proxy.newProxyInstance(target.type().getClassLoader(), new Class[]{target.type()}, handler);
        Iterator var12 = defaultMethodHandlers.iterator();

        while(var12.hasNext()) {
            DefaultMethodHandler defaultMethodHandler = (DefaultMethodHandler)var12.next();
            defaultMethodHandler.bindTo(proxy);
        }
        // 返回代理对象
        return proxy;
    }
    //...省略
}

this.factory.create→InvocationHandlerFactory→Default.create→FeignInvocationHandler

调用方调用服务提供方方法会调用FeignInvocationHandler.invoke是用户定义的远程调用方法,则从dispatch映射中获取对应的SynchronousMethodHandler.invoke()处理

public Object invoke(Object[] argv) throws Throwable {
    RequestTemplate template = this.buildTemplateFromArgs.create(argv);
    Options options = this.findOptions(argv);
    Retryer retryer = this.retryer.clone();

    while(true) {
        try {
            // 具体执行远程调用的方法
            return this.executeAndDecode(template, options);
        } catch (RetryableException var9) {
            RetryableException e = var9;

            try {
                // 判断是否需要重试
                retryer.continueOrPropagate(e);
            } catch (RetryableException var8) {
                Throwable cause = var8.getCause();
                if (this.propagationPolicy == ExceptionPropagationPolicy.UNWRAP && cause != null) {
                    throw cause;
                }

                throw var8;
            }

            if (this.logLevel != Level.NONE) {
                this.logger.logRetry(this.metadata.configKey(), this.logLevel);
            }
        }
    }
}
Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
    // 构造请求对象
    Request request = this.targetRequest(template);
    if (this.logLevel != Level.NONE) {
        this.logger.logRequest(this.metadata.configKey(), this.logLevel, request);
    }

    long start = System.nanoTime();

    Response response;
    try {
        // 执行http请求,引用spring-cloud-loadbalancer后this.client为FeignBlockingLoadBalancerClient
        response = this.client.execute(request, options);
        response = response.toBuilder().request(request).requestTemplate(template).build();
    } catch (IOException var12) {
        if (this.logLevel != Level.NONE) {
            this.logger.logIOException(this.metadata.configKey(), this.logLevel, var12, this.elapsedTime(start));
        }

        throw FeignException.errorExecuting(request, var12);
    }
    //...省略
}

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

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

相关文章

基于springboot的家装平台设计与实现

项目描述 临近学期结束&#xff0c;还是毕业设计&#xff0c;你还在做java程序网络编程&#xff0c;期末作业&#xff0c;老师的作业要求觉得大了吗?不知道毕业设计该怎么办?网页功能的数量是否太多?没有合适的类型或系统?等等。这里根据疫情当下&#xff0c;你想解决的问…

牛客练习赛106 三子棋

牛客练习赛106 三子棋 2022.12.02 题目描述 给定一个 333 \times 333 的棋盘&#xff0c;共有 3393 \times 3 9339 个格子&#xff0c;初始时每个格子均没有放置棋子。 A 和 B 轮流行动&#xff0c;每次行动的人&#xff0c;必须在当前棋盘上选择一个没有放置棋子的格子…

基于PHP+MySQL学生成绩管理系统的设计与实现

基于PHP的学生成绩管理系统主要高校内部提供服务,系统分为管理员,教师用户和学生用户三部分。 在基于PHP的学生成绩管理系统中分为管理员用户,教师用户和学生用户三部分,其中管理员用户主要是用来管理教师信息,学生信息,课程信息,专业信息和班级信息等内容,教师用户主要是用来管…

业务流程管理的未来趋势:个性化定制

自进入互联网时代以来、甚至更早&#xff0c;无论是从物质还是精神层面&#xff0c;“个性化”已经成为大众所普遍追求的东西。个性化定制允许买家按照自身偏好对产品进行二次改造&#xff0c;例如许多汽车品牌&#xff0c;就可以根据车主的想法来实现定制。 其实&#xff0c;…

Spring Boot 集成 EasyExcel 3.x 优雅实现Excel导入导出

Spring Boot 集成 EasyExcel 3.x 本章节将介绍 Spring Boot 集成 EasyExcel&#xff08;优雅实现Excel导入导出&#xff09;。 &#x1f916; Spring Boot 2.x 实践案例&#xff08;代码仓库&#xff09; 介绍 EasyExcel 是一个基于 Java 的、快速、简洁、解决大文件内存溢出的…

CSS盒子模型(上)

&#x1f353;个人主页&#xff1a;bit.. &#x1f352;系列专栏&#xff1a;Linux(Ubuntu)入门必看 C语言刷题 数据结构与算法 HTML和CSS3 目录 1.盒子模型 1.2盒子模型&#xff08;Box Model&#xff09;组成 1.3边框&#xff08;border&#xff09; 1.4 表格的…

这才是Git的正确学习方式

程序员宝藏库&#xff1a;https://gitee.com/sharetech_lee/CS-Books-Store 你想要的&#xff0c;这里都有&#xff01; 我认为学习一门知识最怕的就是一知半解、草草了事&#xff0c;对于Git这种工具类更是如此。 有很多同学工作后&#xff0c;日常用到git clone、git add、g…

ubuntu18.04下cmake的安装

一.使用安装命令 sudo apt install cmake这种方式安装最为简单&#xff0c;但是&#xff0c;这种方式安装的不是最新版本的Cmake。 我此次安装cmake是因为要编译fastdds&#xff0c;其实之前系统中有cmake&#xff0c;但是在编译fastdds的过程中依然提示我&#xff1a; CMake…

第二证券|千亿巨头飙涨,消费板块掀起涨停潮!

消费板块复苏可期。 外围股市团体大反弹&#xff0c;隔夜纳斯达克指数大涨超4&#xff05;&#xff0c;标普500指数涨超3&#xff05;&#xff0c;道琼斯指数涨逾2%。跟着近两个月来的持续反弹&#xff0c;道琼斯指数自阶段底已反弹超20%&#xff0c;进入技术性牛市。早盘A股同…

留言墙项目【Vue3 + nodejs + express + mysql】——上

创建项目 如何使用 mddir 命令生成目录结构树 规范文件目录 ## 默认目录 |-- undefined|-- .gitignore|-- babel.config.js|-- jsconfig.json|-- package.json|-- README.md|-- vue.config.js|-- yarn.lock|-- 开发文档.md|-- public| |-- favicon.ico| |-- index.html|-…

[激光原理与应用-29]:典型激光器 -1- 固体激光器

目录 第1章 什么是固体激光器 1.1 什么是固体激光器 1.2 固体激光器特点 1.3 特性 1.4 分类 1.5 波长 第2章 固体激光器的组成 2.1 固体工作物质 2.2 激励源 第1章 什么是固体激光器 1.1 什么是固体激光器 用固体激光材料作为工作介质的激光器。 固体激光材料是在作…

老杨说运维 | 想转型的请注意!这几点不容忽视

随着各行各业数字化转型的持续推进&#xff0c;以及信息化建设的不断深入&#xff0c;IT系统规模及复杂程度日趋增长。据IDC预测&#xff0c;2021年中国金融行业IT支出规模&#xff08;包括&#xff1a;软件、硬件、IT服务等&#xff09;达到2186.02亿元&#xff0c;到2025年将…

Go-Excelize API源码阅读(三十九)——SetCellHyperLink

Go-Excelize API源码阅读&#xff08;三十九&#xff09;——SetCellHyperLink 开源摘星计划&#xff08;WeOpen Star&#xff09; 是由腾源会 2022 年推出的全新项目&#xff0c;旨在为开源人提供成长激励&#xff0c;为开源项目提供成长支持&#xff0c;助力开发者更好地了解…

Mysql存储过程和游标的一点理解

最近学习数据库语言sql&#xff0c;学到了存储过程和游标这一块&#xff0c;上课一点没听&#xff0c;可以说是全程懵逼。不过好在有个课后的实验&#xff0c;然而cmd中的报错往往极其粗糙&#xff0c;只会告诉你什么附近有错&#xff08;有时候还是错的&#xff09;&#xff0…

大一新生HTML期末作业 个人旅游图片博客HTML5 用DIV+CSS技术设计的个人网站(web前端网页制作课作业)

&#x1f389;精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业…

Centos7安装部署openLDAP并springboot集成openLDAP

这里安装部署都是基于docker的&#xff0c;供参考 安装docker 1、yum list docker 2、yum install -y yum-utils device-mapper-persistent-data lvm2 3、yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo 4、yum install do…

斐波那契数列的矩阵乘法方法

1、求斐波那契数列矩阵乘法的方法 1.1 斐波那契数列的线性求解&#xff08;O(n)O(n)O(n)&#xff09;的方法 //斐波那契数列&#xff1a;1 1 2 3 5 8 ... int fibonacci(int n) {if (n < 1) return 0;if (n 1 || n 2) return 1;int a 1, b 1, c 0;for (int i 3; i &…

K_A08_002 基于 STM32等单片机驱动MAX1508模块按键控制直流电机正反转加减速启停

目录 一、资源说明 二、基本参数 1、参数 2、引脚说明 3、驱动说明 MAX1508模块驱动时序 对应程序: PWM信号 四、部分代码说明 接线说明 1、STC89C52RCMAX1508模块 2、STM32F103C8T6MAX1508模块 五、基础知识学习与相关资料下载 六、视频效果展示与程序资料获取 七、项目…

[附源码]计算机毕业设计springboot校园生活服务平台

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

PowerBI工作区连接Log Aanlytics

其实在2021.6月的时候微软已经更新了该功能&#xff0c;通过PowerBI高级容量工作区连接Log Analytics工作区&#xff0c;从而分析历史活动数据。并且在应用市场创建了一个模板应用方便分析日志数据。使用该模板可以&#xff1a; • 观察历史使用趋势 • 按照范围、容量、数据集…