手写Openfeign实现原理——极简版

文章目录

  • 前言
  • Openfeign实现思路
  • 前期准备
    • 基本依赖项
  • 开始实现
    • 自定义注解
    • 自定义代理类
    • 定义创建代理对象的工厂
    • InstantiationAwareBeanPostProcessor实现bean的注入
      • OpenInstantiationAwareBeanPostProcessor 自定义
    • feign接口
    • 启动类
    • 小结
  • 踩坑记录
    • @Import
    • @Component和@Configuration区别是什么
  • 总结

前言

最近开发cloud项目,里面涉及到服务间调用,最后使用的openfeign解决的,于是对于openfeign的底层原理有些兴趣了,提前透露一下底层无非就是使用一些http调用的工具帮助我们实现了请求调用

Openfeign实现思路

在这里插入图片描述

前期准备

基本依赖项

  • 首先创建一个springboot项目
  • 有一个发送请求的工具这里使用的ribbon
        <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-ribbon</artifactId><version>2.2.9.RELEASE</version><scope>compile</scope><optional>true</optional></dependency>

开始实现

自定义注解

创建两个自定义注解,分别用于在启动类和接口上添加

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Configuration
@Import(OpenInstantiationAwareBeanPostProcessor.class)//这个类重点注意
public @interface EnableRemoteClient {
}@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface CustomProxy {String servie();String address();
}

自定义代理类

代理类中有一个RestTemplate 用于发送请求

public class CustomProxyHandler implements InvocationHandler {RestTemplate restTemplate=new RestTemplate();@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//首先获取对象Class<?> declaringClass = method.getDeclaringClass();//判断当前的被代理对象是否使用了自定的注解if(declaringClass.isAnnotationPresent(CustomProxy.class)){CustomProxy annotation = declaringClass.getAnnotation(CustomProxy.class);String servie = annotation.servie();String address = annotation.address();String url=address+servie;RequestMapping annotation1 = method.getAnnotation(RequestMapping.class);url=url+annotation1.value()[0];//判断请求方法是否入参if(args!=null&&args.length!=0){return restTemplate.getForObject(url,method.getReturnType(),args);}else {return restTemplate.getForObject(url,method.getReturnType());}}else{return null;}}
}

定义创建代理对象的工厂

用于创建代理对象使用,

public class ProxyFactory {/*** @description: 创建具体的代理对象* @author: * @date: 2023/9/1 17:30* @param: [targetInterface]* @return: T**/public static  <T> T createProxy(Class<T> targetInterface) {return (T) Proxy.newProxyInstance(targetInterface.getClassLoader(),new Class[]{targetInterface},new CustomProxyHandler());}
}

InstantiationAwareBeanPostProcessor实现bean的注入

InstantiationAwareBeanPostProcessor 是 Spring 框架提供的一个扩展接口,它在 Spring 容器实例化 bean 之前和之后对 bean 进行处理。其主要功能如下:

  1. 实例化前置处理(Before Instantiation):在 Spring 容器实例化 bean 之前,可以通过这个接口来自定义 bean 的实例化方式。可以在此处进行一些特殊的准备工作,比如使用自定义的实例化逻辑或者切入点的选择。

  2. 实例化后置处理(After Instantiation):在 Spring 容器实例化 bean 之后,可以通过这个接口对实例化后的 bean 进行处理。可以在此处做一些初始化的操作,比如对属性进行赋值、调用初始化方法等。

  3. 属性设置前置处理(Before Property Set):在 Spring 容器对 bean 的属性进行设置之前,可以通过这个接口来自定义属性的设置方式。可以在此处对属性进行修改或者校验操作。

  4. 属性设置后置处理(After Property Set):在 Spring 容器对 bean 的属性进行设置之后,可以通过这个接口对属性设置后的 bean 进行处理。可以在此处做一些属性设置后的额外操作。

  5. 初始化前置处理(Before Initialization):在 Spring 容器对 bean 进行初始化之前,可以通过这个接口来自定义初始化的方式。可以在此处添加一些额外的初始化逻辑。

  6. 初始化后置处理(After Initialization):在 Spring 容器对 bean 进行初始化之后,可以通过这个接口对初始化后的 bean 进行处理。可以在此处做一些初始化后的额外操作。

