SPI 机制(Service Provider Interface)其实源自服务提供者框架(Service Provider Framework,参考【EffectiveJava】page6),是一种将服务接口与服务实现分离以达到解耦、大大提升了程序可扩展性的机制。引入服务提供者就是引入了 spi 接口的实现者,通过本地的注册发现获取到具体的实现类,轻松可插拔

在开发中,有很多地方都适用到了 java 的 spi 机制;例如:JSBC设计…等,这里我们不去研究这些示例,直接看一下他的实现方式

一、JAVA SPI

其实 javaspi 核心核心就是 ServiceLoader 这个类;我们可以通过在 resource/META-INF/services 下,放置要实例化的类实现的统一接口全路径的文件,在里面每行写上实例化类的全路径,即可使用 spi 的方式实例化,如下:

首先定义一个接口:

package com.spi.test.source;public interface Developer {void hello();
}

然后实现两个实现类:

package com.spi.test.source;public class Make1Developer implements Developer {public void hello() {System.out.println("make1 的 develop");}
}public class Make2Developer implements Developer {public void hello() {System.out.println("make2 的 develop");}
}

然后在 resource/META-INF/services 下创建 com.spi.test.source.Developer 文件内容如下:

com.spi.test.source.Make1Developer
com.spi.test.source.Make2Developer

测试类如下:

public static void main(String[] args) {ServiceLoader<Developer> serviceLoader = ServiceLoader.load(Developer.class);// 循环调用下实例的方法serviceLoader.forEach(item -> {System.out.println("正在执行的类是:" + item.getClass().toString());item.hello();});
}

二、扩展 java spi

上线是 jdk 自己携带的 SPI 功能,但是他设计的并不太好,只实现了基础的功能;另外 dubboSPI 也有自己的扩展方式,而且实现上也有了优化,

dubbo spi 对比 java spi 的优化点:

  • JDK 标准的 SPI 会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源
  • 增加了对扩展点 IoC 和 AOP 的支持,一个扩展点可以直接 setter 注入其它扩展点
  • 如果扩展点加载失败,连扩展点的名称都拿不到了。比如:JDK 标准的 ScriptEngine,通过 getName() 获取脚本类型的名称,但如果 RubyScriptEngine 因为所依赖的 jruby.jar 不存在,导致 RubyScriptEngine 类加载失败,这个失败原因被吃掉了,和 ruby 对应不起来,当用户执行 ruby 脚本时,会报不支持 ruby,而不是真正失败的原因

有兴趣可以去看一下 dubbo-common.jar 下的 org.apache.dubbo.common.extension.ExtensionLoader 类的实现;这里不在详细说明,我们这里只简单模仿 java spi 自己扩展一个 spi 的功能

首先定义一个注解,这里我们加一步校验,就是接口上必须加上注解,才可以将实现其接口的类实例化:

package com.spi.test.my.source;import java.lang.annotation.*;@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface SpiActive {}

然后自己实现一个扩展类加载器辅助类:

package com.spi.test.my.source;import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.*;import static java.nio.charset.StandardCharsets.UTF_8;public class ExpansionLoader<T> implements Iterable<T> {/*** 约定第三方实现配置文件目录**/private static final String SERVICE_DIRECTORY = "META-INF/expansions/";/*** 接口的类型,用于获取此接口下的第三方实现**/private final Class<T> type;/*** 实例列表,用于保存获取到的第三方实现的列表**/private Set<T> targets;public ExpansionLoader(Class<T> type) {this.type = type;this.targets = this.loadExtensionFile();}/*** 工厂方法,用于通过接口获取第三方实现的类加载器** @param type       接口的类型* @return         返回一个指定接口类型的类加载器辅助类**/public static <T> ExpansionLoader<T> load(Class<T> type) {if (type == null) {throw new IllegalArgumentException("Spi需要知道你想要找到哪个功能的第三方实现!");}if (!type.isInterface()) {throw new IllegalArgumentException("只支持寻找接口类型的第三方实现!");}if (type.getAnnotation(SpiActive.class) == null) {throw new IllegalArgumentException("目标接口必须被@Spi注解标注!");}return new ExpansionLoader<>(type);}private Set<T> loadExtensionFile() {String fileName = ExpansionLoader.SERVICE_DIRECTORY + this.type.getName();try {ClassLoader classLoader = ExpansionLoader.class.getClassLoader();Enumeration<URL> urls = classLoader.getResources(fileName);if (urls == null) {return Collections.emptySet();}URL resourceUrl = urls.nextElement();return loadResource(classLoader, resourceUrl);} catch (IOException e) {return Collections.emptySet();}}private Set<T> loadResource(ClassLoader classLoader, URL resourceUrl) {Set<T> set = new HashSet<>();try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceUrl.openStream(), UTF_8))) {String line;while ((line = reader.readLine()) != null) {final int ci = line.indexOf('#');if (ci == 0) {continue;} else if (ci > 0) {line = line.substring(0, ci);}Class<T> clazz = (Class<T>) classLoader.loadClass(line.trim());set.add(clazz.newInstance());}} catch (Exception e) {}return set;}@Overridepublic Iterator<T> iterator() {return this.targets.iterator();}
}

以上是工具部分;下面开始定义第三方需要实现的接口:

package com.spi.test.my;import com.spi.test.my.source.SpiActive;@SpiActive
public interface Production {void hello();
}

然后添加两个实现类:

