Dissect Eclipse Plugin Framework (4)
准备工作做完,就可以来看看具体实现过程。
我们模拟的几个重要的类是:
Plugin: 插件类,描述每个具体插件;
PluginDescriptor: 插件描述符,记录了插件的ID、Name、Version、依赖、扩展点等;
PluginManager: 插件管理器,负责所有插件资源的管理,包括插件的启动、停止、使能(Enable/Disable)等等;
PluginRegistry: 插件注册表,提供了一个由插件ID到Plugin的映射;
我们首先来定义一个简单的Plugin:
- public abstract class Plugin {
- /**
- * Plugin State
- */
- private boolean started_;
- private final PluginManager manager_;
- private final IPluginDescriptor descriptor_;
- public Plugin(PluginManager manager, IPluginDescriptor descr) {
- manager_ = manager;
- descriptor_ = descr;
- }
- /**
- * @return descriptor of this plug-in
- */
- public final IPluginDescriptor getDescriptor() {
- return descriptor_;
- }
- /**
- * @return manager which controls this plug-in
- */
- public final PluginManager getManager() {
- return manager_;
- }
- final void start() throws PluginException {
- if (!started_) {
- doStart();
- started_ = true;
- }
- }
- final void stop() throws PluginException {
- if (started_) {
- doStop();
- started_ = false;
- }
- }
- public final boolean isActive() {
- return started_;
- }
- /**
- * Get the resource string
- * @param key
- * @return
- */
- public String getResourceString(String key) {
- IPluginDescriptor desc = getDescriptor();
- return desc.getResourceString(key);
- }
- /**
- * Get the Plugin Path
- *
- * @return
- */
- public String getPluginPath() {
- return getDescriptor().getPluginHome();
- }
- /**
- * Template method, which will do the really start work
- *
- * @throws Exception
- */
- protected abstract void doStart() throws PluginException;
- /**
- * Template method, which will do the really stop work
- *
- * @throws Exception
- */
- protected abstract void doStop() throws PluginException;
- }
可见,这只是一个抽象类,每个插件需要定义自己的派生自"Plugin"的子类,作为本插件的一个入口。其中doStart和doStop是两个简单的模板方法,每个插件的初始化和资源释放操作可以定义在这里。
接下来我们看看系统的启动流程:首先将所有的插件清单读入("plugin.xml"),并根据这个文件解析出PluginDescriptor(包括这个Plugin的所有导出库、依赖插件、扩展点等等),放到PluginRegistry中。这个过程也是整个插件平台的一个非常重要的部分,需要从插件清单中解析的部分包括:
- 每个插件所依赖的的插件列表(在"plugin.xml"中用"require" element标识);
- 每个插件要输出的资源和类(在"plugin.xml"中用"library" element标识);
- 每个插件所声明的扩展点列表;
- 每个插件所声明的扩展列表(扩展其它扩展点的扩展)。
当把所有的插件信息都读入到系统中,就可以根据自己的需要来启动指定的插件了(比如,在Xerdoc DS中,首先,我们会启动Core插件)。
启动一个插件的步骤是:
- public Plugin getPlugin(String id) throws PluginException {
- ... ...
- IPluginDescriptor descr = pluginRegistry_.getPluginDescriptor(id);
- if (descr == null) {
- throw new PluginException("Cannot found this plugin " + id);
- }
- result = activatePlugin(descr);
- return result;
- }
- private synchronized Plugin activatePlugin(IPluginDescriptor descr)
- throws PluginException {
- ... ...
- try {
- try {
- // 首先需要检查这个插件所依赖的插件是否都已经启动,
- // 如果没有,则需要先启动那些插件,才能启动本插件
- checkPrerequisites(descr);
- } catch (PluginException e) {
- badPlugins_.add(descr.getId());
- throw e;
- }
- // 得到插件的主类名
- // 这个信息也是定义在"Plugin.xml"中,
- // 并且在加载插件信息的时候读入到PluginDescriptor中的
- String className = descr.getPluginClassName();
- if ((className == null) || "".equals(className.trim())) {
- result = null;
- } else {
- Class pluginClass;
- try {
- // 用每个插件自己的PluginClassLoader来得到这个插件的主类
- pluginClass = descr.getPluginClassLoader().loadClass(
- className);
- } catch (ClassNotFoundException cnfe) {
- badPlugins_.add(descr.getId());
- throw new PluginException("can't find plug-in class "
- + className);
- }
- try {
- Class pluginManagerClass = getClass();
- Class pluginDescriptorClass = IPluginDescriptor.class;
- Constructor constructor = pluginClass
- .getConstructor(new Class[] { pluginManagerClass,
- pluginDescriptorClass });
- // 调用插件默认的构造函数
- // Plugin(PluginManager, IPluginDescriptor);
- result = (Plugin) constructor.newInstance(new Object[] {
- this, descr });
- } catch (InvocationTargetException ite) {
- ... ...
- } catch (Exception e) {
- ... ...
- }
- try {
- result.start();
- } catch (Exception e) {
- ... ...
- }
- ... ...
- }
- }
- return result;
- }
其实最核心的工作就是三步:
- 首先检查这个插件所依赖的其它插件是否已经被启动,如果没有,则需要首先将那些插件启动;
- 根据类名,用插件类加载器加载这个类(这个类是Plugin类的一个派生类);
- 调用Plugin类的默认的构造函数(主要是为了将PluginManager和PluginDescriptor传进去)。
这就用到了前面说过的类加载器(ClassLoader),Eclipse中定义了插件类加载器(PluginClassLoader)。插件类加载器(PluginClassLoader)其实很简单,它派生自URLClassLoader -
This class loader is used to load classes and resources from a search path of URLs referring to both JAR files and directories.
PluginClassLoader会将PluginDescriptor中声明输出的路径(可以是JAR文件,可以是类路径,可以是资源路径)加入到此URLClassLoader类加载器的搜索路径中去。
比如:
- <runtime>
- <library id="com.xerdoc.desktop.view.htmlrender" path="XerdocDSHTMLRender.jar" type="code">
- <export prefix="*"/>
- </library>
- <library id="resources" path="image/" type="resources">
- <export prefix="*"/>
- </library>
- </runtime>
PluginClassLoader会将"XerdocDSHTMLRender.jar"和"image/"目录都加入到URLClassLoader的类搜索路径中去,这样,就可以用这个类加载器来加载相应的插件类和资源了。
PluginClassLoader加载插件的策略是:
首先试图从父ClassLoader加载(系统类加载器),如果无法加载则会试图从本类加载器加载,如果还是找不到,这时的行为与一般的URLClassLoader不同,也PluginClassLoader最大的特色:它会试图从此插件的需求依赖插件("require"形容的插件)中去加载需求的类或者资源。
比如下面这个例子:
- <requires>
- <import plugin-id="com.xerdoc.desktop.core" plugin-version="0.4.0" match="compatible"/>
- <import plugin-id="com.xerdoc.desktop.core.ui.swt" plugin-version="0.2.0" match="compatible"/>
- </requires>
这是Office Excel Parser插件清单的片断。如果这个插件的类加载器无法加载某个需要的类或者资源,将会委托"com.xerdoc.desktop.core"插件或者"com.xerdoc.desktop.core.ui.swt"插件的类加载器去加载。
系统Native Library(比如SWT插件中要用到的系统本地库)的加载也是PluginClassLoader的功能。
就举SWT的例子,熟悉SWT的人都知道,运行SWT应用程序的时候需要添加以下命令行参数:
-Djava.library.path="/home/elan/workspace/xerdoc_ds/swt-native"
这就是为了让类加载器能够在相应的目录("/home/elan/workspace/xerdoc_ds/swt-native")下面找到需要的系统本地库资源。但是这样的命令行参数对于某些应用并不合适。对于Xerdoc DS来说,SWT的UI界面也同样是一个插件,同时也还会有其它用到本地资源库的插件,总不能增加一个插件还要修改命令行参数吧?因此,需要修改ClassLoader,使之能够加载指定的Native Library。方法就是重写findLibrary函数:
- /**
- * Returns the absolute path name of a native library. The VM invokes this
- * method to locate the native libraries that belong to classes loaded with
- * this class loader. If this method returns <tt>null</tt>, the VM
- * searches the library along the path specified as the
- * "<tt>java.library.path</tt>" property. </p>
- *
- * @param libname
- * The library name
- *
- * @return The absolute path of the native library
- *
- * @see System#loadLibrary(String)
- * @see System#mapLibraryName(String)
- *
- * @since 1.2
- */
- protected String findLibrary(String libname) {
- return null;
- }
重写其实很简单,只需要根据每个插件需要加载Native Library的目录来搜索就可以了,比如这是Linux下面,SWT插件清单的片断:
- <library id="swt-native" path="swt-native/" type="native_library">
- <export prefix="*"/>
- </library>
这样,在找Native Library的时候就可以从"$PLUGIN_HOME/swt-native/"这个目录中找到相应的so文件(Linux下的动态链接库)。
最后来说说资源文件(比如说png, ico等等),其实同加载类资源一样,只要在"library"中声明的目录,就都会加入到类加载器的类搜索路径中去,这样,我们都可以直接访问里面的资源。
Popularity: 28%
Related entries:
- No Related Posts

March 8th, 2008 at 2:54 pm
相当感谢你的文章。我最近正为这个东西发愁呢,看到你的文章就好像是看到了黎明的曙光一样。希望能够将来成长得和你一样厉害,可以去帮助更多的人。
工作顺利!