通过实现 InstantiationAwareBeanPostProcessor 接口,并重写其中的方法,可以在 Spring 容器实例化和初始化 bean 的各个阶段进行自定义处理,从而灵活地对 bean 进行定制化的操作。

OpenInstantiationAwareBeanPostProcessor 自定义

public class OpenInstantiationAwareBeanPostProcessor implements InstantiationAwareBeanPostProcessor, ApplicationContextAware {private ApplicationContext applicationContext;/*** 实例化前,后面的方法可以不用看了* @param beanClass* @param beanName* @return* @throws BeansException*/@Overridepublic Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {log.info("正在为:{}生成代理对象,被代理的类为:{}",beanName,beanClass.getName());if(!beanClass.isAnnotationPresent(CustomProxy.class)){return null;}//动态代理里面需要实现的方法,本文采用的是jdk动态代理//返回代理对象Object object = ProxyFactory.createProxy(beanClass);return object;}/*** 实例化后* @param bean* @param beanName* @return* @throws BeansException*/@Overridepublic boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {return true;}@Overridepublic PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) throws BeansException {return pvs;}/*** 初始化钱* @param bean* @param beanName* @return* @throws BeansException*/@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {return bean;}/*** 初始化后* @param bean* @param beanName* @return* @throws BeansException*/@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {return bean;}@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {this.applicationContext=applicationContext;}
}

feign接口

这里的servie没有参数是因为我的另一个服务中没有配置context-path,如果你们自己的被调用的那个demo中有配置一定要加上哦

@CustomProxy(servie = "",address = "http://localhost:8082/")
public interface MyService {@RequestMapping(value = "/QuerySubselect",method = RequestMethod.GET)String  test();
}

启动类

@SpringBootApplication
@EnableRemoteClient
@Import({MyService.class})//接口注册,注意接口上面加@Se
public class OpenfeignDemoApplication {public static void main(String[] args) {SpringApplication.run(OpenfeignDemoApplication.class, args);}}

小结

这个版本的实现目前spring还不能识别我添加了注解的这个接口,需要在启动类中使用@Import注解手动配置,所以还不完善,不过后期会继续完善的,目前博主也在学习这个,这个思路就是基于我们正常的发送请求,然后有一个人帮助我们去发送请求这个思路。

踩坑记录

@Import

将接口 MyService 标识为 @Import 的目的是为了在启动类中将该接口的实现类(动态代理对象)注册到 Spring 容器中。

当你使用 @Import({MyService.class}) 注解时,Spring 在启动时会扫描被注解的类,并根据其类型进行相应的处理。在这种情况下,MyService 类型是一个接口,而不是一个具体的类。

由于接口无法直接实例化,因此你需要在某处提供对 MyService 接口的实现类的创建逻辑。通常情况下,使用动态代理是一种常见的方式。

动态代理是一种在运行时生成代理类的机制,可以在不修改原始接口源代码的情况下,通过代理类来增强接口的功能。你可以编写一个动态代理类,实现 MyService 接口,并在动态代理类中实现你所需的增强逻辑。

然后,在启动类上使用 @Import 注解,并传入该动态代理类的类型,告诉 Spring 在启动时需要将该动态代理类注册到容器中。

这样,当其他组件需要使用 MyService 接口时,Spring 容器会从容器中获取该接口的实例,而实际上获得的是动态代理对象,该代理对象会在实际调用接口方法时根据你的增强逻辑进行处理。

  • 因为openfeign的标注的接口没有实现类所以需要这个@Import注解帮助我们告诉spring容器可以去容器中去这个类型的代理对象,目前还没有想到其他的方式不用使用@Import注解声明的。后续再想想办法

@Component和@Configuration区别是什么

@Configuration 注解不能直接添加到接口上,因为 @Configuration 注解是用于标识一个类为配置类的注解,它主要用于定义和配置 Bean 对象以及其他的配置信息。
在这里插入图片描述
一开始使用这个注解导致使用了自定义注解的接口一直扫描不到,后来修改为@Component注解后就可以了。
@Component 是 Spring Framework 中的一个核心注解之一,用于将一个普通的 Java 类标识为一个可以被 Spring 自动扫描并管理的组件。

具体来说,@Component 注解可以应用于以下场景:

  1. Bean 的自动扫描和注册:通过在类上添加 @Component 注解,Spring 容器会自动扫描并将该类作为 Bean 进行注册,使得我们可以通过依赖注入等方式方便地使用该类的实例。

