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

news2025/1/12 22:57:14

文章目录

  • 前言
  • 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();

    @Override
    public 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
     */
    @Override
    public 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
     */
    @Override
    public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
        return true;
    }

    @Override
    public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) throws BeansException {
        return pvs;
    }


    /**
     * 初始化钱
     * @param bean
     * @param beanName
     * @return
     * @throws BeansException
     */
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    /**
     * 初始化后
     * @param bean
     * @param beanName
     * @return
     * @throws BeansException
     */
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Override
    public 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.coloradmin.cn/o/961362.html

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

相关文章

Standford Compiler Course Assignment 1

第一个作业是写一个词法分析的rule&#xff0c;词法分析对我帮助不大&#xff0c;主要是理解使用就可以&#xff0c;就大部分参照github上的实现了。 实验需要注意的内容&#xff1a; 1&#xff09;cool/include/PA2/cool-parse.h 里面定义了需要处理的关键字 /* Tokens. */ …

基本介绍——数据挖掘

1.数据挖掘的定义 数据挖掘是采用数学的、统计的、人工智能和神经网络等领域的科学方法&#xff0c;如记忆推理、聚类分析、关联分析、决策树、神经网络、基因算法等技术&#xff0c;从大量数据中挖掘出隐含的、先前未知的、对决策有潜在价值的关系、模式和趋势&#xff0c;并…

新款奥迪 A7L 正式上市,媒介盒子多家媒体助阵

新款奥迪 A7L 正式上市&#xff0c;媒介盒子多家媒体助阵&#xff01; 哈喽,大家好,今天媒介盒子小编又来跟大家分享媒体推广的干货知识了,本篇分享的主要内容是:新车上市,上汽奥迪A7L的营销策略。 新款奥迪 A7L 正式上市&#xff0c;新车推出 11 款车型&#xff0c;售价为 4…

探索云原生容器编排技术:如Kubernetes如何为大数据处理和AI模型的自动化部署带来便利

文章目录 1. 弹性伸缩2. 容器化3. 自动化部署4. 存储管理5. 服务发现和负载均衡6. 监控和日志记录7. 多云支持8. 多版本管理9. 安全性和隔离10. 社区支持和生态系统 &#x1f388;个人主页&#xff1a;程序员 小侯 &#x1f390;CSDN新晋作者 &#x1f389;欢迎 &#x1f44d;点…

部署java程序的服务器cpu过高如何排查和解决

1.top命令找到占用CPU高的Java进程PID 2.根据进程ID找到占用CPU高的线程 ps -mp pid -o THREAD,tid | sort -r ps -mp 124682 -o THREAD,tid | sort -r 3.将指定的线程ID输出为16进制格式 printf “%x\n” tid printf "%x\n" 6384 18f0 4.jstack pid |…

C++中基类和派生类的析构函数

和构造函数类似&#xff0c;析构函数也不能被继承。与构造函数不同的是&#xff0c;在派生类的析构函数中不用显式地调用基类的析构函数&#xff0c;因为每个类只有一个析构函数&#xff0c;编译器知道如何选择&#xff0c;无需程序员干涉。 另外析构函数的执行顺序和构造函数的…

分类算法系列④:朴素贝叶斯算法

目录 1、贝叶斯算法 2、朴素贝叶斯算法 3、先验概率和后验概率 4、⭐机器学习中的贝叶斯公式 5、文章分类中的贝叶斯 6、拉普拉斯平滑系数 6.1、介绍 6.2、公式 7、API 8、示例 8.1、分析 8.2、代码 8.3、⭐预测流程分析 &#x1f343;作者介绍&#xff1a;准大三…

Modelsim查看断言

断言编译modelsim 在modelsim中开启断言编译和显示功能&#xff1a; 【编译verilog代码时按照system verilog进行编译】 vlog -sv abc.v 或者使用通配符编译所有的.v或者.sv文件 &#xff08; vlog -sv *.sv *.v&#xff09; 【仿真命令加一个-assertdebug】 vsim -a…

华为云服务

【计算】 【存储】 对象存储服务 OBS 对象存储服务&#xff08;Object Storage Service&#xff0c;OBS&#xff09;是一个基于对象的海量存储服务&#xff0c;为客户提供海量、安全、高可靠、低成本的数据存储能力。 OBS系统和单个桶都没有总数据容量和对象/文件数量的限制…

视频文件损坏无法播放如何修复?导致视频文件损坏的原因

