手写OpenFeign(简易版)

Remoting组件实现

  • 1. 前言
  • 2. 原理说明
  • 3. 远程调用组件实现---自定义注解
    • 3.1 添加Spring依赖
    • 3.2 编写@EnableRemoting注解
    • 3.3 编写@RemoteClient注解
    • 3.4 编写@GetMapping注解
  • 4. 远程调用组件实现---生成代理类
    • 4.1 编写自定义BeanDefinition注册器
    • 4.2 编写自定义包扫描器
    • 4.3 编写FactoryBean
    • 4.4 编写远程调用接口的默认实现
    • 4.5 http调用工具类
  • 5. 成果展现

1. 前言

今日有一个需求中需要调用其他服务的接口,由于该项目并非SpringCloud项目,所以先使用OkHttp实现远程调用,但是总觉得有点low,于是想手写一个远程调用组件,当然其实你不自己写,使用OpenFeign也是可以的。

2. 原理说明

项目启动时,扫描带有自定义注解的接口,创建动态代理,并注册到IOC容器。
其实OpenFeign的原理也是如此,只不过OpenFeign中逻辑复杂,但最核心不外乎动态代理、注册BeanDefinition

3. 远程调用组件实现—自定义注解

3.1 添加Spring依赖

新建Module:Remoting,添加以下依赖:

<dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId>
</dependency>
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId>
</dependency>
<!-- okhttp -->
<dependency><groupId>com.squareup.okhttp3</groupId><artifactId>okhttp</artifactId>
</dependency>

3.2 编写@EnableRemoting注解

此注解作用与OpenFeign的@EnableFeignClients作用一致,用于扫描我们自定义@RemoteClient注解。

com.xczs.remoting.register.RemoteClientsRegistrar;
import org.springframework.context.annotation.Import;import java.lang.annotation.*;@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(RemoteClientsRegistrar.class)
public @interface EnableRemoting {/*** Alias for the {@link #basePackages()} attribute. Allows for more concise annotation* declarations e.g.: {@code @ComponentScan("org.my.pkg")} instead of* {@code @ComponentScan(basePackages="org.my.pkg")}.* @return the array of 'basePackages'.*/String[] value() default {};/*** Base packages to scan for annotated components.* <p>* {@link #value()} is an alias for (and mutually exclusive with) this attribute.* <p>* @return the array of 'basePackages'.*/String[] basePackages() default {};}

3.3 编写@RemoteClient注解

此注解作用与OpenFeign的@FeignClient作用一致,用于标识我们需要生成代理的接口,创建代理类并注册到Spring的IOC容器。

import java.lang.annotation.*;@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface RemoteClient {String value() default "";/*** @return 绝对 URL 或可解析的主机名(协议是可选的)。*/String url() default "";}

3.4 编写@GetMapping注解

用于标识该方法是get方法。

import java.lang.annotation.*;@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface GetMapping {String value() default "";}

4. 远程调用组件实现—生成代理类

4.1 编写自定义BeanDefinition注册器

RemoteClientsRegistrar.java

import com.xczs.remoting.annotation.EnableRemoting;
import com.xczs.remoting.scanner.RemoteClientClassPathBeanDefinitionScanner;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;import java.util.Map;
import java.util.Objects;public class RemoteClientsRegistrar implements ImportBeanDefinitionRegistrar {@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {Map<String, Object> attributes = importingClassMetadata.getAnnotationAttributes(EnableRemoting.class.getName());AnnotationAttributes annotationAttributes = AnnotationAttributes.fromMap(attributes);String[] basePackages = null;if (Objects.nonNull(annotationAttributes.getStringArray("value"))) {basePackages = annotationAttributes.getStringArray("value");}if (Objects.isNull(basePackages) || basePackages.length == 0) {basePackages = (String[])attributes.get("basePackages");}if (Objects.isNull(basePackages) || basePackages.length == 0) {throw new IllegalArgumentException("@EnableRemoting注解value或basePackages属性不可同时为空.");}RemoteClientClassPathBeanDefinitionScanner scanner = new RemoteClientClassPathBeanDefinitionScanner(registry, false);scanner.doScan(basePackages);}
}

4.2 编写自定义包扫描器

RemoteClientClassPathBeanDefinitionScanner.java

