DangerWind-RPC-framework---四、SPI

        SPI 即 Service Provider Interface ,可以理解为专门提供给服务提供者或者扩展框架功能的开发者去使用的一个接口。SPI 将服务接口和具体的服务实现分离开来,将服务调用方和服务实现者解耦,能够提升程序的扩展性、可维护性。修改或者替换服务实现并不需要修改调用方。很多框架都使用了 Java 的 SPI 机制,比如:Spring 框架、数据库加载驱动、日志接口、以及 Dubbo 的扩展实现等等。参考Dubbo的SPI机制,来实现本RPC框架的SPI部分。

        举个例子,client端在与server端进行通信时,需要对消息进行序列化。序列化时可以使用序列化算法有很多,包括Hessian、Kryo、ProtoStuff。系统的需求是根据消息中的序列化算法名称来调用相关序列化算法对应的类中的方法来进行序列化与反序列化,加之为了便于扩展,需要使用SPI来进行解耦。

        SPI的使用方式如下:

Serializer serializer = ExtensionLoader.getExtensionLoader(Serializer.class).getExtension(codecName);

        codecName是序列化算法名称,需要根据该名称加载出对应的类。

    private final Class<?> type;private ExtensionLoader(Class<?> type) {this.type = type;}// 每个SPI接口都有自身的ExtensionLoaderpublic static <S> ExtensionLoader<S> getExtensionLoader(Class<S> type) {if (type == null) {throw new IllegalArgumentException("Extension type should not be null.");}if (!type.isInterface()) {throw new IllegalArgumentException("Extension type must be an interface.");}if (type.getAnnotation(SPI.class) == null) {throw new IllegalArgumentException("Extension type must be annotated by @SPI");}// firstly get from cache, if not hit, create oneExtensionLoader<S> extensionLoader = (ExtensionLoader<S>) EXTENSION_LOADERS.get(type);if (extensionLoader == null) {EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<S>(type));extensionLoader = (ExtensionLoader<S>) EXTENSION_LOADERS.get(type);}return extensionLoader;}

        每个SPI接口都有自身的ExtensionLoader,调用getExtensionLoader时,首先会进行一系列的合法检查操作,之后会尝试获取该接口的ExtensionLoader,先尝试本地缓存CHM中获取,获取不到的话再创建Loader对象。

        之后通过getExtension获取实例,实例也进行了本地缓存,缓存中没有的话再创建实例。

    private final Map<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();public T getExtension(String name) {if (StringUtil.isBlank(name)) {throw new IllegalArgumentException("Extension name should not be null or empty.");}// firstly get from cache, if not hit, create one// 缓存holderHolder<Object> holder = cachedInstances.get(name);if (holder == null) {cachedInstances.putIfAbsent(name, new Holder<>());holder = cachedInstances.get(name);}// create a singleton if no instance exists// holder为空,双重检查锁创建示例Object instance = holder.get();if (instance == null) {synchronized (holder) {instance = holder.get();if (instance == null) {instance = createExtension(name);holder.set(instance);}}}return (T) instance;}

          获取到类的Class对象后可以通过反射的方式创建此对象。 

   // 缓存   private static final Map<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>();private T createExtension(String name) {// load all extension classes of type T from file and get specific one by name// SPI接口对应的实现类,其标识名与class文件的映射,根据标识名获取classClass<?> clazz = getExtensionClasses().get(name);if (clazz == null) {throw new RuntimeException("No such extension of name " + name);}T instance = (T) EXTENSION_INSTANCES.get(clazz);if (instance == null) {try {// 缓存中不存在,则创建实例EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());instance = (T) EXTENSION_INSTANCES.get(clazz);} catch (Exception e) {log.error(e.getMessage());}}return instance;}

         关键是获取Class对象的过程 ,即getExtensionCalsses方法:

    // 该SPI接口所有实现类的标识与其Class对象的缓存private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>(); private static final String SERVICE_DIRECTORY = "META-INF/extensions/";  private Map<String, Class<?>> getExtensionClasses() {// get the loaded extension class from the cache// 根据Interface实现类的类名获取对应类的缓存Map<String, Class<?>> classes = cachedClasses.get();// double checkif (classes == null) {synchronized (cachedClasses) {classes = cachedClasses.get();if (classes == null) {classes = new HashMap<>();// load all extensions from our extensions directoryloadDirectory(classes);// 将Map集合存储在Holder中进行缓存cachedClasses.set(classes);}}}return classes;}private void loadDirectory(Map<String, Class<?>> extensionClasses) {// 固定路径下的文件,SPI接口的类名作为文件名,在此文件中规定需要加载的实现类String fileName = ExtensionLoader.SERVICE_DIRECTORY + type.getName();try {Enumeration<URL> urls;// 系统类加载器,它能够加载用户类路径(ClassPath)上的类和资源。对于SPI机制尤为重要,因为SPI的实现类通常是由应用程序提供并放置在应用程序的类路径下的ClassLoader classLoader = ExtensionLoader.class.getClassLoader();// 获取当前类加载器加载的URL资源,文件名确定一般urls是唯一的urls = classLoader.getResources(fileName);if (urls != null) {while (urls.hasMoreElements()) {URL resourceUrl = urls.nextElement();// 使用classLoader加载资源,资源目标在resourceUrl下,加载后的class存储在extensionClasses Map集合当中loadResource(extensionClasses, classLoader, resourceUrl);}}} catch (IOException e) {log.error(e.getMessage());}}private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, URL resourceUrl) {try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceUrl.openStream(), UTF_8))) {String line;// read every line// #是注释,截取注释之前的部分while ((line = reader.readLine()) != null) {// get index of commentfinal int ci = line.indexOf('#');if (ci >= 0) {// string after # is comment so we ignore itline = line.substring(0, ci);}line = line.trim();if (line.length() > 0) {try {final int ei = line.indexOf('=');// 标识与类名String name = line.substring(0, ei).trim();String clazzName = line.substring(ei + 1).trim();// our SPI use key-value pair so both of them must not be emptyif (name.length() > 0 && clazzName.length() > 0) {// 加载类Class<?> clazz = classLoader.loadClass(clazzName);// 在map中保存extensionClasses.put(name, clazz);}} catch (ClassNotFoundException e) {log.error(e.getMessage());}}}} catch (IOException e) {log.error(e.getMessage());}}
kyro=github.javaguide.serialize.kyro.KryoSerializer
protostuff=github.javaguide.serialize.protostuff.ProtostuffSerializer
hessian=github.javaguide.serialize.hessian.HessianSerializer

       逐层的方法调用,实现了加载META-INF/extensions/路径下对应SPI配置文件从而加载Class对象并获取实例的过程,重要部分可参考注释。

       需要注意META-INF/extensions/下的文件名需要与代码里一致,代码里指定的文件名是SPI接口类的全类名。文件里的内容也需要按照(实现类标识=实现类全类名)来编写,这样才能与代码一致,程序才可以正确解析文件,并使用类加载器加载对应的Class。最后按照<实现类标识,实现类Class对象>进行缓存。

       由于(类标识,Class对象)、(Class对象,对象实例)、(类标识,对象实例)这三个缓存的存在,后续可以直接传入标识获取到对应类的实例,也优化了RPC框架的性能。

 

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

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

相关文章

etcd 实现分布式锁

10 基于 Etcd 的分布式锁实现原理及方案

如何通过兔子和窝窝的故事理解“在机器人学习和研究中的获得成本与维护成本”(节选)

获得成本 掌握一门课程&#xff0c;以最为简单的学校成绩过60为例&#xff0c;需要按要求提交材料&#xff0c;包括作业、报告、实验和考试等&#xff0c;依据学分和考核要求的不同&#xff0c;需要对于花费时间和经历进行完成。 维护成本 考完了&#xff0c;如果被动学习那…

docker拉取镜像-配置阿里云镜像加速

1、配置阿里云镜像&#xff08;用于拉取镜像加速&#xff09; sudo mkdir -p /etc/docker sudo tee /etc/docker/daemon.json <<-EOF {"registry-mirrors": ["https://xxxxxxxx.mirror.aliyuncs.com"] } EOF sudo systemctl daemon-reload sudo syst…

Docker 使用基础(4)—存储卷

&#x1f3ac;慕斯主页&#xff1a;修仙—别有洞天 ♈️今日夜电波&#xff1a;秒針を噛む—ずっと真夜中でいいのに。 0:34━━━━━━️&#x1f49f;──────── 4:20 &#x1f504; ◀️ ⏸ …

JVM堆内存的结构,YGC,FGC的原理

JVM堆内存结构&#xff1a; JVM堆内存可分为三个区域&#xff1a;新生代&#xff08;Young Generation&#xff09;、年老代&#xff08;Tenured Generation&#xff0c;也叫做Old Generation&#xff09;和永久代&#xff08;Permanent Generation&#xff0c;也叫做Method Ar…

linux 设置nginx开机自启

1、关闭当前nginx运行 systemctl stop nginx 2、添加以下内容到nginx.service文件&#xff0c;注意nginx.pid文件的路径&#xff0c;要替换哦&#xff01; vim /etc/systemd/system/nginx.service [Unit] DescriptionThe NGINX HTTP and reverse proxy server Afternetwork…

ArcGIS如何快速对齐两个图层

1、问题 如何让两个图层快速对齐 2、使用捕捉工具 移动点或折点&#xff0c;使其与其他要素的折点、边或端点精确重合。 可指定捕捉规则来控制是将输入折点捕捉到指定距离范围内的最近折点、边还是端点。

MySQL数字相关数据处理函数

目录 1. 随机数生成 rand ( ) 2. 四舍五入 round&#xff08;&#xff09; 3. 舍去 truncate ( ) 4. 向上/下取整 5. 空处理 ifnull&#xff08; x , y &#xff09; 1. 随机数生成 rand ( ) rand ( ) 生成 0 到 1 的随机数&#xff1b; rand ( x ) 生成 0 到 1 的随机数…

简单理解Lua 协程(coroutine)

也许更好的阅读体验 协程简单理解为可以暂停的线程&#xff0c;但是同一时刻只有一个协程可以处于运行状态。 文章目录 coroutine.create()coroutine.resume()coroutine.wrap()coroutine.yield()coroutine.resume()参数传递resume和yield之间互换数据 coroutine.create() lua…

403 禁止错误: 它是什么?如何修复?

您应该对403错误代码很熟悉&#xff01;这种错误会导致流量损失&#xff0c;甚至错失一些商业机会&#xff01; 什么&#xff1f;您在自己的网站上遇到了403错误&#xff1f;请立即修复它&#xff01;但是什么原因导致这种错误&#xff1f;该如何解决&#xff1f;这两个问题都…

66种智能优化算法和改进优化算法优化BP神经网络【开源代码!】【文末福利IT学习资料】

前言 熟话说得好&#xff0c;创新点不够&#xff0c;智能优化算法来凑&#xff0c;不要觉得羞耻&#xff0c;因为不仅我们这么干&#xff0c;很多外国人也这么干&#xff01;因为创新点实在太难想了&#xff0c;和优化算法结合下是最简单的创新点了&#xff01; 之前给大家分享…

485通讯抗干扰,超时重发,不断重连的程序架构

485通讯抗干扰,超时重发,不断重连的编程思路 在工程中会遇到一种情况,当通信受到干扰之后,数据超时重发多次,无法被成功发出去,当恢复干扰后,之前发送的指令就被报错清掉了,相当于串口掉线之后,即使短暂时间内通信连上,掉线之后发出的指令也不生效。 为了确保受到干…

OFDM符号周期

OFDM符号周期的确定 OFDM符号周期的确定是一个复杂的过程&#xff0c;需要考虑多个因素。以下是主要的考虑因素和确定步骤&#xff1a; 主要考虑因素 信道特性 多径延迟扩展相干时间 系统要求 数据速率频谱效率 硬件限制 采样率计算复杂度 应用场景 移动性要求覆盖范围 …

spark shuffle写操作——SortShuffleWriter

写入的简单流程&#xff1a; 1.生成ExternalSorter对象 2.将消息都是插入ExternalSorter对象中 3.获取到mapOutputWriter&#xff0c;将中间产生的临时文件合并到一个临时文件 4.生成最后的data文件和index文件 可以看到写入的重点类是ExternalSorter对象 ExternalSorter 基…

Vant Ui 最新访问地址

Vant 4 - A lightweight, customizable Vue UI library for mobile web apps. 顺带一个顶部导航栏正常写法 先使用吸顶为0&#xff0c;然后再写nav-bar <van-sticky :offset-top"0"> <van-nav-bar class"top-title" title"村集体土地公示&q…

对为什么react需要时间分片,vue3不需要的浅学习

1、时间分片 时间分片指在让应用在cpu进行大量计算时也能与用户交互&#xff0c;但时间分片只能对大量cpu计算进行优化&#xff0c;无法优化复杂DOM操作&#xff0c;因为要确保用户正在操作的界面是最新。 web卡顿的场景&#xff1a; 1、cpu计算量不大&#xff0c;但dom操作…

人工智能算法工程师(中级)课程1-Opencv视觉处理之基本操作与代码详解

大家好&#xff0c;我是微学AI&#xff0c;今天给大家介绍一下人工智能算法工程师(中级)课程1-Opencv视觉处理之基本操作与代码详解。OpenCV&#xff08;Open Source Computer Vision Library&#xff09;是一个开源的计算机视觉和机器学习软件库。它提供了各种视觉处理函数&am…

Redis为什么变慢了?一文讲透如何排查Redis性能问题

Redis 作为优秀的内存数据库&#xff0c;其拥有非常高的性能&#xff0c;单个实例的 OPS 能够达到 10W 左右。但也正因此如此&#xff0c;当我们在使用 Redis 时&#xff0c;如果发现操作延迟变大的情况&#xff0c;就会与我们的预期不符。 你也许或多或少地&#xff0c;也遇到…

以太网中的各种帧结构

帧结构&#xff08;Ethernet Frame Structure&#xff09;介绍 以太网信号帧结构&#xff08;Ethernet Signal Frame Structure&#xff09;&#xff0c;有被称为以太网帧结构&#xff0c;一般可以分为两类 —— 数据帧和管理帧。 按照 IEEE 802.3&#xff0c;ISO/IEC8803-3 …

短视频矩阵管理系统:如何提升内容质量,帮助企业获客?

在数字化营销蓬勃发展的今天&#xff0c;短视频已成为企业推广的重要阵地。然而&#xff0c;如何高效管理短视频内容&#xff0c;提升内容质量&#xff0c;进而帮助企业精准获客&#xff0c;成为企业亟待解决的问题。短视频矩阵管理系统应运而生&#xff0c;以其强大的功能和灵…