package com.spi.test.my;public class Make1Production implements Production {@Overridepublic void hello() {System.out.println("make1 的 Production");}
}public class Make2Production implements Production {@Overridepublic void hello() {System.out.println("make2 的 Production");}
}

然后在 resource/META-INF/expansions 下创建 com.spi.test.my.Production 文件内容如下:

com.spi.test.my.Make1Production
com.spi.test.my.Make2Production

测试类如下:

public static void main(String[] args) {ExpansionLoader<Production> extensionLoader = ExpansionLoader.load(Production.class);extensionLoader.forEach(item -> {System.out.println("正在执行的类是:" + item.getClass().toString());item.hello();});
}

JAV spi 和扩展方式相关推荐

  1. SQL与NoSQL区别-扩展方式

    扩展方式是NoSQL数据库与关系型数据库差别最大的地方. 由于关系型数据库将数据存储在数据表中,数据操作的瓶颈出现在多张数据表的操作中,而且数据表越多这个问题越严重,如果要缓解这个问题,只能提高处理能 ...

  2. 为operamasks增加HTML扩展方式的组件调用

    #为operamasks增加HTML扩展方式的组件调用##背景 之前的[博文](http://www.cnblogs.com/p2227/p/3540858.html)中有提及到,发现easyui中的 ...

  3. php curl https_PHP FFI:一种全新的PHP扩展方式

    (给PHP开发者加星标,提升PHP技能) 转自:laruence/鸟哥 www.laruence.com/2020/03/11/5475.html 随着 PHP7.4 而来的有一个我认为非常有用的一个 ...

  4. 常见的数据扩展方式unsqueeze与expand的用法与区别

    常见的数据扩展方式unsqueeze与expand的用法与区别 unsqueeze以及expand的区别 unsqueeze以及expand的区别 unsqueeze可以增加一个维度,但是维度的siz ...

  5. 系统扩展方式 scale up和scale out

    什么是scale up和scale out? 许多存储系统开始很简单,但当需要进行系统扩展时就会变得复杂.升级存储系统最常见的原因是需要更多的容量,以支持更多的用户,文件,应用程序或连接的服务器. 但 ...

  6. 系统扩展方式 scale up和scale out(转载)

    什么是scale up和scale out? 许多存储系统开始很简单,但当需要进行系统扩展时就会变得复杂.升级存储系统最常见的原因是需要更多的容量,以支持更多的用户,文件,应用程序或连接的服务器. 但 ...

  7. [亲测失败] Ubuntu 双显示器扩展方式 接显示器分辨率低的解决 [拼接整合]

    Ubuntu 10.04双显示器扩展方式,笔记本外接显示器分辨率低增加没有的分辨率. 使用扩展方式,但是外接的显示器没有认出来(未知),分辨率超不过笔记本的1366x768,原本外接显示器是19寸宽屏 ...

  8. 《ArcGIS Runtime SDK for Android开发笔记》——(13)、图层扩展方式加载Google地图...

    1.前言 http://mt2.google.cn/vt/lyrs=m@225000000&hl=zh-CN&gl=cn&x=420&y=193&z=9& ...

  9. spi的dma方式前四个字节_UTF-16是固定两个字节长度吗?

    答案是否定的. Unicode 和 UTF-8 有什么区别?​www.zhihu.com 介绍两个概念,字符集和编码规则. Unicode 是「字符集」 UTF-8 是「编码规则」 字符集:为每一个「 ...

最新文章

  1. SQLite与pandas
  2. XShell连接Deepin
  3. 虚幻4皮肤材质_虚幻周报20200721 | CJ就要开始啦~
  4. CATALAN数 学习
  5. python回归方程系数计算_线性回归中的正规方程将θ系数返回为“NaN”
  6. ZOJ - 2972 Hurdles of 110m(记忆化搜索/动态规划)
  7. 程序员修神之路--略懂数据库集群读写分离而已
  8. jzoj3055-比赛【数学,统计】
  9. 关于codeforces加载慢
  10. 带有Python示例的math.exp()方法
  11. CLIP再创辉煌!西南交大MSRA提出CLIP4Clip,进行端到端的视频文本检索!
  12. [转载]了解Linux的进程与线程
  13. 安卓学习笔记17:常用控件 - 编辑框
  14. h5跳转小程序页面url_小程序和h5跳转
  15. 【论文笔记】Recover Canonical-View Faces in the Wild with Deep Neural Network
  16. Hindex--华为Hbase二级索引
  17. android 常用依赖库
  18. 数学知识——博弈论(巴什博奕、尼姆博奕、威佐夫博奕)思路及例题
  19. 易语言禁止服务器,禁止指定程序联网易语言源码
  20. linux命令测网速

热门文章

  1. 头条php,基于PHP的免费新闻头条接口查询
  2. 笔记本电源指示灯维修及散热硅脂替换
  3. 那年杏花微雨,你说你是BOM
  4. Genymotion 各对应版本
  5. Java显示棋盘_Java的GUI学习:显示国际棋盘
  6. C语言编程规范 学习笔记
  7. 综合实践计算机的入门知识教学设计,3-6年级综合实践活动3.我是电脑小画家_教案、教学设计_市级优课(0001)【信息技术】.doc...
  8. 监听通知栏内容,获取通知栏消息,安卓原生SDK扩展
  9. 开源在线excel编辑器_推荐3款爽到爆的在线网站
  10. 基于Springboot的在线租车,自租车,企业租车管理系统,基于Idea开发