需求
一个服务程序(jar),定义了一些RPC的服务,希望能实现在运行时动态加载外部某个目录下的jar,运行某个特定服务。
为什么需要插件模式
- 解耦。特定服务的实现与服务框架解耦,便于独立开发、维护和拓展。
- 可选。缩小服务模块的体积。实际业务不必要的服务可裁剪。
- 开放。开发人员可自行开发服务实现,放在插件目录。
思路
-
插件协议:所有的插件模式一定有一套协议,由插件的调用方和实现方共同遵守。这个协议考虑用Java的SPI实现。
-
动态加载:通过自定义类加载器加载外部目录下的jar。
具体实现(https://github.com/tianshanfz/java-plugin-demo)
- 定义插件接口。例:PluginInterface.java
public interface PluginInterface { int sayHello(String name); }
- 实现插件。创建插件工程,实现插件接口,然后根据Java SPI的规则,在resources目录下创建META-INF/services目录,并以插件接口命名,内容为实现接口的类名。例:
- 在插件调用方工程中,创建类加载器,加载插件目录下所有jar。例:
File dir = new File(PLUGIN_DIR); File[] files = dir.listFiles(); ArrayList<URL> urls = new ArrayList<URL>(); for (File file : files) { if (file.getName().endsWith(".jar")) { System.out.println("loading new plugin " + file.getAbsolutePath() + "..."); urls.add(file.toURI().toURL()); } } int validSize = urls.size(); System.out.println("totally load " + validSize + " plugin jars." ); pluginClassLoader = new URLClassLoader(urls.toArray(new URL[validSize]) );
- 在需要调用插件的地方,通过ServiceLoader加载指定接口的插件:
PluginInterface ret = null; ServiceLoader<PluginInterface> demoServiceLoader = ServiceLoader.load(PluginInterface.class,pluginClassLoader); Iterator<PluginInterface> it = demoServiceLoader.iterator(); while (it.hasNext()) { ret = it.next(); System.out.println("new demo plugin found: " + ret.getClass().getName()); }
结尾
本文仅记录了一种可行的支持动态加载插件的方案,如有更合理的方案或者不当之处,欢迎讨论。