java插件化开发

前言

最近使用JavaFx开发小工具,工具包含多个功能,当把小工具给同事用时,发现有些功能仅针对我个人使用,其他人不一定用的上,于是打算将这些工具插件化。

基本思路

java 程序多以jar形式存在,因此所谓插件就是一个个jar包,开发思路如下:

  1. 主程序定义插件接口
  2. 插件项目引入主程序jar,并实现插件接口
  3. 主程序通过类加载器加载插件jar中实现了插件接口的类。

详细步骤

插件开发

  1. 主程序中需要定义一个插件主入口的接口,这里定义为indi.ta.worktools.ext.PluginService,包含一个主入口方法void service()
public interface PluginService {
    /**
     * 插件运行方法
     */
    void service();
}
  1. 在主程序中开放一些可以提供给插件程序调用的接口,比如:提供插件与主程序界面交互的方法;
  2. 将主程序打包成 jar 包,用来给插件项目作为依赖引入;
  3. 新建 Java 项目用来开发插件,引入主程序 jar 包作为依赖;
  4. 插件项目中提供一个实现了indi.ta.worktools.ext.PluginService接口的类,作为插件的主入口;
  5. 项目类路径下添加 META-INF 目录,该目录下添加文件plugin.json,内容如下:
{
  "name": "测试插件",
  "version": "1.0",
  "mainClass": "indi.testplugin.TestPlugin"
}

name:插件名称;
version:插件版本;
mainClass:插件主类名(实现了indi.ta.worktools.ext.PluginService接口的类.

  1. 将插件打成 jar 包(不需要包含3中引入的主程序jar),置于主程序的plugins目录(也可以自行指定目录)下;

插件加载

  1. 主程序定义插件类Plugin,用于封装从plugin.json读取的插件信息,其中字段service就是用来保存插件中实现了indi.ta.worktools.ext.PluginService接口的类的实例:
public class Plugin {
    /**
     * 插件名称
     */
    private String name;
    /**
     * 插件版本
     */
    private String version;
    /**
     * 插件路径
     */
    private String path;

    /**
     * 主类类名
     */
    private String mainClass;
    /**
     * 实现类
     */
    private PluginService service;
}
  1. 创建插件加载类,从插件目录中读取所有插件,readPlugin()方法用来从jar包中读取插件信息到Plugin类中,调用插件的时候,通过 PluginPluginService service调用service()方法就可以唤起插件了
public class PluginLoader {

    /**
     * 加载所有插件,返回一个插件数组
     */
    public static List<Plugin> load() throws Exception {
        List<Plugin> plugins = new ArrayList<>();

        File parentDir = new File("plugins");
        File[] files = parentDir.listFiles();
        if (null == files) {
            return Collections.emptyList();
        }

        // 此处从 plug-ins 文件夹下加载所有插件
        Plugin plugin;
        String path;
        for (File dir : files) {
            if (!dir.isDirectory()) {
                break;
            }
            // 插件jar包需与插件根目录同名
            path = dir + System.getProperty("file.separator") + dir.getName() + ".jar";
            plugin = readPlugin(path);
            plugins.add(plugin);
        }
        return plugins;
    }

    /**
     * 从插件jar包中读取 json 文件到 Plugin 类
     *
     * @param path jar 相对路径
     */
    private static Plugin readPlugin(String path) throws IOException {
        JarFile jarFile = new JarFile(path);
        ZipEntry zipEntry = jarFile.getEntry("META-INF/plugin.json");
        InputStream is = jarFile.getInputStream(zipEntry);
        BufferedReader br = new BufferedReader(new InputStreamReader(is));
        String temp;
        StringBuilder sb = new StringBuilder();
        while ((temp = br.readLine()) != null) {
            sb.append(temp);
        }
        Plugin plugin = null;
        try {
            plugin = JSON.parseObject(sb.toString(), Plugin.class);
            plugin.setPath(path);

            URL[] url = new URL[]{new URL("file:" + path)};
            URLClassLoader loader = new URLClassLoader(url);
            Class<?> clazz = loader.loadClass(plugin.getMainClass());

            plugin.setService((PluginService) clazz.newInstance());
        } catch (Exception e) {
            new ExceptionAlter(e).show();
        }
        return plugin;
    }