import com.xczs.remoting.annotation.RemoteClient;
import com.xczs.remoting.bean.RemoteClientFactoryBean;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.util.Assert;import java.util.Set;@Slf4j
public class RemoteClientClassPathBeanDefinitionScanner extends ClassPathBeanDefinitionScanner {public RemoteClientClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters) {super(registry, useDefaultFilters);}@Overridepublic Set<BeanDefinitionHolder> doScan(String... basePackages) {// 添加过滤器, 只扫描添加了 RemoteClient 注解的类addIncludeFilter(new AnnotationTypeFilter(RemoteClient.class));Set<BeanDefinitionHolder> beanDefinitionHolderSet = super.doScan(basePackages);// 对扫描到的数据进行代理处理processBeanDefinitions(beanDefinitionHolderSet);return beanDefinitionHolderSet;}@Overrideprotected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();}private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitionHolderSet) {beanDefinitionHolderSet.forEach(beanDefinitionHolder -> {BeanDefinition beanDefinition = beanDefinitionHolder.getBeanDefinition();if (beanDefinition instanceof AnnotatedBeanDefinition) {AnnotationMetadata annotationMetadata = ((AnnotatedBeanDefinition) beanDefinition).getMetadata();Assert.isTrue(annotationMetadata.isInterface(), "@RemoteClient只能使用在接口上");}// 设置工厂等操作需要基于GenericBeanDefinition, BeanDefinitionHolder是其子类GenericBeanDefinition definition = (GenericBeanDefinition) beanDefinition;// 获取接口的全路径名称String beanClassName = definition.getBeanClassName();// 设置构造函数参数definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);// 设置工厂definition.setBeanClass(RemoteClientFactoryBean.class);definition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_TYPE);});}
}

4.3 编写FactoryBean

RemoteClientFactoryBean.java