如果我们遇到因视频文件损坏而无法正常播放&#xff0c;我们该怎么办&#xff1f;这种情况通常意味着视频文件已经损坏。我们不能访问、编辑或使用它们。那么应该用什么正确的工具和修复程序来修复视频呢&#xff1f; 视频文件损坏的原因 了解视频损坏如何修复之前&#xff0c…

【板栗糖GIS】——360浏览器如何安装微信读书插件

目录 1. 下载插件 ​2. 解压edge插件 3. 点击360浏览器的插件管理 4. 加载已解压的扩展程序 前情提示&#xff1a;不要从任务栏打开360浏览器&#xff0c;要从桌面快捷键打开 1. 下载插件 截止到2023年9月1日&#xff0c;github提供的为0.03版本&#xff0c;下载后会得到…

AP51656 LED车灯电源驱动IC 兼容替代PT4115 PT4205 PWM和线性调光

产品描述 AP51656是一款连续电感电流导通模式的降压恒流源 用于驱动一颗或多颗串联LED 输入电压范围从 5V 到 60V&#xff0c;输出电流 可达 1.5A 。根据不同的输入电压和 外部器件&#xff0c; 可以驱动高达数十瓦的 LED。 内置功率开关&#xff0c;采用高端电流采样设置 …

ThingsKit物联网平台告警中心之告警记录

概述 当设备达到预先指定的阈值时&#xff0c;平台会自动产生告警&#xff0c;可以通过告警记录及时查看详细的告警信息以及对告警进行处理和反馈。 详情 场景联动中设备产生的告警记录详情。 :::info &#x1f4a1; 提示 告警记录状态&#xff1a;激活未确认、激活已确认、…

使用 Amazon SageMaker 的生成式 AI 定制个性化头像

生成式 AI 已经成为各行业创意过程增强和加速的常用工具,包括娱乐、广告和平面设计。它可以为观众创造更个性化的体验,并提高最终产品的整体质量。 亚马逊云科技开发者社区为开发者们提供全球的开发技术资源。这里有技术文档、开发案例、技术专栏、培训视频、活动与竞赛等。帮…

【STM32】学习笔记(串口通信)

串口通信 通信接口硬件电路电平标准USARTUSART框图 通信接口 串口是一种应用十分广泛的通讯接口&#xff0c;串口成本低、容易使用、通信线路简单&#xff0c;可实现两个设备的互相通信 单片机的串口可以使单片机与单片机、单片机与电脑、单片机与各式各样的模块互相通信&#…

liunx下ubuntu基础知识学习记录

使用乌班图 命令安装使用安装网络相关工具安装dstat抓包工具需要在Ubuntu内安装openssh-server gcc安装vim安装hello word输出1. 首先安装gcc 安装这个就可以把gcc g一起安装2. 安装VIM3.编译运行代码 解决ubuntu与主机不能粘贴复制 命令安装使用 安装网络相关工具 使用ifconf…

wandb安装方法及本地部署教程

文章目录 1 wandb介绍2 wandb安装2.1 注册wandb账号2.2 创建项目并获得密钥2.3 安装wandb并登录 3 wandb本地部署3.1 设置wandb运行模式3.2 云端查看运行数据 4 总结 1 wandb介绍 Wandb&#xff08;Weights & Biases&#xff09;是一个用于跟踪、可视化和协作机器学习实验…

9.GPIO子系统

目录 GPIO1节点 内核设备树新增rgb_led节点&#xff08;使用gpio子系统&#xff09; 常用的对外接口 头文件 of_find_node_by_path()函数 of_get_named_gpio()函数 gpio_request()函数 gpio_free()函数 gpio_direction_output()函数 gpio_direction_input()函数 gpi…

Gradio Auth登录页设置中文

最近入坑了gradio, 就是一款python框架&#xff0c;可以通过简单的几行代码&#xff0c;就能够帮我们构建一个简易的页面网站&#xff0c;并且可以在里面做相应的逻辑处理。目前该框架在AI领域相对比较火爆&#xff0c;用于给自己的大模型构建操作页面。 官网地址&#xff1a;…

离散数据编码方式总结(OneHotEncoder、LabelEncoder、OrdinalEncoder、get_dummies、DictVector

写在前面 在机器学习的特征选择的时候&#xff0c;往往有一些离散的特征不好计算&#xff0c;此时需要对这些特征进行编码&#xff0c;但是编码方式有很多&#xff0c;不同的包也会有不同的编码方式。&#xff08;明白OneHotEncoder、LabelEncoder、OrdinalEncoder、get_dummi…