HOW TO MAKE PLUGIN FRAMEWORK
本文将为你展示如何基于Java[1]构建一个完整可用的插件框架(Plugin Framework)。
关于Plugin Wikipedia[2]是这样描述的
我们可以简单的理解插件就是应用程序的一个模块,但是这个模块又是相对独立的自治域,这个模块可以引用(依赖)其他的模块,这个模块可以通过某种方式于其他的模块交互。应用程序不依赖任何插件就可以运行,但是需要加载相应的插件才能实现特定的功能。
在了解了插件的基本知识后,我们分析一下构建一个插件框架需要处理那些问题
1.定义
我们希望插件保持内聚,这样能方便插件的安装和卸载,在逻辑实体上希望插件的定义尽可能的简单,下面我们给出插件的
物理存储布局和逻辑实体
1.1物理布局
所谓物理布局就是插件是如何存储的,下图给出了一个简单的插件布局
插件以文件夹为单位,每个文件夹是单独的插件,每个插件文件夹都应该包含libs目录和config目录这两个目录分别存储了
插件需要的库文件和配置文件,libs目录下的文件会在插件加载的过程中自动加载,还有就是最重要的插件描述文件plugin.xml。一个简单的插件描述文件如下所示
<?xml version="1.0" encoding="utf-8"?> <plugin id="cn.edu.nwpu.as.VmasPlugin" name="vmas" version="1.0.0" author="liuqiang"> <runtime path="cn.edu.nwpu.as.VmasPlugin.jar"/> <require id="org.opensolaris.gear.processtree.ProcessTreePlugin" version="1.0.1"/> </plugin>
插件应该通过插件描述文件plugin.xml描述自身的一些基本属性,包括 插件的id,这个id应该是全局唯一的。插件的名字,版本
,作者等等。除此之外插件还应该描述自身的运行时环境,依赖的插件。
关于运行时环境
插件应该将自己所需的库文件和class文件都放到libs目录下,这些文件将被加载到插件的classpath中。以来关系也非常重
要,在插件加载的过程中,插件管理器会根据plugin.xml中描述的依赖关系选择插件加载的次序。不正确的插件依赖关系会导致
加载失败。
1.2逻辑实体
一个简单的接口定义了插件的逻辑实体
public interface Plugin { public void init(); public void start(); public void destory(); public IExtension getExtension(String id); }
所有实现了这个接口的类都可以认为是一个插件,其中init方法在插件初始化的时候调用并且只调用一次,start方法在插件启
动的时候调用,destory在插件销毁的时候调用。为了确保重复加载,start方法和destory方法应该保证对称。
2 插件管理器
插件管理器负责管理和插件有关的一切,在系统启动的时候,插件管理期扫描插件目录,找到符合定义的插件目录,验证合
法性,读取插件描述文件,扫描库文件。然后根据插件描述文件构建插件依赖关系图,确定插件启动顺序,然后按照顺序启动插
件,设定安全管理器,设定classloader,执行init方法,执行start方法。
以上就是插件管理器加载插件的全过程,在详细讲解每一步之前我们先了解下Java的classloader。
以下是JDK中关于ClassLoader的解释[3]
下面这张图说明了Java类加载器的结构
上图很好的解释了Java程序的类加载过程,首先是Bootstrap加载jre的类,然后时System加载CLASSPATH下的类,然后是应用
程序的类加载器(如果有的话)加载属于应用程序自己的类,接着PluginFrameWork加载插件管理器所需类,从图中我们可以看
到插件管理器加载的类被所有的插件共享,但是每个插件都有自己的命名空间,这很好的解决了插件之间的库隔离问题,两个
不同的插件现在可以加载同一个类的两个版本而不会有任何冲突。
为了实现上面的类加载方式,我们简单的扩展URLClassLoader实现了简单的PluginClassLoader
public class PluginClassLoader extends URLClassLoader { private PluginImage image; private ClassLoader[] requireClassLoader; public PluginClassLoader(URL[] urls) { super(urls); } public PluginClassLoader(PluginImage image,ClassLoader parent) { super(image.getUrls(),parent); this.image = image; image.pluginClassLoader = this; if (image.getRequires() == null) { requireClassLoader = null; } else { requireClassLoader = new ClassLoader[image.getRequires().size()]; for(int i=0;i<image.getRequires().size();i++){ requireClassLoader[i] = PluginManager.getInstance() .getPluginImage(image.getRequires().get(i).getId()).getPluginClassLoader(); } } } protected Class<?> findClass(String name) throws ClassNotFoundException { Class<?> c = null; try { c = super.findClass(name); } catch (ClassNotFoundException e) { if (requireClassLoader == null) throw new ClassNotFoundException(); else { for (ClassLoader l : requireClassLoader) { try { if (l != null) c = l.loadClass(name); } catch (ClassNotFoundException ee) {} } if (c == null) { throw new ClassNotFoundException(); } } } return c; } protected String findLibrary(String name) { Hashtable<String, String> libs = image.getLibrarys(); if (libs.containsKey(name)) { return libs.get(name); // full name } else if (libs.containsKey("lib" + name + ".so")) { return libs.get("lib" + name + ".so"); } else { return null; } } public PluginImage getImage() { return image; } }
[1]:http://www.java.com/
[2]:http://zh.wikipedia.org/wiki/Plug-in
[3]:http://download.developers.sun.com.cn/javadoc/html/zh_CN/api/java/lang/ClassLoader.html