import org.springframework.beans.factory.FactoryBean;import java.lang.reflect.Proxy;public class RemoteClientFactoryBean<T> implements FactoryBean<T> {private Class<T> type;public RemoteClientFactoryBean(Class<T> type) {this.type = type;}@Overridepublic T getObject() throws Exception {// 因为 DefaultRemoteClient 需要Class<T>作为参数, 所以该类包含一个Class<T>的成员, 通过构造函数初始化return (T) Proxy.newProxyInstance(type.getClassLoader(), new Class[]{type},new DefaultRemoteClient<>(type));}@Overridepublic Class<?> getObjectType() {// 该方法返回的getObject()方法返回对象的类型,这里是基于type生成的代理对象, 所以类型就是上面定义的typereturn type;}
}

4.4 编写远程调用接口的默认实现

DefaultRemoteClient.java

import com.xczs.core.utils.OkHttpUtils;
import com.xczs.remoting.annotation.GetMapping;
import com.xczs.remoting.annotation.RemoteClient;
import lombok.extern.slf4j.Slf4j;
import okhttp3.Response;import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;@Slf4j
public class DefaultRemoteClient<T> implements InvocationHandler {/*** 这里声明一个Class, 用来接收接口声明的泛型实际类型的class, T是声明的实体类类型*/private Class<T> type;public DefaultRemoteClient(Class<T> type) {this.type = type;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// Object 方法,走原生方法, 比如 hashCode()if (Object.class.equals(method.getDeclaringClass())) {return method.invoke(this, args);}// 其它走动态代理Class<?> declaringClass = method.getDeclaringClass();RemoteClient remoteClient = declaringClass.getAnnotation(RemoteClient.class);String domain = remoteClient.url();Annotation[] annotations = method.getAnnotations();for (Annotation annotation : annotations) {if (annotation instanceof GetMapping) {GetMapping getAnno = (GetMapping) annotation;String uri = getAnno.value();String url = domain + uri;Response response = OkHttpUtils.doExecuteGet(url);String resp = response.body().string();return resp;}}return null;}
}

4.5 http调用工具类

OkHttpUtils工具类

import com.xczs.core.exception.RPCException;
import com.xczs.core.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;import java.io.IOException;
import java.util.Objects;
import java.util.concurrent.TimeUnit;@Slf4j
public class OkHttpUtils {private static OkHttpClient okHttpClient = null;private static final MediaType mediaType = MediaType.parse(org.springframework.http.MediaType.APPLICATION_JSON_VALUE);public static Response doExecuteGet(String url) throws IOException {return doExecuteGet(url, null);}public static Response doExecuteGet(String url, Headers headers) throws IOException {init();// 创建request对象Request.Builder builder = new Request.Builder().url(url);if (Objects.nonNull(headers)) {builder.headers(headers);}Request request = builder.build();Response response = okHttpClient.newCall(request).execute();if (!response.isSuccessful()) {log.error("OkHttpUtils.doExecuteGet:{}", response.body().string());throw new RPCException(response.code() + "", response.message());}return response;}public static Response doExecutePost(String url, Object body) throws IOException {return doExecutePost(url, null, body);}public static Response doExecutePost(String url, Headers headers, Object body) throws IOException {init();RequestBody requestBody = RequestBody.create(mediaType, JSONUtil.writeValueAsString(body));// 创建request对象Request.Builder builder = new Request.Builder().url(url).post(requestBody);if (Objects.nonNull(headers)) {builder.headers(headers);}Request request = builder.build();Response response = okHttpClient.newCall(request).execute();if (!response.isSuccessful()) {log.error("OkHttpUtils.doExecutePost:{}", response.body().string());throw new RPCException(response.message());}return response;}private static synchronized void init() {if (Objects.isNull(okHttpClient)) {okHttpClient = new OkHttpClient.Builder().connectTimeout(15, TimeUnit.SECONDS).readTimeout(60, TimeUnit.SECONDS).writeTimeout(60, TimeUnit.SECONDS).build();}}}

5. 成果展现

服务提供方:
在这里插入图片描述
远程调用接口:
在这里插入图片描述
测试类:
在这里插入图片描述
调用结果:
在这里插入图片描述

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

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

相关文章

JVM初识

什么是JVM&#xff1f; JVM全称是Java Virtual Machine&#xff0c;中文译名Java虚拟机。 JVM本质上是一个运行在计算机上的程序&#xff0c;他的职责是运行Java字节码文件。 JVM的功能 jvm的功能主要分为三部分&#xff1a; 解释和运行 对字节码文件中的指令&#xff0c;实…

vue实现导出+ 样式修改

1.安装插件 npm xlsx-style ^0.18.5 npm install xlsx -S ^0.8.13 2. 修改代码 node_modules里面找到 以下位置xlsx.js 搜索 write_ws_xml_data 替换成以下代码 function write_ws_xml_data(ws, opts, idx, wb) {var o [], r [], range safe_decode_range(ws[!ref]…

搭建储能监控云平台:实现能源管理的智能化

搭建储能监控云平台&#xff1a;实现能源管理的智能化 在全球能源变革的大背景下&#xff0c;储能技术的重要性日益凸显。储能监控云平台作为能源管理的智能解决方案&#xff0c;可以为企业提供全方位的储能系统监控与数据分析&#xff0c;提高能源利用率&#xff0c;降低能源成…

QFN封装对国产双轴半自动划片机的性能有哪些要求?

1. 高精度切割&#xff1a;QFN封装要求芯片的尺寸和形状误差要尽可能小&#xff0c;因此对国产双轴半自动划片机的切割精度提出了高要求。高精度的切割能够提高封装的良品率和稳定性。 2. 快速和稳定&#xff1a;QFN封装生产需要快速、稳定的生产过程&#xff0c;因此对国产双轴…

Jenkins 插件下载速度慢、安装失败了!我教你怎么解决!

Jenkins部署完毕&#xff0c;如果不安装插件的话&#xff0c;那它就是一个光杆司令&#xff0c;啥事也做不了&#xff01; 所以首先要登陆管理员账号然后点击系统管理再点击右边的插件管理安装CI/CD必要插件。 但是问题来了&#xff0c;jenkins下载插件速度非常慢&#xff0c…

XTuner 大模型单卡低成本微调实战

XTuner 大模型单卡低成本微调实战 Finetune简介增量预训练微调指令跟随微调LoRA XTuner介绍功能亮点 8GB显存玩转LLMFlash AttentionDeepSpeed ZeRO 上手操作平台激活环境微调 参考教程&#xff1a;XTuner Finetune简介 LLM的下游应用任务中&#xff0c;增量预训练和指令跟随…

卓越协同,数字化运维:智能工单系统助力企业解决派单难题-亿发

不少企业的I运维部门在管理制度上存在架构混乱、分工不明、流程不透明等问题&#xff0c;导致部门内部和合作服务商之间的协作常常呈现出“踢皮球”的状态。因此&#xff0c;有效的企业运维协同管理显得尤为关键。然而&#xff0c;如果内部的协同流程设计不合理&#xff0c;过多…

软件测试|使用Python打印五子棋棋盘

简介 五子棋是我们传统的益智类游戏&#xff0c;在制作五子棋时&#xff0c;我们需要先将棋盘打印出来&#xff0c;本文就来介绍一下使用Python打印五子棋棋盘。 步骤一&#xff1a;打印空棋盘 首先&#xff0c;我们需要在Python中定义一个棋盘函数&#xff0c;该函数将打印…

DHCP自动获取实验和DNS正向解析实验

一、服务程序 1.1DHCP定义 DHCP&#xff08;动态主机配置协议&#xff09;是一个局域网的网络协议。指的是由服务器控制一段IP地址范围&#xff0c;客户机登录服务器时就可以自动获得服务器分配的IP地址和子网掩码。默认情况下&#xff0c;DHCP作为Windows Server的一个服务组…

基于JavaWeb+BS架构+SpringBoot+Vue电影订票系统系统的设计和实现

基于JavaWebBS架构SpringBootVue电影订票系统系统的设计和实现 文末获取源码Lun文目录前言主要技术系统设计功能截图订阅经典源码专栏Java项目精品实战案例《500套》 源码获取 文末获取源码 Lun文目录 1 绪 论 3 1.1研究背景和意义 3 1.2拟解决的问题及特性 3 1.3论文的结构 …

目标检测中的数据增强

整个代码参考:bubbliiiing/object-detection-augmentation。 random_data.py import cv2 import numpy as np from PIL import Image, ImageDrawdef rand(a=0, b=1):return np.random.rand()*(b-a) + adef get_random_data(annotation_line, input_shape, jitter=.3, hue=.1…

下一代 Vue3 Devtools 正式开源

什么是 Vue DevTools Vue DevTools 是一个旨在增强 Vue 开发人员体验的工具,它提供了一些功能来帮助开发者更好地了解 Vue 应用程序。 Vue DevTools:Unleash Vue Developer Experience. Enhance your Vue development journey with an amazing experience! 典型的功能特征包…

3d云渲染用什么显卡比较好?3d云渲染显卡推荐

3D云渲染能加快渲染速度&#xff0c;是众多公司的首选方案&#xff0c;作为公司负责人&#xff0c;选择哪个平台值得思考&#xff0c;今天我就说下我的选择吧。 首先我们要了解云渲染的渲染方式&#xff0c;云渲染的渲染方式分两种&#xff0c;一种是CPU渲染&#xff0c;一种是…

linux下编译ffmpeg 以及交叉编译并引入Android

linux下编译ffmpeg 下载: http://ffmpeg.org/download.html 支持mp3编码&#xff1a;ffmpeg自身只支持mp3的解码但是不支持mp3的编码&#xff0c;如果希望格式转换为mp3&#xff0c;我们可以先安装支持库lame:(使用时: ffmpeg -i audio.wav -acodec libmp3lame audio.mp3) #…

克服大模型(LLM)部署障碍,全面理解LLM当前状态

近日&#xff0c;CMU Catalyst 团队推出了一篇关于高效 LLM 推理的综述&#xff0c;覆盖了 300 余篇相关论文&#xff0c;从 MLSys 的研究视角介绍了算法创新和系统优化两个方面的相关进展。 在人工智能&#xff08;AI&#xff09;的快速发展背景下&#xff0c;大语言模型&…

从文本文件或 csv 文件读取信息的示例

如下表格说明文本文件或 csv 文件中的信息如何在 WinCC (TIA Portal) 中显示。 IO 域用作于显示&#xff0c;只有最有一个条目被输出。 注意 在此例中由于最后一条条目被搜索&#xff0c;脚本的运行系统会随着文件的尺寸增长而增长。先前示例中的配置在该示例中不是必须的。但是…

手把手教你使用Django如何连接Mysql

目录 一、引言 二、准备工作 三、配置Django连接MySQL 1、安装MySQL驱动&#xff1a; 2、配置数据库设置&#xff1a; 3、 创建数据库迁移&#xff1a; 四、编写Django模型和视图函数 1、编写模型&#xff1a; 2. 编写视图函数&#xff1a; 3. 编写模板&#xff1a; …

开关电源如何覆铜

开关电源如何覆铜 开关电源覆铜是一个很重要的技术方法&#xff0c;如果没有很好的覆铜&#xff0c;就有可能会造成开关电源芯片的损坏。先介绍常见的开关电源电路&#xff1a; 图 1开关电源电路 从左到右分别是非同步整流Buck电路和同步整流Buck电路&#xff0c;第二排从左到…

MIinW-W64交叉编译找不到‘mutex‘问题解决

问题 在linux下安装mingw-w64来交叉编译Windows的程序和库. 就像我之前的一篇博客提到的来进行mingw的交叉编译 这样默认安装的线程模型是win32模型.这个线程模型不支持mutex. 一般查找问题的过程: 线程模型通常包含互斥锁&#xff08;mutex&#xff09;作为线程同步的基本工…

Arduino快速上手esp8266方案开发

认识ESP8266 ESP8266 是 Espressif Systems 生产的 Wi-Fi 片上系统 (SoC)。它非常适合物联网和家庭自动化项目&#xff0c;目前有非常高的市场普及率&#xff0c;还有更加高端的同时支持wifi和蓝牙的双核心芯片ESP32&#xff0c;可以在乐鑫官网查看完整的芯片列表。 ESP8266芯…