手写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调用工具类
    • 4.6 启动类上添加@EnableRemoting注解
  • 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 {

    @Override
    public 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);
    }

    @Override
    public Set<BeanDefinitionHolder> doScan(String... basePackages) {
        // 添加过滤器, 只扫描添加了 RemoteClient 注解的类
        addIncludeFilter(new AnnotationTypeFilter(RemoteClient.class));
        Set<BeanDefinitionHolder> beanDefinitionHolderSet = super.doScan(basePackages);
        // 对扫描到的数据进行代理处理
        processBeanDefinitions(beanDefinitionHolderSet);
        return beanDefinitionHolderSet;
    }

    @Override
    protected 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;
    }

    @Override
    public T getObject() throws Exception {
        // 因为 DefaultRemoteClient 需要Class<T>作为参数, 所以该类包含一个Class<T>的成员, 通过构造函数初始化
        return (T) Proxy.newProxyInstance(type.getClassLoader(), new Class[]{type},
                new DefaultRemoteClient<>(type));
    }

    @Override
    public Class<?> getObjectType() {
        // 该方法返回的getObject()方法返回对象的类型,这里是基于type生成的代理对象, 所以类型就是上面定义的type
        return 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;
    }

    @Override
    public 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 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();
        }
    }

}

4.6 启动类上添加@EnableRemoting注解

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@EnableRemoting(basePackages = {"com"})
public class TestApplication {

    public static void main(String[] args) {
       SpringApplication.run(TestApplication.class, args);
    }

}

5. 成果展现

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

web开发学习笔记(2.js)

1.引入 2.js的两种引入方式 3.输出语句 4.全等运算符 5.定义函数 6.数组 7.数组属性 8.字符串对象的对应方法 9.自定义对象 10.json对象 11.bom属性 12.window属性 13.定时刷新时间 14.跳转网址 15.DOM文档对象模型 16.获取DOM对象&#xff0c;根据DOM对象来操作网页 如下图…

【软件测试】学习笔记-统一测试数据平台

这篇文章主要探讨全球大型电商企业中关于准备测试数据的最佳实践&#xff0c;从全球大型电商企业早期的测试数据准备实践谈起&#xff0c;分析这些测试数据准备方法在落地时遇到的问题&#xff0c;以及如何在实践中解决这些问题。其实&#xff0c;这种分析问题、解决问题的思路…

yum仓库以及NFS共享

yum实现过程 1.光驱里自带yum 2.网络下载到本地 3.直接通过网络 如何实现安装服务 yum客户端找到yum服务端&#xff0c;找到yum的仓库位置&#xff0c;下载元信息&#xff0c;因为里面有软件的位置&#xff0c;因此可以找到软件包的位置&#xff0c;然后下载到本地 仓库的类…

UI设计中插画赏析和产品色彩分析

插画赏析&#xff1a; 1. 插画是设计的原创性和艺术性的基础 无论是印刷品、品牌设计还是UI界面&#xff0c;更加风格化的插画能够将不同的风格和创意加入其中&#xff0c;在激烈的竞争中更容易因此脱颖而出。留下用户才有转化。 2. 插画是视觉触发器&#xff0c;瞬间传达大量…

OpenHarmony ArkUI ETS- 装饰器解读

前言 最近利用空闲时间在学习华为方舟开发框架&#xff08;简称&#xff1a;ArkUI&#xff09;的ets开发&#xff0c;发现在ets语言中装饰器的有着非常重要的作用&#xff0c;在写每一个自定义组件时都需要用到它&#xff0c;看到装饰器这个字眼&#xff0c;想起之前学过的设计…

模板方法模式介绍

目录 一、模板方法模式介绍 1.1 模板方法模式的定义 1.2 模板方法模式的原理 1.2.1 模板方法模式类图 1.2.2 类图角色说明 1.2.3 示例代码 二、模板方法模式的应用 2.1 需求说明 2.2 需求实现 2.2.1 账户抽象类 2.2.2 借款一个月 2.2.3 借款7天 2.2.4 测试类 三、…

kibana查看和展示es数据

本文来说下使用kibana查看和展示es数据 文章目录 数据准备查询所有文档示例kibana查看和展示es数据 数据准备 可以使用es的命令或者java程序来往&#xff0c;es进行新增数据 查询所有文档示例 在 apifox 中&#xff0c;向 ES 服务器发 GET请求 &#xff1a;http://localhost:92…

Unity之触发器

目录 &#x1f4d5;一、触发器概念 &#x1f4d5;二、碰撞与触发的区别 &#x1f4d5;三、触发器小实例 一、触发器概念 第一次玩侠盗猎车手是在小学&#xff0c;从那以后就开启了我的五星好市民之路。 下面是小编在小破站截的图&#xff0c;这是罪恶都市最开始的地方&a…

AI工具(20240116):Copilot Pro,Fitten Code等

