Java SPI机制实现插件化扩展功能
1.背景
我们有一个图数据库的服务,用户希望在不修改现有源代码的情况下扩展自定义的分词器,达到可插件式扩展功能的目标。
通过Java的SPI机制实现插件式的扩展功能还是比较简便的,下面分主程序部分和插件实现2部分来说明。
特别的,在实现过程中遇到一个比较怪异的问题:
ServiceLoader.load()
时抛出NoClassDefFoundError
异常,经过Google及StackOverflow都没能找到原因,问题表现与这几个链接中描述的类似:serviceloader-issue-in-jetty、serviceloader-in-glassfish4-java-ee-app、serviceloader-next-causing-a-noclassdeffounderror。
文末会记录一下这个问题的解决过程及原因分析。
2.SPI插件实现要素
主程序部分主要包括:
- 定义插件接口
- 加载插件实现的Jar包
- 加载插件实现类对象
插件实现部分主要包括:
- 实现插件接口
- 配置SPI入口
- 打Jar包
3.实现插件化的流程
下面以扩展一个分词器实例来说明插件化的流程。
-
定义接口
定义接口com.baidu.hugegraph.plugin.HugeGraphPlugin
,内容如下:public interface HugeGraphPlugin { public String name(); public void register(); public String supportsMinVersion(); public String supportsMaxVersion(); }
-
加载插件实现的Jar包
参考SPI官方文档,我们定义了一个目录plugins
来存放插件的Jar包,在启动Java主程序服务时通过参数-Djava.ext.dirs=plugins
指定插件Jar包的目录。当需要扩展新的插件时,只需要把插件Jar包拷贝到plugins
目录下,重启主程序服务即可生效。完整的启动命令示例:java -Djava.ext.dirs=plugins -Dname="HugeGraphServer" ${JAVA_OPTIONS} -cp ${CP}:${CLASSPATH} com.baidu.hugegraph.dist.HugeGraphServer ${APP_ARGS}
-
加载插件实现类实例
在主程序中,我们通过ServiceLoader
来加载所有插件实例。private static void registerPlugins() { LOG.info("Loading plugins..."); ServiceLoader<HugeGraphPlugin> plugins = ServiceLoader.load(HugeGraphPlugin.class); for (HugeGraphPlugin plugin : plugins) { LOG.info("Loading plugin {}({})", plugin.name(), plugin.getClass().getCanonicalName()); try { plugin.register(); LOG.info("Loaded plugin {}", plugin.name())