基于SPI的Java插件技术
最近在弄一个大型的渗透测试辅助工具的项目,正愁插件这里怎么弄呢,因为那种直接调用命令行之类的方法太不优雅了,我实在是不喜欢,正好在翻文章的时候翻到了一个基于SPI的Java插件技术,本着看到需要的东西就要弄个demo实践一下,下面是实践内容
SPI机制
SPI全称为 (Service Provider Interface),是JDK内置的一种服务提供发现机制。工作原理就是ClassPath路径下的META-INF/services文件夹中, 以接口的全限定名来命名文件名,文件里面写该接口的实现。然后再资源加载的方式,读取文件的内容(接口实现的全限定名), 然后再去加载类。SPI可以很灵活的让接口和实现分离, 让api提供者只提供接口, 第三方来实现,使用SPI机制最典型的例子就是Java的数据库驱动。
缺点
虽然ServiceLoader也算是使用的延迟加载,但是基本只能通过遍历全部获取,也就是接口的实现类全部加载并实例化一遍。如果你并不想用某些实现类,它也被加载并实例化了,这就造成了浪费。获取某个实现类的方式不够灵活,只能通过Iterator形式获取,不能根据某个参数来获取对应的实现类。
实现
首先新建我们加载插件的项目,分别有三个文件,提供插件服务的接口IPluginService
、插件加载类PluginLoader
以及我们的测试主类PluginMain
,下面一一列举
IPluginService:
public interface IPluginService {
/*
*@Author:RabbitQ
*@CreateData:2023/8/21 23:07
*decription:插件功能主入口方法
*/
void service();
/*
*@Author:RabbitQ
*@CreateData:2023/8/21 23:07
*decription:插件名成,通常用于展示在界面
*/
String name();
/*
*@Author:RabbitQ
*@CreateData:2023/8/21 23:07
*decription:表示当前插件的版本
*/
String version();
}j
PluginLoader:
public class PluginLoader {
/*
*@Author:RabbitQ
*@CreateData:2023/8/21 23:09
*decription:插件加载的相对路径:这里表示所有的插件jar都放在主程序jar同级目录的plugins文件夹下
*/
public static final String PLUGIN_PATH = "plugins";
public static List<IPluginService> loadPlugins() throws MalformedURLException {
List<IPluginService> plugins = new ArrayList<>();
File parentDir = new File(PLUGIN_PATH);
File[] files = parentDir.listFiles();
if (null == files) {
return Collections.emptyList();
}
// 从目录下筛选出所有jar文件
List<File> jarFiles = Arrays.stream(files)
.filter(file -> file.getName().endsWith(".jar"))
.collect(Collectors.toList());
URL[] urls = new URL[jarFiles.size()];
for (int i = 0; i < jarFiles.size(); i++) {
// 加上 "file:" 前缀表示本地文件
urls[i] = new URL("file:" + jarFiles.get(i).getAbsolutePath());
}
URLClassLoader urlClassLoader = new URLClassLoader(urls);
// 使用 ServiceLoader 以SPI的方式加载插件包中的 IPluginService 实现类
ServiceLoader<IPluginService> serviceLoader = ServiceLoader.load(IPluginService.class, urlClassLoader);
for (IPluginService iPluginService : serviceLoader) {
plugins.add(iPluginService);
}
return plugins;
}
}
PluginMain:
public class PluginMain {
public static void main(String[] args) throws MalformedURLException {
System.out.println("开始加载插件");
List<IPluginService> services = PluginLoader.loadPlugins();
System.out.println(services.size() + "个插件加载成功n");
for (int i = 0; i < services.size(); i++) {
IPluginService service = services.get(i);
System.out.println("===插件" + i + "===");
System.out.println("插件名:" + service.name());
System.out.println("版本号:" + service.version());
System.out.println("插件服务启动:");
service.service();
}
}
}
接下来是我们的测试插件项目,首先把上面的项目要打包成一个jar包添加到测试插件项目的库文件中(这里我是添加到了本地的maven仓库,然后maven引用的,库依赖出了迷之问题,两个方法都ok),因为插件类需要继承我们的IPluginService
接口,共有一个文件Plugin1Service
,内容为:
public class Plugin1Service implements IPluginService {
@Override
public void service() {
// 这里可以做插件需要做的任何事情,这里仅用一句打印表示插件的功能被调用
System.out.println(name() + "功能调用");
}
@Override
public String name() {
return "插件一";
}
@Override
public String version() {
return "1.0";
}
}
然后在resource
目录下新建META-INF
,在META-INF
目录下新建services
目录,然后新建一个文件,要用你的加载插件类的项目的全限定类名,这里我的是com.rabbitq.IPluginService
,内容为你的插件类的全限定类名org.example.Plugin1Service
具体情况如下图:
然后打包你的插件项目并将打包好的jar包放到你的插件加载项目的根目录的plugins目录下,然后运行你的插件加载项目,结果如下:
上面项目已放在github上,项目代码地址:https://github.com/Rabb1tQ/PluginLoaderUtil
引用:
https://blog.csdn.net/weixin_44155115/article/details/127354183
原文始发于微信公众号(天幕安全团队):基于SPI的Java插件技术
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论