Copilot Pro Copilot Pro是微软推出的Copilot的付费增强版本,通过提供优先访问GPT-4等最新AI模型,大大提升用户的创造力和工作效率。该服务可与Microsoft 365订阅捆绑使用,支持在Word、Excel等Office应用内直接使用Copilot功能,帮助用户更快速地起草文档、电子邮件和演示文稿等…

Kafka-消费者-KafkaConsumer分析-ConsumerCoordinator

在前面介绍了Kafka中Rebalance操作的相关方案和原理。 在KafkaConsumer中通过ConsumerCoordinator组件实现与服务端的GroupCoordinator的交互&#xff0c;ConsumerCoordinator继承了AbstractCoordinator抽象类。 下面我们先来介绍AbstractCoordinator的核心字段&#xff0c;如…

Linux下安装jdk、tomcat

linux下安装jdk、tomcat 一、linux下安装jdk1.1.下载Linux版本的JDK1.2.Linux安装JDk1.3.设置环境变量1.4.卸载JDK 二、linux下安装tomcat2.1.下载Linux版本的Tomcat2.2.在usr目录下新建tomcat目录2.3.进入到tomcat目录中解压下载的tomcat安装包2.4.配置环境变量-前提是已经安装…

HTML--CSS--盒子模型

在CSS模型中&#xff0c;所有元素都可以看做是一个盒子&#xff0c;这个盒子的组成部分&#xff1a; content 内容&#xff0c;文本或者图片 padding 内边距&#xff0c;定义内容到边框的距离 margin 外边距&#xff0c;定义当前元素与其他元素之间的距离 border 边框&#xff…

RK3566RK3568安卓11隐藏状态栏带接口

文章目录 前言一、创建全局变量二、设置应用添加隐藏导航栏按钮三、添加按钮功能四、动态隐藏还有显示功能五、创建系统导航栏广播接口总结 前言 关于Android系统的状态栏&#xff0c;不同的客户有不同的需求: 有些客户需要永久隐藏状态栏&#xff0c;有些客户需要在设置显示中…

春节假期出游一些很实用的手机技巧!这样玩,就很哇塞~

随着春节的脚步越来越近&#xff0c;无论是准备出游还是回家&#xff0c;你蠢蠢欲动的心是否已经拦不住了&#xff1f;华为 nova 12系列这些很哇塞的玩法你必须知道&#xff01;这个新年让你旅行出圈有秘籍&#xff01; 出发前智慧播报航班信息不错过。智慧播报的功能就很实…

c语言二维数组

系列文章目录 c语言二维数组 c语言二维数组 系列文章目录一、二维数组的定义一、二维数组的内存模型 一、二维数组的定义 int main() {//二维数组的定义int arr[3][4];arr[0][0]; arr[0][1]; arr[0][2]; arr[0][3]; arr[0][4];arr[1][0]; arr[1][1]; arr[1][2]; arr[1][3]; ar…

Qt 状态机框架:The State Machine Framework (一)

传送门: Qt 状态机框架:The State Machine Framework (一) Qt 状态机框架:The State Machine Framework (二) 一、什么是状态机框架 状态机框架提供了用于创建和执行状态图/表[1]的类。这些概念和表示法基于Harel的Statecharts&#xff1a;一种复杂系统的可视化形式&#xff…

JUC之可重入锁

&#x1f4d1;前言 本文主要是【JUC】——JUC之可重入锁的文章&#xff0c;如果有什么需要改进的地方还请大佬指出⛺️ &#x1f3ac;作者简介&#xff1a;大家好&#xff0c;我是听风与他&#x1f947; ☁️博客首页&#xff1a;CSDN主页听风与他 &#x1f304;每日一句&…

.NET 8.0 发布到 IIS

如何在IIS&#xff08;Internet信息服务&#xff09;上发布ASP.NET Core 8&#xff1f; 在本文中&#xff0c;我假设您的 Windows Server IIS 上已经有一个应用程序池。 按照步骤了解在 IIS 环境下发布 ASP.NET Core 8 应用程序的技巧。 您需要设置代码以支持 IIS 并将项目配…

【Docker】在Windows操作系统安装Docker前配置环境

欢迎来到《小5讲堂》&#xff0c;大家好&#xff0c;我是全栈小5。 这是《Docker容器》序列文章&#xff0c;每篇文章将以博主理解的角度展开讲解&#xff0c; 特别是针对知识点的概念进行叙说&#xff0c;大部分文章将会对这些概念进行实际例子验证&#xff0c;以此达到加深对…

利用Lambda表达式实现vector中pair/结构体的排序

众所周知&#xff0c;对于vector<pair<int, int> >若直接使用sort排序&#xff0c;会默认按照pair的第一个关键字从小到大进行排序&#xff1a; #include <bits/stdc.h>using namespace std;int main() {vector<pair<int, int> > p;p.push_back…