  2. 类型指定的自动装配:当需要进行自动装配时,Spring 容器会检测所有被 @Component 注解标记的类,并根据类型进行自动装配。

@Component 注解是一个通用的注解,如果对应的类有更具体的角色或作用,还可以使用其他派生注解,例如:

  • @Controller:标识一个类是控制器(Controller)组件,用于接收和处理用户请求。
  • @Service:标识一个类是服务(Service)组件,用于封装业务逻辑。
  • @Repository:标识一个类是数据访问仓库(Repository)组件,用于访问持久化数据。

这些派生注解都是基于 @Component 注解的,继承了 @Component 的功能,并且更明确地表示了对应类的角色和用途。

总结

对于这次去实现这么一个框架的小demo自己思考了一天,不能说很长时间吧,主要是因为对于spring中提供的一些扩展机制还不是很了解,但是我相信它一定提供了能够解决我上面问题的一些api的,只是我还没有发现而已。对于这些技术的应用底层原理还是很简单的,可以发现无非就是没让你做的事情有人帮你做了这么一个过程,只不过这个过程被人封装起来以后就是一个技术或者一个框架了。

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

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

相关文章

HTTP与SOCKS5的区别对比

在互联网世界中&#xff0c;服务器是一种重要的工具&#xff0c;可以帮助我们提高网络安全性等。今天&#xff0c;我们将重点关注两种常见的技术&#xff1a;HTTP和SOCKS5。让我们深入了解它们的工作原理、用途和优缺点&#xff0c;并通过Python代码示例学习如何使用它们。 HT…

一文了解tcp/ip协议的运行原理

接触代理ip的人都了解https/sock5等ip协议&#xff0c;那么TCP/IP 协议又是什么&#xff1f; 一、什么是TCP/IP 协议&#xff1f; TCP/IP 协议实际上是一系列网络通信协议的一个统称&#xff0c;他负责具体的数据传输工作&#xff0c;核心的两个协议包括TCP以及IP&#xff0c…

Unity3D 如何在ECS架构下,用Unity引擎进行游戏开发详解

前言 Unity3D是一款强大的游戏引擎&#xff0c;它提供了丰富的功能和工具&#xff0c;可以帮助开发者快速构建高质量的游戏。而Entity Component System&#xff08;ECS&#xff09;是Unity3D中一种新的架构模式&#xff0c;它可以提高游戏的性能和可扩展性。本文将详细介绍在…

Flink 如何定位反压节点?

分析&回答 Flink Web UI 自带的反压监控 —— 直接方式 Flink Web UI 的反压监控提供了 Subtask 级别的反压监控。监控的原理是通过Thread.getStackTrace() 采集在 TaskManager 上正在运行的所有线程&#xff0c;收集在缓冲区请求中阻塞的线程数&#xff08;意味着下游阻…

Redis数据结构总结

Redis 是一款开源的&#xff0c;内存中的数据结构存储系统&#xff0c;它可以用作数据库、缓存和消息代理。Redis 支持多种类型的数据结构&#xff0c;如字符串&#xff08;String&#xff09;、哈希&#xff08;Hashes&#xff09;、列表&#xff08;Lists&#xff09;、集合&…

vue3的面试题

ref里面放对象发生的事情 ref只会对对象的属性进行响应式转换&#xff0c;而不会对对象的原型链上的属性进行转换。如果需要对对象的原型链上的属性进行响应式转换&#xff0c;可以使用reactive函数。 toRefs的适用场景&#xff1f; toRefs是Vue 3中的一个响应式API&#xf…

使用环境中的视觉地标和扩展卡尔曼滤波器定位移动机器人研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

SQL高阶语句

目录 1、概念 1.1、概述 1.2、常见的MySQL高阶语句的概念&#xff1a; 1.3、 SQL高阶语句的作用 2、常用查询 2.1、按关键字排序 2.1.1、概述和作用 2.1.2、 &#xff08;1&#xff09;语法 2.1.3、模板表&#xff1a;ky30 ​编辑2.1.4、分数按降序排列 2.1.5、ORDER…

Kafka环境搭建与相关启动命令

一、Kafka环境搭建 点击下载kafka_2.11-2.3.1.tgz文件链接 1、上传kafka_2.11-2.3.1.tgz&#xff0c;解压kafka_2.11-2.3.1.tgz&#xff0c;得到kafka_2.11-2.3.1文件夹 1&#xff09;上传 #使用mobaxterm将 kafka_2.11-2.3.1.tgz 传入tools文件夹 #用下面代码进入tools文件…

SPSS统计作图教程:频率多边形

SPSS统计作图教程&#xff1a;频率多边形 1、问题与数据 某研究者想了解某数据集中最大携氧能力&#xff08;VO2max&#xff09;是否服从正态分布&#xff0c;部分数据如图1。研究者应如何绘图查看呢&#xff1f; 图1 部分数据 2、对问题的分析 研究者想绘图展示最大携氧能…

深入理解 JVM 之——Java 内存区域与溢出异常

更好的阅读体验 \huge{\color{red}{更好的阅读体验}} 更好的阅读体验 本篇为深入理解 Java 虚拟机第二章内容&#xff0c;推荐在学习前先掌握基础的 Linux 操作、编译原理、计算机组成原理等计算机基础以及扎实的 C/C 功底。 该系列的 GitHub 仓库&#xff1a;https://github…

go学习part20(1)反射

283_尚硅谷_反射基本介绍和示意图_哔哩哔哩_bilibili 1.介绍 1&#xff09;基本数据类型的类型和类别一致&#xff0c;但是结构体等不一样。 2)反射的例子&#xff08;桥连接&#xff0c;序列化&#xff09; 序列化指定tag&#xff0c;会反射生成tag字符串 3&#xff09;refl…

Vue在表格中拿到该行信息的方式(作用域插槽-#default-scope-解决按钮与行点击的顺序问题)

遇到的问题 在做表格的时候&#xff0c;表格是封装好了的&#xff0c;用于展示数据。如果想给单行增加按钮&#xff0c;可以单独写一列存放按钮&#xff0c;最基本的需求是&#xff0c;点击按钮后要拿到数据然后发起请求。 且Vue的element-plus&#xff0c;当我们点击按钮之后…

Linux挖矿程序清除

1. 找到挖矿进程 2.找到病毒的文件地址 ls -l /proc/进程ID/exe3.删除文件命令 rm -rf 文件地址4.杀死挖矿进程 kill -9 进程ID

11 mysql float/double/decimal 的数据存储

前言 这里主要是 由于之前的一个 datetime 存储的时间 导致的问题的衍生出来的探究 探究的主要内容为 int 类类型的存储, 浮点类类型的存储, char 类类型的存储, blob 类类型的存储, enum/json/set/bit 类类型的存储 本文主要 的相关内容是 float, decimal 类类型的相关数据…

【方案】基于视频与AI智能分析技术的城市轨道交通视频监控建设方案

一、背景分析 地铁作为重要的公共场所交通枢纽&#xff0c;流动性非常高、人员大量聚集&#xff0c;轨道交通需要利用视频监控系统来实现全程、全方位的安全防范&#xff0c;这也是保证地铁行车组织和安全的重要手段。调度员和车站值班员通过系统监管列车运行、客流情况、变电…

中国建筑出版传媒许少辉博士八一新书乡村振兴战略下传统村落文化旅游设计日京东当当畅销榜自由营九三学

中国建筑出版传媒许少辉博士八一新书乡村振兴战略下传统村落文化旅游设计日京东当当畅销榜自由营九三学

【JavaSE】String类

两种创建String对象的区别 String s1 "hello"; String s2 new String("hello");s1是先查看常量池是否有 “hello” 数据空间&#xff0c;如果有就直接指向它&#xff0c;如果没有就创建然后指向它。s1最终指向的是常量池的空间地址。 s2是先在堆中创建空…

MySQL数据库——多表查询(3)-自连接、联合查询、子查询

目录 自连接 查询语法 自连接演示 联合查询 查询语法 子查询 介绍 标量子查询 列子查询 行子查询 表子查询 自连接 通过前面的学习&#xff0c;我们对于连接已经有了一定的理解。而自连接&#xff0c;通俗地去理解就是自己连接自己&#xff0c;即一张表查询多次。…

visual studio编写DLL,python调用

选择第一个c DLL&#xff0c; 然后项目源文件下右击新建项&#xff0c;这里名字随便取&#xff0c;在代码中输入一下内容&#xff1a; #include <iostream>#define EXPORT extern "C" __declspec(dllexport)EXPORT int sub(int a, int b) {return a - b; } 在…