SPI思想应用之拔插式插件
1. 插件简介
- 插件在百度百科中解释为:
插件是一种遵循一定规范的应用程序接口编写出来的程序。其只能运行在程序规定的系统平台下(可能同时支持多个平台),而不能脱离指定的平台单独运行。因为插件需要调用原纯净系统提供的函数库或者数据。很多软件都有插件,插件有无数种。例如在IE中,安装相关的插件后,WEB浏览器能够直接调用插件程序,用于处理特定类型的文件。
1.1 插件应用
插件已经不再是什么新鲜的事物了,在很多地方都可以看到各种各样的插件,特别是那些业界有名的软件系统。
- 比如我们熟悉的谷歌浏览器就支持各种各样的插件
- 这个就是我常用的谷歌插件,连现在的软件开发工具都支持各种插件,比如IntelliJ IDEA
- 这些插件都在一定程度上丰富了应用程序的功能,而且都可以按需定制,这种即插即用的高度扩展模式是现在优秀软件必备的一种特征。
1.2 插件思想
软件系统通过定制插件来实现功能的定制与扩展,极大程度上丰富了软件系统本身。试想一个软件想要丰富软件的功能那么一开始就装上全套功能,软件系统必然庞大无比,运行软件的开销也会令机器不堪重负,更何况软件的功能并不是都会用到,很多资源其实都在“尸位素餐”。通过插件来丰富软件系统,当我们需要用到某个功能时寻找到合适的插件接入系统,就像把插头插上插座接通电源,软件系统就拥有了插件赋予的功能。针对不同的用户,可以在软件系统中定制自己喜欢的插件,也就是给软件定制适合自己的功能,软件开销自己把握,这样软件系统符合我的需求也减少了很多不必要的开销。
在JAVA中的SPI思想一文的介绍中,提供了一种为服务寻找服务实例的思想。这种通过接口定义一种标准,利用配置寻找这套标准的实现,然后使用实现的手段极大程度提高了扩展性。SPI多运用在框架提高组件的扩展上面,将这种思维发散开来,结合插件的这种模式运用到服务层面实现一个插拔式的插件服务。
客户端给服务端发送不同的业务请求,服务端根据业务类型在插件注册表寻找不同的插件来解决对应的业务。当客户需要增加一种业务时,开发者只需要开发该类业务的一种特定处理插件就可以轻松接入系统完成系统的升级。当某个业务被时代淘汰无人使用的时候,及时在服务注册表下架该插件,服务管理就是这么轻松。
- 插件注册表对于开发人员来说就是一种约定的配置文件,可以是xml、数据库表亦或是一个map结构。
- 插件就是服务外的一个具有特殊意义的jar包。
2. 插件案例
项目工程结构依旧和SPI中的案例一下,如下:
- 插件定义工程主要就是定义插件接口的,规定插件生产的规格,就像插座得定义插头是双脚还是三角的,接口必须得有标准。
- 插件工程就是实现了插件接入规则的一些具有特定功能的jar包。
- 插件应用工程加载插件,根据客户不同需求使用不同插件进行处理。
- 插件注册表使用properties文件的KV形式来注册插件的信息。
2.1 插件的定义
- 在【commons-api】工程中定义插件的规范,即定义接口,接口名称为ComponentService,内容如下:
public interface ComponentService
{/*** 获取组件名称* @return 组件名称*/String getComponentName();
}
- 这里定义了一套插件的生产规范ComponentService
2.2 插件的实现
- 在【component-A】工程中引入插件的生产标准,即在pom中添加插件规范
<dependencies><dependency><groupId>com.xxxx</groupId><artifactId>commons-api</artifactId><version>1.0-SNAPSHOT</version></dependency>
</dependencies>
- 在【component-A】工程中引入了插件实现规范,接下来实现插件规范生成具体插件,即实现接口,类名为ComponentA,内容如下:
public class ComponentA implements ComponentService
{/*** 组件名称*/private static final String COMPONENT_NAME = "组件A";@Overridepublic String getComponentName(){return COMPONENT_NAME;}
}
- 在【component-B】工程中同样引入插件的生产标准,即在pom中添加插件规范
<dependencies><dependency><groupId>com.xxxx</groupId><artifactId>commons-api</artifactId><version>1.0-SNAPSHOT</version></dependency>
</dependencies>
- 在【component-B】工程中实现插件规范的接口,类名为ComponentB,内容如下:
public class ComponentB implements ComponentService
{/*** 组件名称*/private static final String COMPONENT_NAME = "组件B";@Overridepublic String getComponentName(){return COMPONENT_NAME;}
}
- 这里按照插件的生产标准实现两套插件,分别为ComponentA和ComponentB
2.3 插件的使用
在插件的应用工程中需要解决如何加载插件、如何寻找插件的问题,这里使用一个插件管理器来统筹这些事情。
- 在【component-application】工程中引入插件标准,表示需要应用这类插件,即在pom中添加依赖,如下:
<dependencies><dependency><groupId>com.xxxx</groupId><artifactId>commons-api</artifactId><version>1.0-SNAPSHOT</version></dependency>
</dependencies>
- 在【component-application】工程中实现插件管理器,类名为PluginManager,内容如下:
public class PluginManager
{/*** 插件配置*/private Properties configs;/*** 插件集合*/private final Map<String, ComponentService> plugins = new ConcurrentHashMap<>();public PluginManager(String configPath){InputStream is = null;try{// 获取配置文件流is = Thread.currentThread().getContextClassLoader().getResourceAsStream(configPath);configs = new Properties();// 加载插件配置configs.load(is);}catch (FileNotFoundException e){// 配置文件不存在e.printStackTrace();}catch (IOException e){// 文件读取异常e.printStackTrace();}finally{if (!Objects.isNull(is)){try{// 关闭输入流is.close();}catch (IOException e){// 关闭输入流异常e.printStackTrace();}}}}/*** 加载插件* @param pluginPath 插件路径* @return 插件实例*/private ComponentService loadComponent(String pluginPath){try{JarFile jarFile = new JarFile(pluginPath);URL[] urls = new URL[]{new URL("file:" + pluginPath)};URLClassLoader urlClassLoader = new URLClassLoader(urls);Enumeration<JarEntry> entries = jarFile.entries();// 扫描插件并通过反射创建插件实例while (entries.hasMoreElements()){JarEntry jarEntry = entries.nextElement();if (!jarEntry.isDirectory() && jarEntry.getName().endsWith(".class")){String className = jarEntry.getName().split("\\.")[0].replace("/", ".");Class<?> classObject = urlClassLoader.loadClass(className);if (ComponentService.class.isAssignableFrom(classObject)){ComponentService componentService = (ComponentService) classObject.newInstance();return componentService;}}}}catch (IOException e){// 文件读取异常e.printStackTrace();}catch (IllegalAccessException e){e.printStackTrace();}catch (InstantiationException e){e.printStackTrace();}catch (ClassNotFoundException e){e.printStackTrace();}return null;}/*** 获取插件* @param pluginName 插件名称* @return 插件实例*/public ComponentService getPlugin(String pluginName){if (!Objects.isNull(pluginName) && configs.containsKey(pluginName)){// 采用懒加载模式加载插件,即用到时采取加载需要的插件if (plugins.containsKey(pluginName)){return plugins.get(pluginName);}else{// 插件集合中不存在插件就去加载插件ComponentService component = loadComponent(configs.getProperty(pluginName));// 将加载的插件放入插件集合plugins.put(pluginName, component);return component;}}return null;}
}
在插件管理器中,需要读取插件注册表来明确服务有哪些插件可以使用、插件的信息是什么样的。加载插件就是将jar里面的class文件通过类加载系统搬运到JVM中进行实例化。插件管理器对外只提供获取插件的方法让服务来使用插件。
- 将【component-A】工程和【component-B】工程进行打包制作成jar文件,放在磁盘的某个位置,这里我选择放在D:\plugins目录下
- 在【component-application】工程中新建插件注册表文件,文件名为plugin.properties,文件内容如下:
# 插件注册表:插件名称 - 插件路径
pluginA=D:/plugins/component-A-1.0-SNAPSHOT.jar
pluginB=D:/plugins/component-B-1.0-SNAPSHOT.jar
- 在【component-application】工程中新建应用程序启动类应用插件,类名称为ComponentApplication,内容如下:
public class ComponentApplication
{public static void main(String[] args){// 建造插件管理器PluginManager pluginManager = new PluginManager("plugin.properties");// 获取插件A实例调用ComponentService pluginA = pluginManager.getPlugin("pluginA");System.out.println("插件名称:" + pluginA.getComponentName());// 获取插件B实例调用ComponentService pluginB = pluginManager.getPlugin("pluginB");System.out.println("插件名称:" + pluginB.getComponentName());}
}
- 启动【component-application】工程中ComponentApplication类的main方法,结果如下:
SPI思想应用之拔插式插件相关推荐
- 分布式面试 - dubbo 的 spi 思想是什么?
分布式面试 - dubbo 的 spi 思想是什么? 面试题 dubbo 的 spi 思想是什么? 面试官心理分析 继续深入问呗,前面一些基础性的东西问完了,确定你应该都 ok,了解 dubbo 的一 ...
- 炉石一键拔网线_炉石传说拔网线插件
iefans下载为用户提供的炉石一键拔网线插件是一款专门针对炉石传说酒馆战棋所推出的辅助工具,用户通过使用炉石传说拔网线插件可以在不影响网络环境的情况下对游戏进行断网操作,这样就能够帮助用户一键跳过战 ...
- Dubbo实现原理之基于SPI思想实现Dubbo内核
dubbo中SPI接口的定义如下: @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) public ...
- Django【设计】可插拔的插件方式实现
需求: 在CMDB系统中,我们需要对资产进行采集和资产入库,包括serverBasic.disk.memory.nic信息等,客户端需要采集这些硬件的信息,服务端则负责资产入库,但是需要采集的硬件并不 ...
- 交互平板:拆机 “返厂” OPS拔插式电脑模块,ITX主板是否适合你?
原网址: https://www.bilibili.com/video/av52781834?from=search 微型计算机 CPU: I5-6500 内存:4G DDR4 硬盘:128G
- 导入第三方组件_大型 web 应用公共组件架构是如何来的?
来源:腾讯AlloyTeam https://mp.weixin.qq.com/s/gVUJRF_nLHOT_iXDXQ8F-w 腾讯文档公共组件历史包袱 1. 架构问题--开发层面 腾讯文档管理的公 ...
- java spi 扩展_【扩展和解耦】JAVA原生SPI实现插件扩展
Java极客 | 作者 / 铿然一叶 这是Java极客的第 81 篇原创文章 相关阅读: 1. 什么是插件 通俗的讲插件有以下特征: 1.增加或者替换已有能力 2.不影响原有功能 3.对原有系 ...
- JDK、Spring、Dubbo SPI 原理介绍
导读: 需求变化是程序员生命中唯一不变的事情,本文将介绍 JDK/Spring/Dubbo 中的 SPI 机制,以此来帮助我们编写出一套可扩展性强,易于维护的代码框架. 文|杨亮 网易云商高级 Jav ...
- java spi使用详解
前言 SPI英文全称为Service Provider Interface,顾名思义,服务提供者接口,它是jdk提供给"服务提供厂商"或者"插件开发者"使用的接 ...
最新文章
- 不选择互联网行业,学弟学妹可以选择这些新兴科技行业发展!
- 基于图搜索的路径规划方法
- Pentium 4处理器架构/微架构/流水线 (7) - NetBurst前端详解 - 分支预测
- java----动态绑定
- Eclipse或MyEclipse—在Eclipse或MyEclipse中的操作(1)
- Qt开源作品41-网络调试助手增强版V2022
- ESP32|基于ESP32制作的低成本、可拓展性高的NES游戏机(1)(开源ESP32 NES模拟器)-效果演示及介绍
- JAVA_OPTS设置
- 数据库操作之导入导出dmp
- android消息发送字符串,android - 从Android客户端通过HTTP在HL7消息中发送base64字符串时遇到错误 - 堆栈内存溢出...
- 将销售订单号 + 行号 带到销售出库单上
- 易车与汽车之家的博弈
- 开关电源工作原理浅析
- 家里有黑白照片想要修复,一款小工具帮到你
- 高质量音频转换器,如何转换成mp3音频格式
- 【Unity基础】ugui的基础知识篇
- XELF病毒分析-秘密花园
- 证明SSreg=SYY-RSS最小二乘法的解释变量和非解释变量之间的关系
- iOS内存泄露监测和修复
- Eclipse Theia 揭秘之启动流程篇